diff --git a/v2/cmd/wails/flags/init.go b/v2/cmd/wails/flags/init.go index 16d56a207..3f4bff401 100644 --- a/v2/cmd/wails/flags/init.go +++ b/v2/cmd/wails/flags/init.go @@ -11,6 +11,7 @@ type Init struct { InitGit bool `name:"g" description:"Initialise git repository"` IDE string `name:"ide" description:"Generate IDE project files"` List bool `name:"l" description:"List templates"` + Force bool `name:"f" description:"Force init in non-empty directory (use with caution)"` } func (i *Init) Default() *Init { diff --git a/v2/cmd/wails/init.go b/v2/cmd/wails/init.go index f79e37ffc..043a6d811 100644 --- a/v2/cmd/wails/init.go +++ b/v2/cmd/wails/init.go @@ -105,6 +105,15 @@ func initProject(f *flags.Init) error { // Try to discover author details from git config findAuthorDetails(options) + // Safety check: fail if target directory is non-empty + absTargetDir, err := GetAbsoluteTargetDir(f.ProjectDir) + if err != nil { + return fmt.Errorf("failed to resolve target directory path: %w", err) + } + if err := CheckDirectorySafety(absTargetDir, f.Force); err != nil { + return err + } + // Start Time start := time.Now() diff --git a/v2/cmd/wails/init_safety.go b/v2/cmd/wails/init_safety.go new file mode 100644 index 000000000..53142c157 --- /dev/null +++ b/v2/cmd/wails/init_safety.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "path/filepath" + + "github.com/wailsapp/wails/v2/internal/fs" +) + +// GetAbsoluteTargetDir returns the absolute path of the target directory. +// If targetDir is empty, it returns an empty string (no target directory specified). +func GetAbsoluteTargetDir(targetDir string) (string, error) { + if targetDir == "" { + return "", nil + } + return filepath.Abs(targetDir) +} + +// CheckDirectorySafety checks if the target directory is safe to initialize a project in. +// It returns an error if the directory exists and is non-empty, unless force is true. +// The absTargetDir should be an absolute path obtained from GetAbsoluteTargetDir. +func CheckDirectorySafety(absTargetDir string, force bool) error { + // If no target directory is specified, the default behavior creates a new directory + // with the project name, so we don't need to check safety + if absTargetDir == "" { + return nil + } + + // If directory doesn't exist, it's safe + if !fs.DirExists(absTargetDir) { + return nil + } + + // Check if directory is empty + isEmpty, err := fs.DirIsEmpty(absTargetDir) + if err != nil { + return fmt.Errorf("failed to check target directory: %w", err) + } + + // If directory is empty, it's safe + if isEmpty { + return nil + } + + // Directory is non-empty - fail unless force flag is set + if force { + return nil + } + + return fmt.Errorf("target directory '%s' is not empty. Aborting to prevent data loss. Use -f to force init in a non-empty directory", absTargetDir) +} diff --git a/v2/cmd/wails/init_safety_test.go b/v2/cmd/wails/init_safety_test.go new file mode 100644 index 000000000..aac200aad --- /dev/null +++ b/v2/cmd/wails/init_safety_test.go @@ -0,0 +1,177 @@ +package main + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestGetAbsoluteTargetDir(t *testing.T) { + tests := []struct { + name string + targetDir string + wantEmpty bool + wantErr bool + }{ + { + name: "empty string returns empty", + targetDir: "", + wantEmpty: true, + wantErr: false, + }, + { + name: "relative path returns absolute", + targetDir: "relative/path", + wantEmpty: false, + wantErr: false, + }, + { + name: "absolute path returns absolute", + targetDir: "/absolute/path", + wantEmpty: false, + wantErr: false, + }, + { + name: "current directory returns absolute", + targetDir: ".", + wantEmpty: false, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := GetAbsoluteTargetDir(tt.targetDir) + + if (err != nil) != tt.wantErr { + t.Errorf("GetAbsoluteTargetDir() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if tt.wantEmpty && result != "" { + t.Errorf("GetAbsoluteTargetDir() = %v, want empty string", result) + } + + if !tt.wantEmpty { + if result == "" { + t.Error("GetAbsoluteTargetDir() returned empty string, want non-empty") + } + if !filepath.IsAbs(result) { + t.Errorf("GetAbsoluteTargetDir() = %v, want absolute path", result) + } + } + }) + } +} + +func TestCheckDirectorySafety(t *testing.T) { + // Create a temporary directory for testing + tempDir := t.TempDir() + + tests := []struct { + name string + force bool + setup func(t *testing.T) string // returns absolute path to use, may create files + wantErr bool + errMsg string // substring to check in error message + }{ + { + name: "empty target dir string - should be safe", + force: false, + setup: func(t *testing.T) string { return "" }, + wantErr: false, + }, + { + name: "non-existent directory - should be safe", + force: false, + setup: func(t *testing.T) string { + return filepath.Join(tempDir, "nonexistent") + }, + wantErr: false, + }, + { + name: "empty existing directory - should be safe", + force: false, + setup: func(t *testing.T) string { + dir := filepath.Join(tempDir, "empty_dir") + if err := os.Mkdir(dir, 0755); err != nil { + t.Fatalf("failed to create test directory: %v", err) + } + return dir + }, + wantErr: false, + }, + { + name: "non-empty directory with force flag - should be safe", + force: true, + setup: func(t *testing.T) string { + dir := filepath.Join(tempDir, "nonempty_force") + if err := os.Mkdir(dir, 0755); err != nil { + t.Fatalf("failed to create test directory: %v", err) + } + if err := os.WriteFile(filepath.Join(dir, "file.txt"), []byte("content"), 0644); err != nil { + t.Fatalf("failed to create test file: %v", err) + } + return dir + }, + wantErr: false, + }, + { + name: "non-empty directory without force - should return error", + force: false, + setup: func(t *testing.T) string { + dir := filepath.Join(tempDir, "nonempty_no_force") + if err := os.Mkdir(dir, 0755); err != nil { + t.Fatalf("failed to create test directory: %v", err) + } + if err := os.WriteFile(filepath.Join(dir, "file.txt"), []byte("content"), 0644); err != nil { + t.Fatalf("failed to create test file: %v", err) + } + return dir + }, + wantErr: true, + errMsg: "Use -f to force", + }, + { + name: "non-empty directory with .git folder - should return error", + force: false, + setup: func(t *testing.T) string { + dir := filepath.Join(tempDir, "with_git") + if err := os.Mkdir(dir, 0755); err != nil { + t.Fatalf("failed to create test directory: %v", err) + } + if err := os.Mkdir(filepath.Join(dir, ".git"), 0755); err != nil { + t.Fatalf("failed to create .git directory: %v", err) + } + if err := os.WriteFile(filepath.Join(dir, ".git", "config"), []byte("[core]"), 0644); err != nil { + t.Fatalf("failed to create test file: %v", err) + } + return dir + }, + wantErr: true, + errMsg: "not empty", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + absTargetDir := tt.setup(t) + + err := CheckDirectorySafety(absTargetDir, tt.force) + + // Check error expectation + if (err != nil) != tt.wantErr { + t.Errorf("CheckDirectorySafety() error = %v, wantErr %v", err, tt.wantErr) + return + } + + // Check error message contains expected substring + if tt.wantErr && tt.errMsg != "" { + if !strings.Contains(err.Error(), tt.errMsg) { + t.Errorf("CheckDirectorySafety() error = %v, want error containing %q", err, tt.errMsg) + } + } + }) + } +} diff --git a/v3/.gitignore b/v3/.gitignore new file mode 100644 index 000000000..7fb1f736f --- /dev/null +++ b/v3/.gitignore @@ -0,0 +1,12 @@ +examples/kitchensink/kitchensink +cmd/wails3/wails +/examples/systray-menu/systray +/examples/window/window +/examples/dialogs/dialogs +/examples/menu/menu +/examples/clipboard/clipboard +/examples/plain/plain +/cmd/wails3/ui/.task/ +!internal/commands/webview2/MicrosoftEdgeWebview2Setup.exe +internal/commands/appimage_testfiles/appimage_testfiles +testiosapp/ \ No newline at end of file diff --git a/v3/.prettierignore b/v3/.prettierignore new file mode 100644 index 000000000..94c6af38e --- /dev/null +++ b/v3/.prettierignore @@ -0,0 +1 @@ +website \ No newline at end of file diff --git a/v3/.prettierrc.yml b/v3/.prettierrc.yml new file mode 100644 index 000000000..685d8b6e7 --- /dev/null +++ b/v3/.prettierrc.yml @@ -0,0 +1,6 @@ +overrides: + - files: + - "**/*.md" + options: + printWidth: 80 + proseWrap: always diff --git a/v3/ANDROID_ARCHITECTURE.md b/v3/ANDROID_ARCHITECTURE.md new file mode 100644 index 000000000..d3a589488 --- /dev/null +++ b/v3/ANDROID_ARCHITECTURE.md @@ -0,0 +1,1025 @@ +# Wails v3 Android Architecture + +## Executive Summary + +This document provides a comprehensive technical architecture for Android support in Wails v3. The implementation enables Go applications to run natively on Android with an Android WebView frontend, maintaining the Wails philosophy of using web technologies for UI while leveraging Go for business logic. + +Unlike iOS which uses CGO with Objective-C, Android uses JNI (Java Native Interface) to bridge between Java/Kotlin and Go. The Go code is compiled as a shared library (`.so`) that is loaded by the Android application at runtime. + +## Table of Contents + +1. [Architecture Overview](#architecture-overview) +2. [Core Components](#core-components) +3. [Layer Architecture](#layer-architecture) +4. [File Structure](#file-structure) +5. [Implementation Details](#implementation-details) +6. [Build System](#build-system) +7. [JNI Bridge Details](#jni-bridge-details) +8. [Asset Serving](#asset-serving) +9. [JavaScript Bridge](#javascript-bridge) +10. [Security Considerations](#security-considerations) +11. [Configuration Options](#configuration-options) +12. [Debugging](#debugging) +13. [API Reference](#api-reference) +14. [Troubleshooting](#troubleshooting) +15. [Future Enhancements](#future-enhancements) + +## Architecture Overview + +### Design Principles + +1. **Battery Efficiency First**: All architectural decisions prioritize battery life +2. **No Network Ports**: Asset serving happens in-process via `WebViewAssetLoader` +3. **JNI Bridge Pattern**: Java Activity hosts WebView, Go provides business logic +4. **Wails v3 Compatibility**: Maintain API compatibility with existing Wails v3 applications +5. **Follow Fyne's gomobile pattern**: Use `-buildmode=c-shared` for native library + +### High-Level Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Android Application │ +├─────────────────────────────────────────────────────────────┤ +│ Java/Android Layer │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ MainActivity (Activity) │ │ +│ │ ┌───────────────────────────────────────────────┐ │ │ +│ │ │ Android WebView │ │ │ +│ │ │ ┌─────────────────────────────────────────┐ │ │ │ +│ │ │ │ Web Application (HTML/JS) │ │ │ │ +│ │ │ └─────────────────────────────────────────┘ │ │ │ +│ │ └───────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ │ WailsBridge WailsPathHandler WailsJSBridge│ │ +│ └─────────────────────────────────────────────────────┘ │ +├─────────────────────────────────────────────────────────────┤ +│ JNI Bridge Layer │ +│ System.loadLibrary("wails") │ +├─────────────────────────────────────────────────────────────┤ +│ Go Runtime (libwails.so) │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Wails Application │ │ +│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ +│ │ │App Logic │ │Services │ │Asset Server │ │ │ +│ │ └──────────┘ └──────────┘ └──────────────────┘ │ │ +│ └──────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Comparison with iOS Architecture + +| Aspect | iOS | Android | +|--------|-----|---------| +| Native Language | Objective-C | Java | +| Bridge Technology | CGO (C headers) | JNI | +| Build Mode | `-buildmode=c-archive` (.a) | `-buildmode=c-shared` (.so) | +| Entry Point | `main.m` calls `WailsIOSMain()` | `MainActivity` loads `libwails.so` | +| WebView | WKWebView | Android WebView | +| URL Scheme | `wails://localhost` | `https://wails.localhost` | +| Asset Interception | `WKURLSchemeHandler` | `WebViewAssetLoader` + `PathHandler` | +| JS → Native | `WKScriptMessageHandler` | `@JavascriptInterface` | +| Native → JS | `evaluateJavaScript:` | `evaluateJavascript()` | +| App Lifecycle | `UIApplicationDelegate` | `Activity` lifecycle methods | + +## Core Components + +### 1. Java Components + +#### MainActivity (`MainActivity.java`) + +**Purpose**: Android Activity that hosts the WebView and manages app lifecycle. + +**Location**: `build/android/app/src/main/java/com/wails/app/MainActivity.java` + +**Key Responsibilities**: +- Initialize the native Go library via `WailsBridge` +- Configure and manage the Android WebView +- Set up asset loading via `WebViewAssetLoader` +- Handle Android lifecycle events (onCreate, onResume, onPause, onDestroy) +- Execute JavaScript in the WebView when requested by Go + +**Key Methods**: +```java +onCreate(Bundle) // Initialize bridge, setup WebView +setupWebView() // Configure WebView settings and handlers +loadApplication() // Load initial URL (https://wails.localhost/) +executeJavaScript(String) // Run JS code (called from Go via JNI) +onResume() / onPause() // Lifecycle events forwarded to Go +onDestroy() // Cleanup resources +onBackPressed() // Handle back navigation +``` + +#### WailsBridge (`WailsBridge.java`) + +**Purpose**: Manages the JNI connection between Java and Go. + +**Location**: `build/android/app/src/main/java/com/wails/app/WailsBridge.java` + +**Key Responsibilities**: +- Load the native library (`System.loadLibrary("wails")`) +- Declare and call native methods +- Manage callbacks for async operations +- Forward lifecycle events to Go + +**Native Method Declarations**: +```java +private static native void nativeInit(WailsBridge bridge); +private static native void nativeShutdown(); +private static native void nativeOnResume(); +private static native void nativeOnPause(); +private static native byte[] nativeServeAsset(String path, String method, String headers); +private static native String nativeHandleMessage(String message); +private static native String nativeGetAssetMimeType(String path); +``` + +**Key Methods**: +```java +initialize() // Call nativeInit, set up Go runtime +shutdown() // Call nativeShutdown, cleanup +serveAsset(path, method, headers) // Get asset data from Go +handleMessage(message) // Send message to Go, get response +getAssetMimeType(path) // Get MIME type for asset +executeJavaScript(js) // Execute JS (callable from Go) +emitEvent(name, data) // Emit event to frontend +``` + +#### WailsPathHandler (`WailsPathHandler.java`) + +**Purpose**: Implements `WebViewAssetLoader.PathHandler` to serve assets from Go. + +**Location**: `build/android/app/src/main/java/com/wails/app/WailsPathHandler.java` + +**Key Responsibilities**: +- Intercept all requests to `https://wails.localhost/*` +- Forward requests to Go's asset server via `WailsBridge` +- Return `WebResourceResponse` with asset data + +**Key Method**: +```java +@Nullable +public WebResourceResponse handle(@NonNull String path) { + // Normalize path (/ -> /index.html) + // Call bridge.serveAsset(path, "GET", "{}") + // Get MIME type via bridge.getAssetMimeType(path) + // Return WebResourceResponse with data +} +``` + +#### WailsJSBridge (`WailsJSBridge.java`) + +**Purpose**: JavaScript interface exposed to the WebView for Go communication. + +**Location**: `build/android/app/src/main/java/com/wails/app/WailsJSBridge.java` + +**Key Responsibilities**: +- Expose methods to JavaScript via `@JavascriptInterface` +- Forward messages from JavaScript to Go +- Support both sync and async message patterns + +**JavaScript Interface Methods**: +```java +@JavascriptInterface +public String invoke(String message) // Sync call to Go + +@JavascriptInterface +public void invokeAsync(String callbackId, String message) // Async call + +@JavascriptInterface +public void log(String level, String message) // Log to Android logcat + +@JavascriptInterface +public String platform() // Returns "android" + +@JavascriptInterface +public boolean isDebug() // Returns BuildConfig.DEBUG +``` + +**Usage from JavaScript**: +```javascript +// Synchronous call +const result = wails.invoke(JSON.stringify({type: 'call', ...})); + +// Asynchronous call +wails.invokeAsync('callback-123', JSON.stringify({type: 'call', ...})); + +// Logging +wails.log('info', 'Hello from JavaScript'); + +// Platform detection +if (wails.platform() === 'android') { ... } +``` + +### 2. Go Components + +#### Application Layer (`application_android.go`) + +**Purpose**: Main Go implementation for Android platform. + +**Location**: `v3/pkg/application/application_android.go` + +**Build Tag**: `//go:build android` + +**Key Responsibilities**: +- Export JNI functions for Java to call +- Manage global application state +- Handle lifecycle events from Android +- Serve assets and process messages + +**JNI Exports**: +```go +//export Java_com_wails_app_WailsBridge_nativeInit +func Java_com_wails_app_WailsBridge_nativeInit(env *C.JNIEnv, obj C.jobject, bridge C.jobject) + +//export Java_com_wails_app_WailsBridge_nativeShutdown +func Java_com_wails_app_WailsBridge_nativeShutdown(env *C.JNIEnv, obj C.jobject) + +//export Java_com_wails_app_WailsBridge_nativeOnResume +func Java_com_wails_app_WailsBridge_nativeOnResume(env *C.JNIEnv, obj C.jobject) + +//export Java_com_wails_app_WailsBridge_nativeOnPause +func Java_com_wails_app_WailsBridge_nativeOnPause(env *C.JNIEnv, obj C.jobject) + +//export Java_com_wails_app_WailsBridge_nativeServeAsset +func Java_com_wails_app_WailsBridge_nativeServeAsset(env *C.JNIEnv, obj C.jobject, path, method, headers *C.char) *C.char + +//export Java_com_wails_app_WailsBridge_nativeHandleMessage +func Java_com_wails_app_WailsBridge_nativeHandleMessage(env *C.JNIEnv, obj C.jobject, message *C.char) *C.char + +//export Java_com_wails_app_WailsBridge_nativeGetAssetMimeType +func Java_com_wails_app_WailsBridge_nativeGetAssetMimeType(env *C.JNIEnv, obj C.jobject, path *C.char) *C.char +``` + +**Platform Functions**: +```go +func (a *App) platformRun() // Block forever, Android manages lifecycle +func (a *App) platformQuit() // Signal quit +func (a *App) isDarkMode() bool // Query Android dark mode +``` + +#### WebView Window (`webview_window_android.go`) + +**Purpose**: Implements `webviewWindowImpl` interface for Android. + +**Location**: `v3/pkg/application/webview_window_android.go` + +**Build Tag**: `//go:build android` + +**Key Methods**: Most methods are no-ops or return defaults since Android has a single fullscreen window. + +```go +func (w *androidWebviewWindow) execJS(js string) // Execute JavaScript +func (w *androidWebviewWindow) isFullscreen() bool // Always true +func (w *androidWebviewWindow) size() (int, int) // Device dimensions +func (w *androidWebviewWindow) setBackgroundColour(col RGBA) // Set WebView bg +``` + +#### Asset Server (`assetserver_android.go`) + +**Purpose**: Configure base URL for Android asset serving. + +**Location**: `v3/internal/assetserver/assetserver_android.go` + +**Build Tag**: `//go:build android` + +```go +var baseURL = url.URL{ + Scheme: "https", + Host: "wails.localhost", +} +``` + +#### Other Platform Files + +All these files have the `//go:build android` tag: + +| File | Purpose | +|------|---------| +| `init_android.go` | Initialization (no `runtime.LockOSThread`) | +| `clipboard_android.go` | Clipboard operations (stub) | +| `dialogs_android.go` | File/message dialogs (stub) | +| `menu_android.go` | Menu handling (no-op) | +| `menuitem_android.go` | Menu items (no-op) | +| `screen_android.go` | Screen information | +| `mainthread_android.go` | Main thread dispatch | +| `signal_handler_android.go` | Signal handling (no-op) | +| `single_instance_android.go` | Single instance (via manifest) | +| `systemtray_android.go` | System tray (no-op) | +| `keys_android.go` | Keyboard handling (stub) | +| `events_common_android.go` | Event mapping | +| `messageprocessor_android.go` | Android-specific runtime methods | + +## File Structure + +``` +v3/ +├── ANDROID_ARCHITECTURE.md # This document +├── pkg/ +│ ├── application/ +│ │ ├── application_android.go # Main Android implementation +│ │ ├── application_options.go # Contains AndroidOptions struct +│ │ ├── webview_window_android.go +│ │ ├── clipboard_android.go +│ │ ├── dialogs_android.go +│ │ ├── events_common_android.go +│ │ ├── init_android.go +│ │ ├── keys_android.go +│ │ ├── mainthread_android.go +│ │ ├── menu_android.go +│ │ ├── menuitem_android.go +│ │ ├── messageprocessor_android.go +│ │ ├── messageprocessor_mobile_stub.go # Stub for non-mobile +│ │ ├── screen_android.go +│ │ ├── signal_handler_android.go +│ │ ├── signal_handler_types_android.go +│ │ ├── single_instance_android.go +│ │ └── systemtray_android.go +│ └── events/ +│ └── events_android.go +├── internal/ +│ └── assetserver/ +│ ├── assetserver_android.go +│ └── webview/ +│ └── request_android.go +└── examples/ + └── android/ + ├── main.go # Application entry point + ├── greetservice.go # Example service + ├── go.mod + ├── go.sum + ├── Taskfile.yml # Build orchestration + ├── .gitignore + ├── frontend/ # Web frontend (same as other platforms) + │ ├── index.html + │ ├── main.js + │ ├── package.json + │ └── ... + └── build/ + ├── config.yml # Build configuration + ├── Taskfile.yml # Common build tasks + ├── android/ + │ ├── Taskfile.yml # Android-specific tasks + │ ├── build.gradle # Root Gradle build + │ ├── settings.gradle + │ ├── gradle.properties + │ ├── gradlew # Gradle wrapper script + │ ├── gradle/ + │ │ └── wrapper/ + │ │ └── gradle-wrapper.properties + │ ├── scripts/ + │ │ └── deps/ + │ │ └── install_deps.go # Dependency checker + │ └── app/ + │ ├── build.gradle # App Gradle build + │ ├── proguard-rules.pro + │ └── src/ + │ └── main/ + │ ├── AndroidManifest.xml + │ ├── java/ + │ │ └── com/ + │ │ └── wails/ + │ │ └── app/ + │ │ ├── MainActivity.java + │ │ ├── WailsBridge.java + │ │ ├── WailsPathHandler.java + │ │ └── WailsJSBridge.java + │ ├── res/ + │ │ ├── layout/ + │ │ │ └── activity_main.xml + │ │ ├── values/ + │ │ │ ├── strings.xml + │ │ │ ├── colors.xml + │ │ │ └── themes.xml + │ │ └── mipmap-*/ # App icons + │ ├── assets/ # Frontend assets (copied) + │ └── jniLibs/ + │ ├── arm64-v8a/ + │ │ └── libwails.so # Generated + │ └── x86_64/ + │ └── libwails.so # Generated + ├── darwin/ # macOS build files + ├── linux/ # Linux build files + └── windows/ # Windows build files +``` + +## Implementation Details + +### Application Startup Flow + +``` +1. Android OS launches MainActivity + │ +2. MainActivity.onCreate() + │ + ├─> WailsBridge.initialize() + │ │ + │ └─> System.loadLibrary("wails") + │ │ + │ └─> Go runtime starts + │ │ + │ └─> nativeInit() called + │ │ + │ └─> globalApp = app (store reference) + │ + ├─> setupWebView() + │ │ + │ ├─> Configure WebSettings + │ ├─> Create WebViewAssetLoader with WailsPathHandler + │ ├─> Set WebViewClient for request interception + │ └─> Add WailsJSBridge via addJavascriptInterface + │ + └─> loadApplication() + │ + └─> webView.loadUrl("https://wails.localhost/") + │ + └─> WailsPathHandler.handle("/") + │ + └─> WailsBridge.serveAsset("/index.html", ...) + │ + └─> nativeServeAsset() (JNI to Go) + │ + └─> Go AssetServer returns HTML +``` + +### Asset Request Flow + +``` +WebView requests: https://wails.localhost/main.js + │ + ▼ +WebViewClient.shouldInterceptRequest() + │ + ▼ +WebViewAssetLoader.shouldInterceptRequest() + │ + ▼ +WailsPathHandler.handle("/main.js") + │ + ▼ +WailsBridge.serveAsset("/main.js", "GET", "{}") + │ + ▼ +JNI call: nativeServeAsset(path, method, headers) + │ + ▼ +Go: serveAssetForAndroid(app, "/main.js") + │ + ▼ +Go: AssetServer reads from embed.FS + │ + ▼ +Return: byte[] data + │ + ▼ +WailsPathHandler creates WebResourceResponse + │ + ▼ +WebView renders content +``` + +### JavaScript to Go Message Flow + +``` +JavaScript: wails.invoke('{"type":"call","method":"Greet","args":["World"]}') + │ + ▼ +WailsJSBridge.invoke(message) [@JavascriptInterface] + │ + ▼ +WailsBridge.handleMessage(message) + │ + ▼ +JNI call: nativeHandleMessage(message) + │ + ▼ +Go: handleMessageForAndroid(app, message) + │ + ▼ +Go: Parse JSON, route to service method + │ + ▼ +Go: Execute GreetService.Greet("World") + │ + ▼ +Return: '{"result":"Hello, World!"}' + │ + ▼ +JavaScript receives result +``` + +### Go to JavaScript Event Flow + +``` +Go: app.Event.Emit("time", "Mon, 01 Jan 2024 12:00:00") + │ + ▼ +Go: Call Java executeJavaScript via JNI callback + │ + ▼ +WailsBridge.emitEvent("time", "\"Mon, 01 Jan 2024 12:00:00\"") + │ + ▼ +JavaScript: window.wails._emit('time', "Mon, 01 Jan 2024 12:00:00") + │ + ▼ +Frontend event listeners notified +``` + +## Build System + +### Prerequisites + +1. **Go 1.21+** with CGO support +2. **Android SDK** with: + - Platform Tools (adb) + - Build Tools + - Android Emulator +3. **Android NDK r19c+** (r26d recommended) +4. **Java JDK 11+** + +### Environment Variables + +```bash +# Required +export ANDROID_HOME=$HOME/Library/Android/sdk # macOS +export ANDROID_HOME=$HOME/Android/Sdk # Linux + +# Optional (auto-detected if not set) +export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/26.1.10909125 + +# Path additions +export PATH=$PATH:$ANDROID_HOME/platform-tools +export PATH=$PATH:$ANDROID_HOME/emulator +``` + +### Taskfile Commands + +```bash +# Check/install dependencies +task android:install:deps + +# Build Go shared library (default: arm64 for device) +task android:build + +# Build for emulator (x86_64) +task android:build ARCH=x86_64 + +# Build for all architectures (fat APK) +task android:compile:go:all-archs + +# Package into APK +task android:package + +# Run on emulator +task android:run + +# View logs +task android:logs + +# Clean build artifacts +task android:clean +``` + +### Build Process Details + +#### 1. Go Compilation + +```bash +# Environment for arm64 (device) +export GOOS=android +export GOARCH=arm64 +export CGO_ENABLED=1 +export CC=$NDK/toolchains/llvm/prebuilt/$HOST/bin/aarch64-linux-android21-clang + +# Build command +go build -buildmode=c-shared \ + -tags android \ + -o build/android/app/src/main/jniLibs/arm64-v8a/libwails.so +``` + +#### 2. Gradle Build + +```bash +cd build/android +./gradlew assembleDebug +# Output: app/build/outputs/apk/debug/app-debug.apk +``` + +#### 3. Installation + +```bash +adb install app-debug.apk +adb shell am start -n com.wails.app/.MainActivity +``` + +### Architecture Support + +| Architecture | GOARCH | JNI Directory | Use Case | +|--------------|--------|---------------|----------| +| arm64-v8a | arm64 | `jniLibs/arm64-v8a/` | Physical devices (most common) | +| x86_64 | amd64 | `jniLibs/x86_64/` | Emulator | +| armeabi-v7a | arm | `jniLibs/armeabi-v7a/` | Older devices (optional) | +| x86 | 386 | `jniLibs/x86/` | Older emulators (optional) | + +### Minimum SDK Configuration + +```gradle +// build/android/app/build.gradle +android { + defaultConfig { + minSdk 21 // Android 5.0 (Lollipop) - 99%+ coverage + targetSdk 34 // Android 14 - Required for Play Store + } +} +``` + +## JNI Bridge Details + +### JNI Function Naming Convention + +JNI functions must follow this naming pattern: +``` +Java___ +``` + +Example: +```go +//export Java_com_wails_app_WailsBridge_nativeInit +func Java_com_wails_app_WailsBridge_nativeInit(env *C.JNIEnv, obj C.jobject, bridge C.jobject) +``` + +Corresponds to Java: +```java +package com.wails.app; +class WailsBridge { + private static native void nativeInit(WailsBridge bridge); +} +``` + +### JNI Type Mappings + +| Java Type | JNI Type | Go CGO Type | +|-----------|----------|-------------| +| void | void | - | +| boolean | jboolean | C.jboolean | +| int | jint | C.jint | +| long | jlong | C.jlong | +| String | jstring | *C.char (via conversion) | +| byte[] | jbyteArray | *C.char (via conversion) | +| Object | jobject | C.jobject | + +### String Conversion + +```go +// Java String → Go string +goString := C.GoString((*C.char)(unsafe.Pointer(javaString))) + +// Go string → Java String (return) +return C.CString(goString) // Must be freed by Java +``` + +### Thread Safety + +- JNI calls must be made from the thread that owns the JNI environment +- Go goroutines cannot directly call JNI methods +- Use channels or callbacks to communicate between goroutines and JNI thread + +## Asset Serving + +### WebViewAssetLoader Configuration + +```java +assetLoader = new WebViewAssetLoader.Builder() + .setDomain("wails.localhost") // Custom domain + .addPathHandler("/", new WailsPathHandler(bridge)) // All paths + .build(); +``` + +### URL Scheme + +- **Base URL**: `https://wails.localhost/` +- **Why HTTPS**: Android's `WebViewAssetLoader` requires HTTPS for security +- **Domain**: `wails.localhost` is arbitrary but consistent with Wails conventions + +### Path Normalization + +```java +// In WailsPathHandler.handle() +if (path.isEmpty() || path.equals("/")) { + path = "/index.html"; +} +``` + +### MIME Type Detection + +MIME types are determined by Go based on file extension. Fallback mapping in Java: + +```java +private String getMimeType(String path) { + if (path.endsWith(".html")) return "text/html"; + if (path.endsWith(".js")) return "application/javascript"; + if (path.endsWith(".css")) return "text/css"; + // ... etc + return "application/octet-stream"; +} +``` + +## JavaScript Bridge + +### Exposed Interface + +The `WailsJSBridge` is added to the WebView as: +```java +webView.addJavascriptInterface(new WailsJSBridge(bridge, webView), "wails"); +``` + +This makes `window.wails` available in JavaScript. + +### Security Considerations + +1. **@JavascriptInterface annotation** is required for all exposed methods (Android 4.2+) +2. Only specific methods are exposed, not the entire object +3. Input validation should be performed on all received data + +### Async Pattern + +For non-blocking calls: + +```javascript +// JavaScript side +const callbackId = 'cb_' + Date.now(); +window.wails._callbacks[callbackId] = (result, error) => { + if (error) reject(error); + else resolve(result); +}; +wails.invokeAsync(callbackId, message); + +// Java side sends response via: +webView.evaluateJavascript( + "window.wails._callback('" + callbackId + "', " + result + ", null);", + null +); +``` + +## Security Considerations + +### WebView Security + +```java +WebSettings settings = webView.getSettings(); +settings.setAllowFileAccess(false); // No file:// access +settings.setAllowContentAccess(false); // No content:// access +settings.setMixedContentMode(MIXED_CONTENT_NEVER_ALLOW); // HTTPS only +``` + +### JNI Security + +1. **No arbitrary code execution**: JNI methods have fixed signatures +2. **Input validation**: All strings from Java are validated in Go +3. **Memory safety**: Go's memory management prevents buffer overflows + +### Asset Security + +1. **Same-origin policy**: Assets only served from `wails.localhost` +2. **No external network**: All assets embedded, no remote fetching +3. **Content Security Policy**: Can be set via HTML headers + +## Configuration Options + +### AndroidOptions Struct + +```go +type AndroidOptions struct { + // DisableScroll disables scrolling in the WebView + DisableScroll bool + + // DisableOverscroll disables the overscroll bounce effect + DisableOverscroll bool + + // EnableZoom allows pinch-to-zoom in the WebView (default: false) + EnableZoom bool + + // UserAgent sets a custom user agent string + UserAgent string + + // BackgroundColour sets the background colour of the WebView + BackgroundColour RGBA + + // DisableHardwareAcceleration disables hardware acceleration + DisableHardwareAcceleration bool +} +``` + +### Usage + +```go +app := application.New(application.Options{ + Name: "My App", + Android: application.AndroidOptions{ + DisableOverscroll: true, + BackgroundColour: application.NewRGB(27, 38, 54), + }, +}) +``` + +### AndroidManifest.xml Configuration + +```xml + + + + + android:hardwareAccelerated="true"> + + + + + +``` + +## Debugging + +### Logcat Filtering + +```bash +# All Wails logs +adb logcat -v time | grep -E "(Wails|WailsBridge|WailsActivity)" + +# Using task +task android:logs +``` + +### WebView Debugging + +Enable in debug builds: +```java +if (BuildConfig.DEBUG) { + WebView.setWebContentsDebuggingEnabled(true); +} +``` + +Then in Chrome: `chrome://inspect/#devices` + +### Go Debugging + +```go +func androidLogf(level string, format string, a ...interface{}) { + msg := fmt.Sprintf(format, a...) + println(fmt.Sprintf("[Android/%s] %s", level, msg)) +} +``` + +### Common Issues + +1. **"UnsatisfiedLinkError"**: Library not found or wrong architecture +2. **"No implementation found"**: JNI function name mismatch +3. **Blank WebView**: Asset serving not working, check logcat + +## API Reference + +### Go API (Same as Desktop) + +```go +// Create application +app := application.New(application.Options{ + Name: "App Name", + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Services: []application.Service{ + application.NewService(&MyService{}), + }, + Android: application.AndroidOptions{...}, +}) + +// Run (blocks on Android) +app.Run() + +// Emit events +app.Event.Emit("eventName", data) +``` + +### JavaScript API + +```javascript +// Call Go service method +const result = await window.wails.Call.ByName('MyService.Greet', 'World'); + +// Platform detection +if (window.wails.System.Platform() === 'android') { ... } + +// Events +window.wails.Events.On('eventName', (data) => { ... }); +``` + +### Android-Specific Runtime Methods + +```javascript +// Vibrate (haptic feedback) +window.wails.Call.ByName('Android.Haptics.Vibrate', {duration: 100}); + +// Show toast +window.wails.Call.ByName('Android.Toast.Show', {message: 'Hello!'}); + +// Get device info +const info = await window.wails.Call.ByName('Android.Device.Info'); +``` + +## Troubleshooting + +### Build Errors + +**"NDK not found"** +```bash +# Set NDK path explicitly +export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/26.1.10909125 +``` + +**"undefined reference to JNI function"** +- Check function name matches exactly (case-sensitive) +- Ensure `//export` comment is directly above function + +**"cannot find package"** +```bash +cd examples/android && go mod tidy +``` + +### Runtime Errors + +**App crashes on startup** +1. Check logcat for stack trace +2. Verify library is in correct jniLibs directory +3. Check architecture matches device/emulator + +**WebView shows blank** +1. Enable WebView debugging +2. Check Chrome DevTools for errors +3. Verify `https://wails.localhost/` resolves + +**JavaScript bridge not working** +1. Check `wails` object exists: `console.log(window.wails)` +2. Verify `@JavascriptInterface` annotations present +3. Check for JavaScript errors in console + +## Future Enhancements + +### Phase 1: Core Stability +- [ ] Complete JNI callback implementation for Go → Java +- [ ] Full asset server integration +- [ ] Error handling and recovery +- [ ] Unit and integration tests + +### Phase 2: Feature Parity +- [ ] Clipboard support +- [ ] File dialogs (via Storage Access Framework) +- [ ] Notifications +- [ ] Deep linking + +### Phase 3: Android-Specific Features +- [ ] Material Design 3 theming integration +- [ ] Edge-to-edge display support +- [ ] Predictive back gesture +- [ ] Picture-in-Picture mode +- [ ] Widgets + +### Phase 4: Advanced Features +- [ ] Background services +- [ ] Push notifications (FCM) +- [ ] Biometric authentication +- [ ] App Shortcuts +- [ ] Wear OS companion + +## Conclusion + +This architecture provides a solid foundation for Android support in Wails v3. The design prioritizes: + +1. **Compatibility**: Same Go code runs on all platforms +2. **Performance**: No network overhead, native rendering +3. **Security**: Sandboxed WebView, validated inputs +4. **Maintainability**: Clear separation of concerns + +The implementation follows Android best practices while maintaining the simplicity that Wails developers expect. The JNI bridge pattern, while more complex than iOS's CGO approach, provides robust interoperability between Java and Go. + +### Key Implementation Status + +| Component | Status | Notes | +|-----------|--------|-------| +| Java Activity | ✅ Complete | MainActivity with WebView | +| JNI Bridge | ✅ Complete | WailsBridge with native methods | +| Asset Handler | ✅ Complete | WailsPathHandler | +| JS Bridge | ✅ Complete | WailsJSBridge | +| Go Platform Files | ✅ Complete | All *_android.go files | +| Taskfile | ✅ Complete | Build orchestration | +| Gradle Project | ✅ Complete | App structure | +| JNI Implementation | 🔄 Partial | Needs Go → Java callbacks | +| Asset Server Integration | 🔄 Partial | Needs full wiring | +| Testing | ❌ Pending | Needs emulator testing | + +--- + +*Document Version: 1.0* +*Last Updated: November 2024* +*Wails Version: v3-alpha* diff --git a/v3/IOS_ARCHITECTURE.md b/v3/IOS_ARCHITECTURE.md new file mode 100644 index 000000000..2e07f4f0c --- /dev/null +++ b/v3/IOS_ARCHITECTURE.md @@ -0,0 +1,419 @@ +# Wails v3 iOS Architecture + +## Executive Summary + +This document provides a comprehensive technical architecture for iOS support in Wails v3. The implementation enables Go applications to run natively on iOS with a WKWebView frontend, maintaining the Wails philosophy of using web technologies for UI while leveraging Go for business logic. + +## Table of Contents + +1. [Architecture Overview](#architecture-overview) +2. [Core Components](#core-components) +3. [Layer Architecture](#layer-architecture) +4. [Implementation Details](#implementation-details) +5. [Battery Optimization](#battery-optimization) +6. [Build System](#build-system) +7. [Security Considerations](#security-considerations) +8. [API Reference](#api-reference) + +## Architecture Overview + +### Design Principles + +1. **Battery Efficiency First**: All architectural decisions prioritize battery life +2. **No Network Ports**: Asset serving happens in-process via native APIs +3. **Minimal WebView Instances**: Maximum 2 concurrent WebViews (1 primary, 1 for transitions) +4. **Native Integration**: Deep iOS integration using Objective-C runtime +5. **Wails v3 Compatibility**: Maintain API compatibility with existing Wails v3 applications + +### High-Level Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ iOS Application │ +├─────────────────────────────────────────────────────────────┤ +│ UIKit Framework │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ WailsViewController │ │ +│ │ ┌───────────────────────────────────────────────┐ │ │ +│ │ │ WKWebView Instance │ │ │ +│ │ │ ┌─────────────────────────────────────────┐ │ │ │ +│ │ │ │ Web Application (HTML/JS) │ │ │ │ +│ │ │ └─────────────────────────────────────────┘ │ │ │ +│ │ └───────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────┘ │ +├─────────────────────────────────────────────────────────────┤ +│ Bridge Layer (CGO) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │URL Handler │ │JS Bridge │ │Message Handler│ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +├─────────────────────────────────────────────────────────────┤ +│ Go Runtime │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Wails Application │ │ +│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ +│ │ │App Logic │ │Services │ │Asset Server │ │ │ +│ │ └──────────┘ └──────────┘ └──────────────────┘ │ │ +│ └──────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Core Components + +### 1. Platform Layer (`application_ios.go`) + +**Purpose**: Go interface for iOS platform operations + +**Key Functions**: +- `platformRun()`: Initialize and run the iOS application +- `platformQuit()`: Gracefully shutdown the application +- `isDarkMode()`: Detect iOS dark mode state +- `ExecuteJavaScript(windowID uint, js string)`: Execute JS in WebView + +**Exported Go Functions (Called from Objective-C)**: +- `ServeAssetRequest(windowID C.uint, urlStr *C.char, callbackID C.uint)` +- `HandleJSMessage(windowID C.uint, message *C.char)` + +### 2. Native iOS Layer (`application_ios.m`) + +**Components**: + +#### WailsSchemeHandler +```objc +@interface WailsSchemeHandler : NSObject +``` +- Implements `WKURLSchemeHandler` protocol +- Intercepts `wails://` URL requests +- Bridges to Go for asset serving +- Manages pending requests with callback IDs + +**Methods**: +- `startURLSchemeTask:`: Intercept request, call Go handler +- `stopURLSchemeTask:`: Cancel pending request +- `completeRequest:withData:mimeType:`: Complete request with data from Go + +#### WailsMessageHandler +```objc +@interface WailsMessageHandler : NSObject +``` +- Implements JavaScript to Go communication +- Handles `window.webkit.messageHandlers.external.postMessage()` +- Serializes messages to JSON for Go processing + +**Methods**: +- `userContentController:didReceiveScriptMessage:`: Process JS messages + +#### WailsViewController +```objc +@interface WailsViewController : UIViewController +``` +- Main view controller containing WKWebView +- Manages WebView lifecycle +- Handles JavaScript execution requests + +**Properties**: +- `webView`: WKWebView instance +- `schemeHandler`: Custom URL scheme handler +- `messageHandler`: JS message handler +- `windowID`: Unique window identifier + +**Methods**: +- `viewDidLoad`: Initialize WebView with configuration +- `executeJavaScript:`: Run JS code in WebView + +### 3. Bridge Layer (CGO) + +**C Interface Functions**: +```c +void ios_app_init(void); // Initialize iOS app +void ios_app_run(void); // Run main loop +void ios_app_quit(void); // Quit application +bool ios_is_dark_mode(void); // Check dark mode +unsigned int ios_create_webview(void); // Create WebView +void ios_execute_javascript(unsigned int windowID, const char* js); +void ios_complete_request(unsigned int callbackID, const char* data, const char* mimeType); +``` + +## Layer Architecture + +### Layer 1: Presentation Layer (WebView) + +**Responsibilities**: +- Render HTML/CSS/JavaScript UI +- Handle user interactions +- Communicate with native layer + +**Key Features**: +- WKWebView for modern web standards +- Hardware-accelerated rendering +- Efficient memory management + +### Layer 2: Communication Layer + +**Request Interception**: +``` +WebView Request → WKURLSchemeHandler → Go ServeAssetRequest → AssetServer → Response +``` + +**JavaScript Bridge**: +``` +JS postMessage → WKScriptMessageHandler → Go HandleJSMessage → Process → ExecuteJavaScript +``` + +### Layer 3: Application Layer (Go) + +**Components**: +- Application lifecycle management +- Service binding and method calls +- Asset serving from embedded fs.FS +- Business logic execution + +### Layer 4: Platform Integration Layer + +**iOS-Specific Features**: +- Dark mode detection +- System appearance integration +- iOS-specific optimizations + +## Implementation Details + +### Request Handling Flow + +1. **WebView makes request** to `wails://localhost/path` +2. **WKURLSchemeHandler intercepts** request +3. **Creates callback ID** and stores `WKURLSchemeTask` +4. **Calls Go function** `ServeAssetRequest` with URL and callback ID +5. **Go processes request** through AssetServer +6. **Go calls** `ios_complete_request` with response data +7. **Objective-C completes** the `WKURLSchemeTask` with response + +### JavaScript Execution Flow + +1. **Go calls** `ios_execute_javascript` with JS code +2. **Bridge dispatches** to main thread +3. **WKWebView evaluates** JavaScript +4. **Completion handler** logs any errors + +### Message Passing Flow + +1. **JavaScript calls** `window.webkit.messageHandlers.wails.postMessage(data)` +2. **WKScriptMessageHandler receives** message +3. **Serializes to JSON** and passes to Go +4. **Go processes** message in `HandleJSMessage` +5. **Go can respond** via `ExecuteJavaScript` + +## Battery Optimization + +### WebView Configuration + +```objc +// Disable unnecessary features +config.suppressesIncrementalRendering = NO; +config.allowsInlineMediaPlayback = YES; +config.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone; +``` + +### Memory Management + +1. **Single WebView Instance**: Reuse instead of creating new instances +2. **Automatic Reference Counting**: Use ARC for Objective-C objects +3. **Lazy Loading**: Initialize components only when needed +4. **Resource Cleanup**: Properly release resources when done + +### Request Optimization + +1. **In-Process Serving**: No network overhead +2. **Direct Memory Transfer**: Pass data directly without serialization +3. **Efficient Caching**: Leverage WKWebView's built-in cache +4. **Minimal Wake Locks**: No background network activity + +## Build System + +### Build Tags + +```go +//go:build ios +``` + +### CGO Configuration + +```go +#cgo CFLAGS: -x objective-c -fobjc-arc +#cgo LDFLAGS: -framework Foundation -framework UIKit -framework WebKit +``` + +### Build Script (`build_ios.sh`) + +**Steps**: +1. Check dependencies (go, xcodebuild, xcrun) +2. Set up iOS cross-compilation environment +3. Build Go binary with iOS tags +4. Create app bundle structure +5. Generate Info.plist +6. Sign for simulator +7. Create launch script + +**Environment Variables**: +```bash +export CGO_ENABLED=1 +export GOOS=ios +export GOARCH=arm64 +export SDK_PATH=$(xcrun --sdk iphonesimulator --show-sdk-path) +``` + +### Simulator Deployment + +```bash +xcrun simctl install "$DEVICE_ID" "WailsIOSDemo.app" +xcrun simctl launch "$DEVICE_ID" "com.wails.iosdemo" +``` + +## Security Considerations + +### URL Scheme Security + +1. **Custom Scheme**: Use `wails://` to avoid conflicts +2. **Origin Validation**: Only serve to authorized WebViews +3. **No External Access**: Scheme handler only responds to app's WebView + +### JavaScript Execution + +1. **Input Validation**: Sanitize JS code before execution +2. **Sandboxed Execution**: WKWebView provides isolation +3. **No eval()**: Avoid dynamic code evaluation + +### Data Protection + +1. **In-Memory Only**: No temporary files on disk +2. **ATS Compliance**: App Transport Security enabled +3. **Secure Communication**: All data stays within app process + +## API Reference + +### Go API + +#### Application Functions + +```go +// Create new iOS application +app := application.New(application.Options{ + Name: "App Name", + Description: "App Description", +}) + +// Run the application +app.Run() + +// Execute JavaScript +app.ExecuteJavaScript(windowID, "console.log('Hello')") +``` + +#### Service Binding + +```go +type MyService struct{} + +func (s *MyService) Greet(name string) string { + return fmt.Sprintf("Hello, %s!", name) +} + +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&MyService{}), + }, +}) +``` + +### JavaScript API + +#### Send Message to Go + +```javascript +window.webkit.messageHandlers.wails.postMessage({ + type: 'methodCall', + service: 'MyService', + method: 'Greet', + args: ['World'] +}); +``` + +#### Receive from Go + +```javascript +window.wailsCallback = function(data) { + console.log('Received:', data); +}; +``` + +### Objective-C Bridge API + +#### From Go to Objective-C + +```c +// Execute JavaScript +ios_execute_javascript(windowID, "alert('Hello')"); + +// Complete asset request +ios_complete_request(callbackID, htmlData, "text/html"); +``` + +#### From Objective-C to Go + +```c +// Serve asset request +ServeAssetRequest(windowID, urlString, callbackID); + +// Handle JavaScript message +HandleJSMessage(windowID, jsonMessage); +``` + +## Performance Metrics + +### Target Metrics + +- **WebView Creation**: < 100ms +- **Asset Request**: < 10ms for cached, < 50ms for first load +- **JS Execution**: < 5ms for simple scripts +- **Message Passing**: < 2ms round trip +- **Memory Usage**: < 50MB baseline +- **Battery Impact**: < 2% per hour active use + +### Monitoring + +1. **Xcode Instruments**: CPU, Memory, Energy profiling +2. **WebView Inspector**: JavaScript performance +3. **Go Profiling**: pprof for Go code analysis + +## Future Enhancements + +### Phase 1: Core Stability +- [ ] Production-ready error handling +- [ ] Comprehensive test suite +- [ ] Performance optimization + +### Phase 2: Feature Parity +- [ ] Multiple window support +- [ ] System tray integration +- [ ] Native menu implementation + +### Phase 3: iOS-Specific Features +- [ ] Widget extension support +- [ ] App Clip support +- [ ] ShareSheet integration +- [ ] Siri Shortcuts + +### Phase 4: Advanced Features +- [ ] Background task support +- [ ] Push notifications +- [ ] CloudKit integration +- [ ] Apple Watch companion app + +## Conclusion + +This architecture provides a solid foundation for iOS support in Wails v3. The design prioritizes battery efficiency, native performance, and seamless integration with the existing Wails ecosystem. The proof of concept demonstrates all four required capabilities: + +1. ✅ **WebView Creation**: Native WKWebView with optimized configuration +2. ✅ **Request Interception**: Custom scheme handler without network ports +3. ✅ **JavaScript Execution**: Bidirectional communication bridge +4. ✅ **iOS Simulator Support**: Complete build and deployment pipeline + +The architecture is designed to scale from this proof of concept to a full production implementation while maintaining the simplicity and elegance that Wails developers expect. \ No newline at end of file diff --git a/v3/IOS_FEATURES_TODO.md b/v3/IOS_FEATURES_TODO.md new file mode 100644 index 000000000..cd3bc0edd --- /dev/null +++ b/v3/IOS_FEATURES_TODO.md @@ -0,0 +1,100 @@ +# iOS Features TODO (Prioritized) + +This document lists potential iOS features and platform options to enhance the Wails v3 iOS runtime. Items are ordered by importance for typical app development workflows. + +## Top Priority (Implement first) + +1) Input accessory bar control +- Status: Implemented as `IOSOptions.DisableInputAccessoryView` (default false = shown). Native toggle + WKWebView subclass. + +2) Scrolling and bounce behavior +- Options: + - `DisableScroll` (default true in current runtime to preserve no-scroll template behavior) + - `DisableBounce` (default true in current runtime) + - `HideScrollIndicators` (default true in current runtime) +- Purpose: control elastic bounce, page scrolling, and indicators. + +3) Web Inspector / Debug +- Options: + - `DisableInspectable` (default false; inspector enabled by default in dev) +- Purpose: enable/disable WKWebView inspector. + +4) Back/forward navigation gestures +- Options: + - `AllowsBackForwardNavigationGestures` (default false) +- Purpose: enable iOS edge-swipe navigation. + +5) Link previews +- Options: + - `DisableLinkPreview` (default false) +- Purpose: allow long-press link previews. + +6) Media autoplay and inline playback +- Options: + - `DisableInlineMediaPlayback` (default false) + - `RequireUserActionForMediaPlayback` (default false) +- Purpose: control media playback UX. + +7) User agent customization +- Options: + - `UserAgent` (string) + - `ApplicationNameForUserAgent` (string; default "wails.io") +- Purpose: customize UA / identify app. + +8) Keyboard behavior +- Options: + - Already: `DisableInputAccessoryView` + - Future: `KeyboardDismissMode` (none | onDrag | interactive) +- Purpose: refine keyboard UX. + +9) Safe-area and content inset behavior +- Options (future): + - `ContentInsetAdjustment` (automatic | never | always) + - `UseSafeArea` (bool) +- Purpose: fine-tune layout under notch/home indicator. + +10) Data detectors (future feasibility) +- Options: `DataDetectorTypes []string` (phoneNumber, link, address) +- Note: Not all are directly available on WKWebView; feasibility TBD. + +## Medium Priority + +11) Pull-to-refresh (custom) +12) File picker / photo access bridges +13) Haptics feedback helpers +14) Clipboard read/write helpers (partially present) +15) Share sheet / activity view bridges +16) Background audio / PiP controls +17) App lifecycle event hooks (background/foreground) +18) Permissions prompts helpers (camera, mic, photos) +19) Open in external browser vs in-app policy +20) Cookie / storage policy helpers + +## Low Priority + +21) Theme/dynamic color helpers bridging to CSS vars +22) Orientation lock helpers per window +23) Status bar style control from Go +24) Network reachability events bridge +25) Push notifications + +--- + +# Implementation Plan (Top 10) + +Implement the following immediately: +- DisableScroll, DisableBounce, HideScrollIndicators +- AllowsBackForwardNavigationGestures +- DisableLinkPreview +- DisableInlineMediaPlayback +- RequireUserActionForMediaPlayback +- DisableInspectable +- UserAgent +- ApplicationNameForUserAgent + +Approach: +- Extend `IOSOptions` in `pkg/application/application_options.go` with these fields. +- Add native globals + C setters in `pkg/application/application_ios.h/.m`. +- Apply options in `pkg/application/webview_window_ios.m` during WKWebView configuration and on the scrollView. +- Wire from Go in `pkg/application/application_ios.go`. +- Maintain current template behavior as defaults (no scroll/bounce/indicators) to avoid regressions in existing tests. diff --git a/v3/IOS_RUNTIME.md b/v3/IOS_RUNTIME.md new file mode 100644 index 000000000..22aaa320a --- /dev/null +++ b/v3/IOS_RUNTIME.md @@ -0,0 +1,53 @@ +# iOS Runtime Feature Plan + +This document outlines proposed iOS-only runtime features for Wails v3, the initial milestones, and method shapes exposed to the frontend runtime as `IOS.*`. + +## Goals +- Provide a first-class iOS runtime namespace: `IOS`. +- Expose UX-critical features with a small, well-defined, promise-based API. +- Follow the existing runtime pattern: JS -> /wails/runtime -> Go -> ObjC. + +## Object: IOS +- Object ID: 11 (reserved in runtime objectNames) + +## Milestone 1 (MVP) +- Haptics + - `IOS.Haptics.Impact(style: "light"|"medium"|"heavy"|"soft"|"rigid"): Promise` +- Device + - `IOS.Device.Info(): Promise<{ model: string; systemName: string; systemVersion: string; isSimulator: boolean }>` + +## Milestone 2 +- Permissions + - `IOS.Permissions.Request("camera"|"microphone"|"photos"|"notifications"): Promise<"granted"|"denied"|"limited">` + - `IOS.Permissions.Status(kind): Promise<"granted"|"denied"|"limited"|"restricted"|"not_determined">` +- Camera + - `IOS.Camera.PickPhoto(options?): Promise<{ uri: string }>` + - `IOS.Camera.PickVideo(options?): Promise<{ uri: string, duration?: number }>` +- Photos + - `IOS.Photos.SaveImage(dataURL|blob, options?): Promise` + - `IOS.Photos.SaveVideo(fileURI, options?): Promise` + +## Milestone 3 +- Share + - `IOS.Share.Sheet({ text?, url?, imageDataURL? }): Promise` +- Files + - `IOS.Files.Pick({ types?, multiple? }): Promise>` +- Biometric + - `IOS.Biometric.CanAuthenticate(): Promise` + - `IOS.Biometric.Authenticate(reason: string): Promise` +- Notifications + - `IOS.Notifications.RequestPermission(): Promise` + - `IOS.Notifications.Schedule(localNotification): Promise` + +## Notes +- All APIs should be safe no-ops on other platforms (reject with a meaningful error) or be tree-shaken by frontend bundlers. +- UI-affecting APIs must ensure main-thread execution in ObjC. +- File/Photo APIs will use security-scoped bookmarks where relevant. + +## Implementation Status +- [x] Define plan (this document) +- [ ] JS runtime: add IOS object ID + IOS module exports +- [ ] Go: message dispatcher for IOS object +- [ ] iOS: Haptics.Impact(style) native bridge +- [ ] JS->Go->ObjC wiring for Haptics +- [ ] Device.Info() basic implementation diff --git a/v3/README.md b/v3/README.md new file mode 100644 index 000000000..2d0c36a0b --- /dev/null +++ b/v3/README.md @@ -0,0 +1,9 @@ +# v3 Alpha + +Thanks for wanting to help out with testing/developing Wails v3! This guide will help you get started. + +## Getting Started + +All the instructions for getting started are in the v3 documentation directory: `mkdocs-website`. +Please read the README.md file in that directory for more information. + diff --git a/v3/TESTING.md b/v3/TESTING.md new file mode 100644 index 000000000..2e1486dc5 --- /dev/null +++ b/v3/TESTING.md @@ -0,0 +1,452 @@ +# Cross-Platform Testing Guide for Wails v3 + +This document describes the comprehensive cross-platform testing system for Wails v3 examples, supporting Mac, Linux, and Windows compilation. + +## Overview + +The testing system ensures all Wails v3 examples build successfully across all supported platforms: +- **macOS (Darwin)** - Native compilation +- **Windows** - Cross-compilation from any platform +- **Linux** - Multi-architecture Docker compilation (ARM64 + x86_64) + +## Test Directory Structure + +The testing infrastructure is organized in a dedicated test directory: + +```bash +v3/ +├── test/ +│ └── docker/ +│ ├── Dockerfile.linux-arm64 # ARM64 native compilation +│ └── Dockerfile.linux-x86_64 # x86_64 native compilation +├── Taskfile.yaml # Build task definitions +└── TESTING.md # This documentation +``` + +**Benefits of the organized structure:** +- **Separation of Concerns**: Testing files are isolated from application code +- **Clear Organization**: All Docker-related files in one location +- **Easier Maintenance**: Centralized testing infrastructure +- **Better Git Management**: Clean separation for .gitignore patterns + +## Available Commands + +### 🚀 Complete Cross-Platform Testing +```bash +# Build all examples for ALL platforms (macOS + Windows + Linux) +task test:examples:all +``` +**Total: 129 builds** (43 examples × 3 platforms) + CLI code testing + +### All Examples (No DIR Parameter Needed) +```bash +# Current platform only (all 43 examples + CLI code) +task test:examples + +# All examples for specific Linux architectures +task test:examples:linux:docker # Auto-detect architecture +task test:examples:linux:docker:arm64 # ARM64 native +task test:examples:linux:docker:x86_64 # x86_64 native + +# CLI code testing only +task test:cli +``` + +### Single Example Builds (Requires DIR=example) +```bash +# macOS/Darwin single example +task test:example:darwin DIR=badge + +# Windows cross-compilation single example +task test:example:windows DIR=badge + +# Linux native builds (on Linux systems) +task test:example:linux DIR=badge + +# Linux Docker builds (multi-architecture) +task test:example:linux:docker DIR=badge # Auto-detect architecture +task test:example:linux:docker:arm64 DIR=badge # ARM64 native +task test:example:linux:docker:x86_64 DIR=badge # x86_64 native +``` + +## Build Artifacts + +All builds generate platform-specific binaries with clear naming: +- **macOS**: `testbuild-{example}-darwin` +- **Windows**: `testbuild-{example}-windows.exe` +- **Linux**: `testbuild-{example}-linux` +- **Linux ARM64**: `testbuild-{example}-linux-arm64` (Docker) +- **Linux x86_64**: `testbuild-{example}-linux-x86_64` (Docker) + +Example outputs: +```text +examples/badge/testbuild-badge-darwin +examples/badge/testbuild-badge-windows.exe +examples/badge/testbuild-badge-linux-arm64 +examples/badge/testbuild-badge-linux-x86_64 +``` + +## Validation Status + +### ✅ **Production Ready (v3.0.0-alpha)** +- **Total Examples**: 43 examples fully tested +- **macOS**: ✅ All examples compile successfully (100%) +- **Windows**: ✅ All examples cross-compile successfully (100%) +- **Linux**: ✅ Multi-architecture Docker compilation (ARM64 + x86_64) +- **Build System**: Comprehensive Taskfile.yaml integration +- **Git Integration**: Complete .gitignore patterns for build artifacts +- **Total Build Capacity**: 129 cross-platform builds per test cycle + +## Supported Examples + +The system builds all 43 Wails v3 examples: +- badge, badge-custom, binding, build +- cancel-async, cancel-chaining, clipboard, contextmenus +- dev, dialogs, dialogs-basic, drag-n-drop +- environment, events, events-bug, file-association +- frameless, gin-example, gin-routing, gin-service +- hide-window, html-dnd-api, ignore-mouse, keybindings +- menu, notifications, panic-handling, plain +- raw-message, screen, services, show-macos-toolbar +- single-instance, systray-basic, systray-custom, systray-menu +- video, window, window-api, window-call +- window-menu, wml + +**Recently Added (v3.0.0-alpha):** +- dev, events-bug, gin-example, gin-routing, gin-service +- html-dnd-api, notifications + +## Platform Requirements + +### macOS (Darwin) +- Go 1.23+ +- Xcode Command Line Tools +- No additional dependencies required + +**Environment Variables:** +```bash +CGO_LDFLAGS="-framework UniformTypeIdentifiers -mmacosx-version-min=10.13" +CGO_CFLAGS="-mmacosx-version-min=10.13" +``` + +### Windows (Cross-compilation) +- Go 1.23+ +- No additional dependencies for cross-compilation + +**Environment Variables:** +```bash +GOOS=windows +GOARCH=amd64 +``` + +### Linux (Docker) - ✅ Multi-Architecture Support +Uses Ubuntu 24.04 base image with full GTK development environment: + +**Current Status:** Complete multi-architecture Docker compilation system +- ✅ ARM64 native compilation (Ubuntu 24.04) +- ✅ x86_64 native compilation (Ubuntu 24.04) +- ✅ Automatic architecture detection +- ✅ All dependencies install correctly (GTK + WebKit) +- ✅ Go 1.24 environment configured for each architecture +- ✅ Native compilation eliminates cross-compilation CGO issues + +**Architecture Support:** +- **ARM64**: Native compilation using `Dockerfile.linux-arm64` +- **x86_64**: Native compilation using `Dockerfile.linux-x86_64` with `--platform=linux/amd64` +- **Auto-detect**: Taskfile automatically selects appropriate architecture + +**Core Dependencies:** +- `build-essential` - GCC compiler toolchain (architecture-specific) +- `pkg-config` - Package configuration tool +- `libgtk-3-dev` - GTK+ 3.x development files +- `libwebkit2gtk-4.1-dev` - WebKit2GTK development files +- `git` - Version control (for go mod operations) +- `ca-certificates` - HTTPS support + +**Docker Images:** +- `wails-v3-linux-arm64` - Ubuntu 24.04 ARM64 native compilation (built from `test/docker/Dockerfile.linux-arm64`) +- `wails-v3-linux-x86_64` - Ubuntu 24.04 x86_64 native compilation (built from `test/docker/Dockerfile.linux-x86_64`) +- `wails-v3-linux-fixed` - Legacy unified image (deprecated) + +## Docker Configuration + +### Multi-Architecture Build System + +#### ARM64 Native Build Environment (`test/docker/Dockerfile.linux-arm64`) +```dockerfile +FROM ubuntu:24.04 +# ARM64 native compilation environment +# Go 1.24.0 ARM64 binary (go1.24.0.linux-arm64.tar.gz) +# Native GCC toolchain for ARM64 +# All GTK/WebKit dependencies for ARM64 +# Build script: /build/build-linux-arm64.sh +# Output: testbuild-{example}-linux-arm64 +``` + +#### x86_64 Native Build Environment (`test/docker/Dockerfile.linux-x86_64`) +```dockerfile +FROM --platform=linux/amd64 ubuntu:24.04 +# x86_64 native compilation environment +# Go 1.24.0 x86_64 binary (go1.24.0.linux-amd64.tar.gz) +# Native GCC toolchain for x86_64 +# All GTK/WebKit dependencies for x86_64 +# Build script: /build/build-linux-x86_64.sh +# Output: testbuild-{example}-linux-x86_64 +``` + +### Available Docker Tasks + +#### Architecture-Specific Tasks +```bash +# ARM64 builds +task test:example:linux:docker:arm64 DIR=badge +task test:examples:linux:docker:arm64 + +# x86_64 builds +task test:example:linux:docker:x86_64 DIR=badge +task test:examples:linux:docker:x86_64 +``` + +#### Auto-Detection Tasks (Recommended) +```bash +# Single example (auto-detects host architecture) +task test:example:linux:docker DIR=badge + +# All examples (auto-detects host architecture) +task test:examples:linux:docker +``` + +## Implementation Details + +### Key Fixes Applied in v3.0.0-alpha + +#### 1. **Complete Example Coverage** +- **Before**: 35 examples tested +- **After**: 43 examples tested (100% coverage) +- **Added**: dev, events-bug, gin-example, gin-routing, gin-service, html-dnd-api, notifications + +#### 2. **Go Module Resolution** +- **Issue**: Inconsistent replace directives across examples +- **Fix**: Standardized all examples to use `replace github.com/wailsapp/wails/v3 => ../..` +- **Examples Fixed**: gin-example, gin-routing, notifications + +#### 3. **Frontend Asset Embedding** +- **Issue**: Some examples referenced missing `frontend/dist` directories +- **Fix**: Updated embed paths from `//go:embed all:frontend/dist` to `//go:embed all:frontend` +- **Examples Fixed**: file-association, notifications + +#### 4. **Manager API Migration** +- **Issue**: Windows badge service using deprecated API +- **Fix**: Updated `app.CurrentWindow()` → `app.Windows.Current()` +- **Files Fixed**: pkg/services/badge/badge_windows.go + +#### 5. **File Association Example** +- **Issue**: Undefined window variable +- **Fix**: Added proper window assignment from `app.Windows.NewWithOptions()` +- **Files Fixed**: examples/file-association/main.go + +### Build Performance +- **macOS**: ~2-3 minutes for all 43 examples +- **Windows Cross-Compile**: ~2-3 minutes for all 43 examples +- **Linux Docker**: ~5-10 minutes for all 43 examples (includes image build) +- **Total Build Time**: ~10-15 minutes for complete cross-platform validation (129 builds) + +## Usage Examples + +### Single Example Testing (Requires DIR Parameter) +```bash +# Test the badge example on all platforms +task test:example:darwin DIR=badge # macOS native +task test:example:windows DIR=badge # Windows cross-compile +task test:example:linux:docker DIR=badge # Linux Docker (auto-detect arch) +``` + +### All Examples Testing (No DIR Parameter) +```bash +# Test everything - all 43 examples, all platforms +task test:examples:all + +# This runs: +# 1. All Darwin builds (43 examples) +# 2. All Windows cross-compilation (43 examples) +# 3. All Linux Docker builds (43 examples, auto-architecture) + +# Platform-specific all examples +task test:examples # Current platform (43 examples) +task test:examples:linux:docker:arm64 # ARM64 builds (43 examples) +task test:examples:linux:docker:x86_64 # x86_64 builds (43 examples) +``` + +### Continuous Integration +```bash +# For CI/CD pipelines +task test:examples:all # Complete cross-platform (129 builds) +task test:examples # Current platform only (43 builds) +``` + +## Build Process Details + +### macOS Builds +1. Sets macOS-specific CGO flags for compatibility +2. Runs `go mod tidy` in each example directory +3. Compiles with `go build -o testbuild-{example}-darwin` +4. Links against UniformTypeIdentifiers framework + +### Windows Cross-Compilation +1. Sets `GOOS=windows GOARCH=amd64` environment +2. Runs `go mod tidy` in each example directory +3. Cross-compiles with `go build -o testbuild-{example}-windows.exe` +4. No CGO dependencies required (uses Windows APIs) + +### Linux Docker Builds +1. **Auto-Detection**: Detects host architecture (ARM64 or x86_64) +2. **Image Selection**: Uses appropriate Ubuntu 24.04 image for target architecture +3. **Native Compilation**: Eliminates cross-compilation CGO issues +4. **Environment Setup**: Full GTK/WebKit development environment +5. **Build Process**: Runs `go mod tidy && go build` with native toolchain +6. **Output**: Architecture-specific binaries (`-linux-arm64` or `-linux-x86_64`) + +## Troubleshooting + +### Common Issues (All Resolved in v3.0.0-alpha) + +#### **Go Module Resolution Errors** +```bash +Error: replacement directory ../wails/v3 does not exist +``` +**Solution**: All examples now use standardized `replace github.com/wailsapp/wails/v3 => ../..` + +#### **Frontend Asset Embedding Errors** +```bash +Error: pattern frontend/dist: no matching files found +``` +**Solution**: Updated to `//go:embed all:frontend` for examples without dist directories + +#### **Manager API Errors** +```bash +Error: app.CurrentWindow undefined +``` +**Solution**: Updated to use new manager pattern `app.Windows.Current()` + +#### **Build Warnings** +Some examples may show compatibility warnings (e.g., notifications using macOS 10.14+ APIs with 10.13 target). These are non-blocking warnings that can be addressed separately. + +### Performance Optimization + +#### **Parallel Builds** +```bash +# The task system automatically runs builds in parallel where possible +task v3:test:examples:all # Optimized for maximum throughput +``` + +#### **Selective Testing** +```bash +# Test specific examples to debug issues +task v3:test:example:darwin DIR=badge +task v3:test:example:windows DIR=contextmenus +``` + +### Performance Tips + +**Parallel Builds:** +```bash +# Build multiple examples simultaneously +task v3:test:example:darwin DIR=badge & +task v3:test:example:darwin DIR=binding & +task v3:test:example:darwin DIR=build & +wait +``` + +**Docker Image Caching:** +```bash +# Pre-build Docker images +docker build -f Dockerfile.linux -t wails-v3-linux-builder . +docker build -f Dockerfile.linux-simple -t wails-v3-linux-simple . +``` + +## Integration with Git + +### Ignored Files +All build artifacts are automatically ignored via `.gitignore`: +```gitignore +/v3/examples/*/testbuild-* +``` + +### Clean Build Environment +```bash +# Remove all test build artifacts +find v3/examples -name "testbuild-*" -delete +``` + +## Validation Results + +### Current Status (as of implementation): +- ✅ **macOS**: All 43 examples compile successfully +- ✅ **Windows**: All 43 examples cross-compile successfully +- ✅ **Linux**: Multi-architecture Docker system fully functional + +### Build Time Estimates: +- **macOS**: ~2-3 minutes for all examples +- **Windows**: ~2-3 minutes for all examples (cross-compile) +- **Linux Docker**: ~5-10 minutes for all examples (includes image build and compilation) +- **Complete Cross-Platform**: ~10-15 minutes for 129 total builds + +## Future Enhancements + +### Planned Improvements: +1. **Automated Testing**: Add runtime testing in addition to compilation +2. **Multi-Architecture**: Support ARM64 builds for Apple Silicon and Windows ARM +3. **Build Caching**: Implement Go build cache for faster repeated builds +4. **Parallel Docker**: Multi-stage Docker builds for faster Linux compilation +5. **Platform Matrix**: GitHub Actions integration for automated CI/CD + +### Platform Extensions: +- **FreeBSD**: Add BSD build support +- **Android/iOS**: Mobile platform compilation (when supported) +- **WebAssembly**: WASM target compilation + +## Changelog + +### v3.0.0-alpha (2025-06-20) +#### 🎯 Complete Cross-Platform Testing System + +#### **✨ New Features** +- **Complete Example Coverage**: All 43 examples now tested (was 35) +- **Cross-Platform Validation**: Mac + Windows builds for all examples +- **Standardized Build Artifacts**: Consistent platform-specific naming +- **Enhanced Git Integration**: Complete .gitignore patterns for build artifacts + +#### **🐛 Major Fixes** +- **Go Module Resolution**: Standardized replace directives across all examples +- **Frontend Asset Embedding**: Fixed missing frontend/dist directory references +- **Manager API Migration**: Updated deprecated Windows badge service calls +- **File Association**: Fixed undefined window variable +- **Build Completeness**: Added 8 missing examples to test suite + +#### **🔧 Infrastructure Improvements** +- **Taskfile Integration**: Comprehensive cross-platform build tasks +- **Performance Optimization**: Parallel builds where possible +- **Error Handling**: Clear build failure reporting and debugging +- **Documentation**: Complete testing guide with troubleshooting + +#### **📊 Validation Results** +- **macOS**: ✅ 43/43 examples compile successfully +- **Windows**: ✅ 43/43 examples cross-compile successfully +- **Build Time**: ~5-6 minutes for complete cross-platform validation +- **Reliability**: 100% success rate with proper error handling + +## Support + +For issues with cross-platform builds: +1. Check platform-specific requirements above +2. Review the troubleshooting section for resolved issues +3. Verify Go 1.24+ is installed +4. Check build logs for specific error messages +5. Use selective testing to isolate problems + +## References + +- [Wails v3 Documentation](https://wails.io/docs/) +- [Go Cross Compilation](https://golang.org/doc/install/cross) +- [GTK Development Libraries](https://www.gtk.org/docs/installations/linux) +- [Task Runner Documentation](https://taskfile.dev/) \ No newline at end of file diff --git a/v3/Taskfile.yaml b/v3/Taskfile.yaml new file mode 100644 index 000000000..840f980a1 --- /dev/null +++ b/v3/Taskfile.yaml @@ -0,0 +1,401 @@ +# https://taskfile.dev + +version: "3" + +includes: + generator: + taskfile: ./internal/generator + dir: ./internal/generator + + runtime: + taskfile: ./internal/runtime + dir: ./internal/runtime + + website: + taskfile: ./website + dir: ./website + optional: true + + docs: + taskfile: ../docs + dir: ../docs + optional: true + +tasks: + recreate-template-dir: + dir: internal/templates + internal: true + silent: true + cmds: + - rm -rf {{.TEMPLATE_DIR}} + - mkdir -p {{.TEMPLATE_DIR}} + + install: + dir: cmd/wails3 + silent: true + cmds: + - go install + - echo "Installed wails CLI" + + release: + summary: Release a new version of Wails. Call with `task v3:release -- ` + dir: tasks/release + cmds: + - go run release.go {{.CLI_ARGS}} + + taskfile:upgrade: + cmds: + - go get -u github.com/go-task/task/v3 + + reinstall-cli: + dir: cmd/wails3 + internal: true + silent: true + cmds: + - go install + - echo "Reinstalled wails CLI" + + generate:events: + dir: tasks/events + cmds: + - go run generate.go + - go fmt ../../pkg/events/events.go + + precommit: + cmds: + - go test ./... + - task: format +# - task: docs:update:api + + test:example:darwin: + dir: 'examples/{{.DIR}}' + platforms: + - darwin + cmds: + - echo "Building example {{.DIR}} for Darwin" + - go mod tidy + - go build -o "testbuild-{{.DIR}}-darwin{{exeExt}}" + env: + CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13 + CGO_CFLAGS: -mmacosx-version-min=10.13 + + test:example:windows: + dir: 'examples/{{.DIR}}' + platforms: + - windows + cmds: + - echo "Building example {{.DIR}} for Windows" + - go mod tidy + - go build -o "testbuild-{{.DIR}}-windows.exe" + env: + GOOS: windows + GOARCH: amd64 + + test:example:linux: + dir: 'examples/{{.DIR}}' + platforms: + - linux + cmds: + - echo "Building example {{.DIR}} for Linux" + - go mod tidy + - go build -o "testbuild-{{.DIR}}-linux" + + test:example:linux:docker:arm64: + summary: Build a single example for Linux ARM64 using Docker (Ubuntu 24.04) + cmds: + - echo "Building example {{.DIR}} for Linux ARM64 using Docker" + - docker build --pull -f test/docker/Dockerfile.linux-arm64 -t wails-v3-linux-arm64 . + - docker run --rm wails-v3-linux-arm64 /build/build-linux-arm64.sh {{.DIR}} + + test:example:linux:docker:x86_64: + summary: Build a single example for Linux x86_64 using Docker (Ubuntu 24.04) + cmds: + - echo "Building example {{.DIR}} for Linux x86_64 using Docker" + - docker build --pull -f test/docker/Dockerfile.linux-x86_64 -t wails-v3-linux-x86_64 . + - docker run --rm wails-v3-linux-x86_64 /build/build-linux-x86_64.sh {{.DIR}} + + test:examples:linux:docker:arm64: + summary: Build all examples for Linux ARM64 using Docker (Ubuntu 24.04) + cmds: + - echo "Building Docker image for Linux ARM64 compilation..." + - docker build --pull -f test/docker/Dockerfile.linux-arm64 -t wails-v3-linux-arm64 . + - echo "Running Linux ARM64 compilation in Docker container..." + - docker run --rm wails-v3-linux-arm64 + + test:examples:linux:docker:x86_64: + summary: Build all examples for Linux x86_64 using Docker (Ubuntu 24.04) + cmds: + - echo "Building Docker image for Linux x86_64 compilation..." + - docker build --pull -f test/docker/Dockerfile.linux-x86_64 -t wails-v3-linux-x86_64 . + - echo "Running Linux x86_64 compilation in Docker container..." + - docker run --rm wails-v3-linux-x86_64 + + test:example:linux:docker: + summary: Build a single example for Linux using Docker (auto-detect architecture) + cmds: + - echo "Auto-detecting architecture for Linux Docker build..." + - | + if [ "$(uname -m)" = "arm64" ] || [ "$(uname -m)" = "aarch64" ]; then + echo "Detected ARM64, using ARM64 Docker image" + task test:example:linux:docker:arm64 DIR={{.DIR}} + else + echo "Detected x86_64, using x86_64 Docker image" + task test:example:linux:docker:x86_64 DIR={{.DIR}} + fi + + test:examples:linux:docker: + summary: Build all examples for Linux using Docker (auto-detect architecture) + cmds: + - echo "Auto-detecting architecture for Linux Docker build..." + - | + if [ "$(uname -m)" = "arm64" ] || [ "$(uname -m)" = "aarch64" ]; then + echo "Detected ARM64, using ARM64 Docker image" + task test:examples:linux:docker:arm64 + else + echo "Detected x86_64, using x86_64 Docker image" + task test:examples:linux:docker:x86_64 + fi + + test:examples:all: + summary: Builds all examples for all platforms (Mac + Windows + Linux via Docker) + vars: + EXAMPLEDIRS: | + badge + badge-custom + binding + build + cancel-async + cancel-chaining + clipboard + contextmenus + dev + dialogs + dialogs-basic + drag-n-drop + environment + events + events-bug + file-association + frameless + gin-example + gin-routing + gin-service + hide-window + ignore-mouse + keybindings + liquid-glass + menu + notifications + panic-handling + plain + raw-message + screen + services + show-macos-toolbar + single-instance + systray-basic + systray-custom + systray-menu + video + window + window-api + window-call + window-menu + wml + cmds: + - echo "Building all examples for all platforms..." + - echo "=== Building for Darwin ===" + - for: { var: EXAMPLEDIRS } + task: test:example:darwin + vars: + DIR: "{{.ITEM}}" + - echo "=== Building for Windows (cross-compile) ===" + - for: { var: EXAMPLEDIRS } + task: test:example:windows + vars: + DIR: "{{.ITEM}}" + - echo "=== Building for Linux (Docker) ===" + - task: test:examples:linux:docker + - echo "=== Testing CLI Code ===" + - task: test:cli + - echo "=== Cleaning Up Test Binaries ===" + - task: clean:test:binaries + + test:cli: + summary: Test CLI-related code compilation + cmds: + - echo "Testing CLI appimage testfiles compilation..." + - cd internal/commands/appimage_testfiles && go mod tidy && go build + - echo "✅ CLI appimage testfiles compile successfully" + + test:cli:all: + summary: Test all CLI components and critical test files + cmds: + - echo "Testing CLI appimage testfiles..." + - cd internal/commands/appimage_testfiles && go mod tidy && go build + - echo "Testing window visibility test..." + - cd tests/window-visibility-test && go mod tidy && go build + - echo "Testing service implementations..." + - cd pkg/services/badge && go build + - echo "✅ All CLI components compile successfully" + + test:generator: + summary: Test code generator test cases compilation + cmds: + - echo "Testing generator test cases (sample)..." + - cd internal/generator/testcases/function_single && go mod tidy && go build + - cd internal/generator/testcases/complex_method && go mod tidy && go build + - cd internal/generator/testcases/struct_literal_single && go mod tidy && go build + - echo "✅ Generator test cases compile successfully" + + test:templates: + summary: Test template generation for core templates + cmds: + - echo "Testing template generation (core templates)..." + - task: install + - echo "Testing lit template generation..." + - rm -rf ./test-template-lit && wails3 init -n test-template-lit -t lit + - mkdir -p ./test-template-lit/frontend/dist && touch ./test-template-lit/frontend/dist/.keep + - cd ./test-template-lit && go mod tidy && go build + - rm -rf ./test-template-lit + - echo "Testing react template generation..." + - rm -rf ./test-template-react && wails3 init -n test-template-react -t react + - mkdir -p ./test-template-react/frontend/dist && touch ./test-template-react/frontend/dist/.keep + - cd ./test-template-react && go mod tidy && go build + - rm -rf ./test-template-react + - echo "✅ Template generation tests completed successfully" + + test:infrastructure: + summary: Test critical infrastructure components + cmds: + - echo "=== Testing CLI Components ===" + - task: test:cli:all + - echo "=== Testing Generator ===" + - task: test:generator + - echo "=== Testing Templates ===" + - task: test:templates + - echo "=== Testing pkg/application ===" + - cd pkg/application && go test -c -o /dev/null ./... + - echo "=== Cleaning Up Test Binaries ===" + - task: clean:test:binaries + - echo "✅ All infrastructure components test successfully" + + test:examples: + summary: Builds the examples for current platform only + vars: + EXAMPLEDIRS: | + badge + badge-custom + binding + build + cancel-async + cancel-chaining + clipboard + contextmenus + dev + dialogs + dialogs-basic + drag-n-drop + environment + events + events-bug + file-association + frameless + gin-example + gin-routing + gin-service + hide-window + ignore-mouse + keybindings + liquid-glass + menu + notifications + panic-handling + plain + raw-message + screen + services + show-macos-toolbar + single-instance + systray-basic + systray-custom + systray-menu + video + window + window-api + window-call + window-menu + wml + cmds: + - echo "Testing examples compilation..." + - for: { var: EXAMPLEDIRS } + task: test:example:darwin + vars: + DIR: "{{.ITEM}}" + platforms: [darwin] + - for: { var: EXAMPLEDIRS } + task: test:example:linux + vars: + DIR: "{{.ITEM}}" + platforms: [linux] + - for: { var: EXAMPLEDIRS } + task: test:example:windows + vars: + DIR: "{{.ITEM}}" + platforms: [windows] + - echo "Testing CLI code..." + - task: test:cli + - echo "=== Cleaning Up Test Binaries ===" + - task: clean:test:binaries + + clean:test:binaries: + summary: Clean up all test-generated binary files and directories (cross-platform) + cmds: + - echo "🧹 Cleaning up test binaries..." + - go run tasks/cleanup/cleanup.go + - echo "✅ Test binaries cleaned up" + + test:all: + summary: Run all tests including examples, infrastructure, and Go unit tests + cmds: + - echo "=== Running Go Unit Tests ===" + - go test ./... + - echo "=== Testing Examples (Current Platform) ===" + - task: test:examples + - echo "=== Testing Infrastructure Components ===" + - task: test:infrastructure + - echo "=== Cleaning Up Test Binaries ===" + - task: clean:test:binaries + - echo "✅ All tests completed successfully" + + build:server: + summary: Build an application in server mode (no GUI, HTTP server only) + desc: | + Builds a Wails application in server mode using the -tags server build tag. + Server mode enables running the application as a pure HTTP server without + native GUI dependencies. + + Usage: task build:server DIR=examples/server + dir: '{{.DIR | default "."}}' + cmds: + - echo "Building {{.DIR | default `.`}} in server mode..." + - go build -tags server -o '{{.OUTPUT | default "server-app"}}' . + - echo "Server mode build complete" + + test:example:server: + summary: Build and test the server mode example + dir: 'examples/server' + cmds: + - echo "Building server example with -tags server..." + - go mod tidy + - go build -tags server -o "testbuild-server" + - echo "✅ Server example builds successfully" + - rm -f testbuild-server + + test:server: + summary: Run server mode unit tests + dir: 'pkg/application' + cmds: + - echo "Running server mode tests..." + - go test -tags server -v -run TestServerMode . + - echo "✅ Server mode tests passed" diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md new file mode 100644 index 000000000..e5997d5d3 --- /dev/null +++ b/v3/UNRELEASED_CHANGELOG.md @@ -0,0 +1,62 @@ +# Unreleased Changes + + + +## Added + +- Add `UseApplicationMenu` option to `WebviewWindowOptions` allowing windows on Windows/Linux to inherit the application menu set via `app.Menu.Set()` by @leaanthony + +## Changed + +- Move `EnabledFeatures`, `DisabledFeatures`, and `AdditionalBrowserArgs` from per-window options to application-level `Options.Windows` (#4559) by @leaanthony + +## Fixed + +- Fix potential panic when setting empty icon or bitmap on Linux (#4923) by @ddmoney420 +- Fix ErrorDialog crash when called from service binding on macOS (#3631) by @leaanthony +- Make menus to be displayed on Windows OS in `v3\examples\dialogs` by @ndianabasi +- Fix race condition causing TypeError during page reload (#4872) by @ddmoney420 + +- Fix incorrect output from binding generator tests by removing global state in the `Collector.IsVoidAlias()` method (#4941) by @fbbdev + +## Deprecated + + +## Removed + +- **BREAKING**: Remove `EnabledFeatures`, `DisabledFeatures`, and `AdditionalLaunchArgs` from per-window `WindowsWindow` options. Use application-level `Options.Windows.EnabledFeatures`, `Options.Windows.DisabledFeatures`, and `Options.Windows.AdditionalBrowserArgs` instead. These flags apply globally to the shared WebView2 environment (#4559) by @leaanthony + +## Security + + +--- + +### Example Entries: + +**Added:** +- Add support for custom window icons in application options +- Add new `SetWindowIcon()` method to runtime API (#1234) + +**Changed:** +- Update minimum Go version requirement to 1.21 +- Improve error messages for invalid configuration files + +**Fixed:** +- Fix memory leak in event system during window close operations (#5678) +- Fix crash when using context menus on Linux with Wayland + +**Security:** +- Update dependencies to address CVE-2024-12345 in third-party library diff --git a/v3/build_ios.sh b/v3/build_ios.sh new file mode 100755 index 000000000..b20f5dfcb --- /dev/null +++ b/v3/build_ios.sh @@ -0,0 +1,233 @@ +#!/bin/bash + +# Wails v3 iOS Build Script +# This script builds a Wails application for iOS Simulator + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}Wails v3 iOS Build Script${NC}" +echo "===============================" + +# Check for required tools +check_command() { + if ! command -v $1 &> /dev/null; then + echo -e "${RED}Error: $1 is not installed${NC}" + exit 1 + fi +} + +echo "Checking dependencies..." +check_command go +check_command xcodebuild +check_command xcrun + +# Configuration +APP_NAME="${APP_NAME:-WailsIOSDemo}" +BUNDLE_ID="${BUNDLE_ID:-com.wails.iosdemo}" +BUILD_DIR="build/ios" +SIMULATOR_SDK="iphonesimulator" +MIN_IOS_VERSION="13.0" + +# Clean build directory +echo "Cleaning build directory..." +rm -rf $BUILD_DIR +mkdir -p $BUILD_DIR + +# Create the iOS app structure +echo "Creating iOS app structure..." +APP_DIR="$BUILD_DIR/$APP_NAME.app" +mkdir -p "$APP_DIR" + +# Create Info.plist +echo "Creating Info.plist..." +cat > "$BUILD_DIR/Info.plist" << EOF + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $APP_NAME + CFBundleIdentifier + $BUNDLE_ID + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $APP_NAME + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + MinimumOSVersion + $MIN_IOS_VERSION + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + +EOF + +cp "$BUILD_DIR/Info.plist" "$APP_DIR/" + +# Build the Go application for iOS Simulator +echo -e "${YELLOW}Building Go application for iOS Simulator...${NC}" + +# Set up environment for iOS cross-compilation +export CGO_ENABLED=1 +export GOOS=ios +export GOARCH=arm64 +export SDK_PATH=$(xcrun --sdk $SIMULATOR_SDK --show-sdk-path) +export CGO_CFLAGS="-isysroot $SDK_PATH -mios-simulator-version-min=$MIN_IOS_VERSION -arch arm64 -fembed-bitcode" +export CGO_LDFLAGS="-isysroot $SDK_PATH -mios-simulator-version-min=$MIN_IOS_VERSION -arch arm64" + +# Find clang for the simulator +export CC=$(xcrun --sdk $SIMULATOR_SDK --find clang) +export CXX=$(xcrun --sdk $SIMULATOR_SDK --find clang++) + +echo "SDK Path: $SDK_PATH" +echo "CC: $CC" + +# Build the demo app using the example +echo "Building demo application..." + +# Create a simplified main.go that uses local packages +cat > "$BUILD_DIR/main.go" << 'EOF' +//go:build ios + +package main + +import ( + "fmt" + "log" +) + +// Since we're building a proof of concept, we'll create a minimal app +// that demonstrates the iOS integration + +func main() { + fmt.Println("Wails iOS Demo Starting...") + + // For the PoC, we'll import the iOS platform code directly + // In production, this would use the full Wails v3 application package + + log.Println("iOS application would start here") + // The actual iOS app initialization happens in the Objective-C layer + // This is just a placeholder for the build process +} +EOF + +# Try to build the binary +cd "$BUILD_DIR" +echo "Attempting to build iOS binary..." + +# For now, let's create a simple test binary to verify the build toolchain +go build -tags ios -o "$APP_NAME" main.go 2>&1 || { + echo -e "${YELLOW}Note: Full iOS build requires gomobile or additional setup${NC}" + echo "Creating placeholder binary for demonstration..." + + # Create a placeholder executable + cat > "$APP_NAME.c" << 'EOF' +#include +int main() { + printf("Wails iOS Demo Placeholder\n"); + return 0; +} +EOF + + $CC -isysroot $SDK_PATH -arch arm64 -mios-simulator-version-min=$MIN_IOS_VERSION \ + -o "$APP_NAME" "$APP_NAME.c" +} + +# Sign the app for simulator (no actual certificate needed) +echo "Preparing app for simulator..." +codesign --force --sign - "$APP_NAME" 2>/dev/null || true +mv "$APP_NAME" "$APP_DIR/" + +# Create a simple launch script +echo "Creating launch script..." +cd - > /dev/null +cat > "$BUILD_DIR/run_simulator.sh" << 'EOF' +#!/bin/bash + +echo "iOS Simulator Launch Script" +echo "============================" + +# Check if Simulator is available +if ! command -v open &> /dev/null; then + echo "Error: Cannot open Simulator" + exit 1 +fi + +# Open Xcode Simulator +echo "Opening iOS Simulator..." +open -a Simulator 2>/dev/null || { + echo "Error: Could not open Simulator. Make sure Xcode is installed." + exit 1 +} + +echo "" +echo "Simulator should now be opening..." +echo "" +echo "Note: This is a proof of concept demonstrating:" +echo " 1. ✅ WebView creation (application_ios.m)" +echo " 2. ✅ Request interception via WKURLSchemeHandler" +echo " 3. ✅ JavaScript execution bridge" +echo " 4. ✅ iOS Simulator build support" +echo "" +echo "The full implementation would require:" +echo " - gomobile for proper Go/iOS integration" +echo " - Proper Xcode project generation" +echo " - Full CGO bindings compilation" +echo "" +echo "See IOS_ARCHITECTURE.md for complete technical details." +EOF + +chmod +x "$BUILD_DIR/run_simulator.sh" + +echo -e "${GREEN}Build complete!${NC}" +echo "" +echo "Build artifacts created in: $BUILD_DIR" +echo "" +echo "To open the iOS Simulator:" +echo " cd $BUILD_DIR && ./run_simulator.sh" +echo "" +echo "The proof of concept demonstrates:" +echo " 1. ✅ WebView creation code (pkg/application/application_ios.m)" +echo " 2. ✅ Request interception (WKURLSchemeHandler implementation)" +echo " 3. ✅ JavaScript execution (bidirectional bridge)" +echo " 4. ✅ iOS build configuration and simulator support" +echo "" +echo "Full implementation requires gomobile integration." +echo "See IOS_ARCHITECTURE.md for complete technical documentation." \ No newline at end of file diff --git a/v3/cmd/wails3/README.md b/v3/cmd/wails3/README.md new file mode 100644 index 000000000..8924153dd --- /dev/null +++ b/v3/cmd/wails3/README.md @@ -0,0 +1,83 @@ +# 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 + +### task + +The `task` command is for running tasks defined in `Taskfile.yml`. It is a wrapper around [Task](https://taskfile.dev). + +### 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 | icon.ico | +| `-macFilename` | string | The output filename for the Mac icon bundle | icons.icns | + +```bash +wails3 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 +wails3 generate syso +``` + +| 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 +wails3 generate defaults +``` +This will generate all the default assets and resources in the current directory. + +#### bindings + +```bash +wails3 generate bindings +``` + +Generates bindings and models for your bound Go methods and structs. \ No newline at end of file diff --git a/v3/cmd/wails3/main.go b/v3/cmd/wails3/main.go new file mode 100644 index 000000000..f239b6354 --- /dev/null +++ b/v3/cmd/wails3/main.go @@ -0,0 +1,174 @@ +package main + +import ( + "os" + "runtime/debug" + + "github.com/pkg/browser" + + "github.com/pterm/pterm" + "github.com/samber/lo" + + "github.com/leaanthony/clir" + "github.com/wailsapp/wails/v3/internal/commands" + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/term" +) + +func init() { + buildInfo, ok := debug.ReadBuildInfo() + if !ok { + return + } + commands.BuildSettings = lo.Associate(buildInfo.Settings, func(setting debug.BuildSetting) (string, string) { + return setting.Key, setting.Value + }) + // Iterate over the Deps and add them to the build settings using a prefix of "mod." + for _, dep := range buildInfo.Deps { + commands.BuildSettings["mod."+dep.Path] = dep.Version + } +} + +func main() { + app := clir.NewCli("wails", "The Wails3 CLI", "v3") + app.NewSubCommand("docs", "Open the docs").Action(openDocs) + app.NewSubCommandFunction("init", "Initialise a new project", commands.Init) + + build := app.NewSubCommand("build", "Build the project") + var buildFlags flags.Build + build.AddFlags(&buildFlags) + build.Action(func() error { + return commands.Build(&buildFlags, build.OtherArgs()) + }) + + app.NewSubCommandFunction("dev", "Run in Dev mode", commands.Dev) + + pkg := app.NewSubCommand("package", "Package application") + var pkgFlags flags.Package + pkg.AddFlags(&pkgFlags) + pkg.Action(func() error { + return commands.Package(&pkgFlags, pkg.OtherArgs()) + }) + app.NewSubCommandFunction("doctor", "System status report", commands.Doctor) + app.NewSubCommandFunction("releasenotes", "Show release notes", commands.ReleaseNotes) + + task := app.NewSubCommand("task", "Run and list tasks") + var taskFlags commands.RunTaskOptions + task.AddFlags(&taskFlags) + task.Action(func() error { + return commands.RunTask(&taskFlags, task.OtherArgs()) + }) + task.LongDescription("\nUsage: wails3 task [taskname] [flags]\n\nTasks are defined in the `Taskfile.yaml` file. See https://taskfile.dev for more information.") + + generate := app.NewSubCommand("generate", "Generation tools") + generate.NewSubCommandFunction("build-assets", "Generate build assets", commands.GenerateBuildAssets) + generate.NewSubCommandFunction("icons", "Generate icons", commands.GenerateIcons) + generate.NewSubCommandFunction("syso", "Generate Windows .syso file", commands.GenerateSyso) + generate.NewSubCommandFunction("runtime", "Generate the pre-built version of the runtime", commands.GenerateRuntime) + generate.NewSubCommandFunction("webview2bootstrapper", "Generate WebView2 bootstrapper", commands.GenerateWebView2Bootstrapper) + generate.NewSubCommandFunction("template", "Generate a new template", commands.GenerateTemplate) + + update := app.NewSubCommand("update", "Update tools") + update.NewSubCommandFunction("build-assets", "Updates the build assets using the given config file", commands.UpdateBuildAssets) + update.NewSubCommandFunction("cli", "Updates the Wails CLI", commands.UpdateCLI) + + bindgen := generate.NewSubCommand("bindings", "Generate bindings + models") + var bindgenFlags flags.GenerateBindingsOptions + bindgen.AddFlags(&bindgenFlags) + bindgen.Action(func() error { + return commands.GenerateBindings(&bindgenFlags, bindgen.OtherArgs()) + }) + bindgen.LongDescription("\nUsage: wails3 generate bindings [flags] [patterns...]\n\nPatterns match packages to scan for bound types.\nPattern format is analogous to that of the Go build tool,\ne.g. './...' matches packages in the current directory and all descendants.\nIf no pattern is given, the tool will fall back to the current directory.") + generate.NewSubCommandFunction("constants", "Generate JS constants from Go", commands.GenerateConstants) + generate.NewSubCommandFunction(".desktop", "Generate .desktop file", commands.GenerateDotDesktop) + generate.NewSubCommandFunction("appimage", "Generate Linux AppImage", commands.GenerateAppImage) + + plugin := app.NewSubCommand("service", "Service tools") + plugin.NewSubCommandFunction("init", "Initialise a new service", commands.ServiceInit) + + tool := app.NewSubCommand("tool", "Various tools") + tool.NewSubCommandFunction("checkport", "Checks if a port is open. Useful for testing if vite is running.", commands.ToolCheckPort) + tool.NewSubCommandFunction("watcher", "Watches files and runs a command when they change", commands.Watcher) + tool.NewSubCommandFunction("cp", "Copy files", commands.Cp) + tool.NewSubCommandFunction("buildinfo", "Show Build Info", commands.BuildInfo) + tool.NewSubCommandFunction("package", "Generate Linux packages (deb, rpm, archlinux)", commands.ToolPackage) + tool.NewSubCommandFunction("version", "Bump semantic version", commands.ToolVersion) + tool.NewSubCommandFunction("lipo", "Create macOS universal binary from multiple architectures", commands.ToolLipo) + + // Low-level sign tool (used by Taskfiles) + toolSign := tool.NewSubCommand("sign", "Sign a binary or package directly") + var toolSignFlags flags.Sign + toolSign.AddFlags(&toolSignFlags) + toolSign.Action(func() error { + return commands.Sign(&toolSignFlags) + }) + + // Setup commands + setup := app.NewSubCommand("setup", "Project setup wizards") + setupSigning := setup.NewSubCommand("signing", "Configure code signing") + var setupSigningFlags flags.SigningSetup + setupSigning.AddFlags(&setupSigningFlags) + setupSigning.Action(func() error { + return commands.SigningSetup(&setupSigningFlags) + }) + + setupEntitlements := setup.NewSubCommand("entitlements", "Configure macOS entitlements") + var setupEntitlementsFlags flags.EntitlementsSetup + setupEntitlements.AddFlags(&setupEntitlementsFlags) + setupEntitlements.Action(func() error { + return commands.EntitlementsSetup(&setupEntitlementsFlags) + }) + + // Sign command (wrapper that calls platform-specific tasks) + sign := app.NewSubCommand("sign", "Sign binaries and packages for current or specified platform") + var signWrapperFlags flags.SignWrapper + sign.AddFlags(&signWrapperFlags) + sign.Action(func() error { + return commands.SignWrapper(&signWrapperFlags, sign.OtherArgs()) + }) + + // iOS tools + ios := app.NewSubCommand("ios", "iOS tooling") + ios.NewSubCommandFunction("overlay:gen", "Generate Go overlay for iOS bridge shim", commands.IOSOverlayGen) + ios.NewSubCommandFunction("xcode:gen", "Generate Xcode project in output directory", commands.IOSXcodeGen) + + app.NewSubCommandFunction("version", "Print the version", commands.Version) + app.NewSubCommand("sponsor", "Sponsor the project").Action(openSponsor) + + defer printFooter() + + err := app.Run() + if err != nil { + pterm.Error.Println(err) + os.Exit(1) + } +} + +func printFooter() { + if !commands.DisableFooter { + docsLink := term.Hyperlink("https://v3.wails.io/getting-started/your-first-app/", "wails3 docs") + + pterm.Println(pterm.LightGreen("\nNeed documentation? Run: ") + pterm.LightBlue(docsLink)) + // Check if we're in a teminal + printer := pterm.PrefixPrinter{ + MessageStyle: pterm.NewStyle(pterm.FgLightGreen), + Prefix: pterm.Prefix{ + Style: pterm.NewStyle(pterm.FgRed, pterm.BgLightWhite), + Text: "♥ ", + }, + } + + linkText := term.Hyperlink("https://github.com/sponsors/leaanthony", "wails3 sponsor") + printer.Println("If Wails is useful to you or your company, please consider sponsoring the project: " + pterm.LightBlue(linkText)) + } +} + +func openDocs() error { + commands.DisableFooter = true + return browser.OpenURL("https://v3.wails.io/getting-started/your-first-app/") +} + +func openSponsor() error { + commands.DisableFooter = true + return browser.OpenURL("https://github.com/sponsors/leaanthony") +} diff --git a/v3/examples/README.md b/v3/examples/README.md new file mode 100644 index 000000000..753ec5138 --- /dev/null +++ b/v3/examples/README.md @@ -0,0 +1,17 @@ +# v3 + +*NOTE*: The examples in this directory may or may not compile / run at any given time during alpha development. + + +## Running the examples + + cd v3/examples/ + go mod tidy + go run . + +## Compiling the examples + + cd v3/examples/ + go mod tidy + go build + ./ diff --git a/v3/examples/android/.gitignore b/v3/examples/android/.gitignore new file mode 100644 index 000000000..edb05e60a --- /dev/null +++ b/v3/examples/android/.gitignore @@ -0,0 +1,24 @@ +# Build outputs +bin/ +*.apk +*.aab + +# Android build artifacts +build/android/.gradle/ +build/android/app/build/ +build/android/local.properties + +# JNI libraries (generated during build) +build/android/app/src/main/jniLibs/*/libwails.so + +# IDE +.idea/ +*.iml + +# OS +.DS_Store +Thumbs.db + +# Frontend build +frontend/dist/ +frontend/node_modules/ diff --git a/v3/examples/android/.task/checksum/android-common-generate-icons b/v3/examples/android/.task/checksum/android-common-generate-icons new file mode 100644 index 000000000..4534dd92e --- /dev/null +++ b/v3/examples/android/.task/checksum/android-common-generate-icons @@ -0,0 +1 @@ +a40fe27d90a25e84deeed985e4075cfa diff --git a/v3/examples/android/.task/checksum/android-common-install-frontend-deps b/v3/examples/android/.task/checksum/android-common-install-frontend-deps new file mode 100644 index 000000000..997225071 --- /dev/null +++ b/v3/examples/android/.task/checksum/android-common-install-frontend-deps @@ -0,0 +1 @@ +82dedd4f821c351be61d8e1dbb6eefa diff --git a/v3/examples/android/.task/checksum/android-generate-android-bindings b/v3/examples/android/.task/checksum/android-generate-android-bindings new file mode 100644 index 000000000..ad9ec9f0b --- /dev/null +++ b/v3/examples/android/.task/checksum/android-generate-android-bindings @@ -0,0 +1 @@ +7bfce68482b8f82eb3495774fb52ddca diff --git a/v3/examples/android/.task/checksum/build-frontend--PRODUCTION-- b/v3/examples/android/.task/checksum/build-frontend--PRODUCTION-- new file mode 100644 index 000000000..4a8874ebd --- /dev/null +++ b/v3/examples/android/.task/checksum/build-frontend--PRODUCTION-- @@ -0,0 +1 @@ +aef25acb8df5f0f69361a3df9b49b2e diff --git a/v3/examples/android/.task/checksum/generate-bindings--BUILD_FLAGS--tags-android-debug--buildvcs-false--gcflags-all---l-- b/v3/examples/android/.task/checksum/generate-bindings--BUILD_FLAGS--tags-android-debug--buildvcs-false--gcflags-all---l-- new file mode 100644 index 000000000..52597e299 --- /dev/null +++ b/v3/examples/android/.task/checksum/generate-bindings--BUILD_FLAGS--tags-android-debug--buildvcs-false--gcflags-all---l-- @@ -0,0 +1 @@ +3eaf69fc9c4a0eeef54a9ebcc9b25cf7 diff --git a/v3/examples/android/Taskfile.yml b/v3/examples/android/Taskfile.yml new file mode 100644 index 000000000..4940aab8e --- /dev/null +++ b/v3/examples/android/Taskfile.yml @@ -0,0 +1,34 @@ +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + android: ./build/android/Taskfile.yml + +vars: + APP_NAME: "android" + BIN_DIR: "bin" + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{OS}}:package" + + run: + summary: Runs the application + cmds: + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} diff --git a/v3/examples/android/build/Taskfile.yml b/v3/examples/android/build/Taskfile.yml new file mode 100644 index 000000000..209793bfd --- /dev/null +++ b/v3/examples/android/build/Taskfile.yml @@ -0,0 +1,174 @@ +version: '3' + +tasks: + go:mod:tidy: + summary: Runs `go mod tidy` + internal: true + cmds: + - go mod tidy + + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + label: build:frontend (PRODUCTION={{.PRODUCTION}}) + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/**/* + deps: + - task: install:frontend:deps + - task: generate:bindings + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + cmds: + - npm run {{.BUILD_COMMAND}} -q + env: + PRODUCTION: '{{.PRODUCTION | default "false"}}' + vars: + BUILD_COMMAND: '{{if eq .PRODUCTION "true"}}build{{else}}build:dev{{end}}' + + + frontend:vendor:puppertino: + summary: Fetches Puppertino CSS into frontend/public for consistent mobile styling + sources: + - frontend/public/puppertino/puppertino.css + generates: + - frontend/public/puppertino/puppertino.css + cmds: + - | + set -euo pipefail + mkdir -p frontend/public/puppertino + # Fetch Puppertino full.css and LICENSE from GitHub main branch + curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/dist/css/full.css -o frontend/public/puppertino/puppertino.css + curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/LICENSE -o frontend/public/puppertino/LICENSE + echo "Puppertino CSS updated at frontend/public/puppertino/puppertino.css" + # Ensure index.html includes Puppertino CSS and button classes + INDEX_HTML=frontend/index.html + if [ -f "$INDEX_HTML" ]; then + if ! grep -q 'href="/puppertino/puppertino.css"' "$INDEX_HTML"; then + # Insert Puppertino link tag after style.css link + awk ' + /href="\/style.css"\/?/ && !x { print; print " "; x=1; next }1 + ' "$INDEX_HTML" > "$INDEX_HTML.tmp" && mv "$INDEX_HTML.tmp" "$INDEX_HTML" + fi + # Replace default .btn with Puppertino primary button classes if present + sed -E -i'' 's/class=\"btn\"/class=\"p-btn p-prim-col\"/g' "$INDEX_HTML" || true + fi + + generate:bindings: + label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}}) + summary: Generates bindings for the frontend + deps: + - task: go:mod:tidy + sources: + - "**/*.[jt]s" + - exclude: frontend/**/* + - frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output + - "**/*.go" + - go.mod + - go.sum + generates: + - frontend/bindings/**/* + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + sources: + - "appicon.png" + generates: + - "darwin/icons.icns" + - "windows/icon.ico" + cmds: + - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico + + dev:frontend: + summary: Runs the frontend in development mode + dir: frontend + deps: + - task: install:frontend:deps + cmds: + - npm run dev -- --port {{.VITE_PORT}} --strictPort + + update:build-assets: + summary: Updates the build assets + dir: build + cmds: + - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir . + + + ios:device:list: + summary: Lists connected iOS devices (UDIDs) + cmds: + - xcrun xcdevice list + + ios:run:device: + summary: Build, install, and launch on a physical iPhone using Apple tools (xcodebuild/devicectl) + vars: + PROJECT: '{{.PROJECT}}' # e.g., build/ios/xcode/.xcodeproj + SCHEME: '{{.SCHEME}}' # e.g., ios.dev + CONFIG: '{{.CONFIG | default "Debug"}}' + DERIVED: '{{.DERIVED | default "build/ios/DerivedData"}}' + UDID: '{{.UDID}}' # from `task ios:device:list` + BUNDLE_ID: '{{.BUNDLE_ID}}' # e.g., com.yourco.wails.ios.dev + TEAM_ID: '{{.TEAM_ID}}' # optional, if your project is not already set up for signing + preconditions: + - sh: xcrun -f xcodebuild + msg: "xcodebuild not found. Please install Xcode." + - sh: xcrun -f devicectl + msg: "devicectl not found. Please update to Xcode 15+ (which includes devicectl)." + - sh: test -n "{{.PROJECT}}" + msg: "Set PROJECT to your .xcodeproj path (e.g., PROJECT=build/ios/xcode/App.xcodeproj)." + - sh: test -n "{{.SCHEME}}" + msg: "Set SCHEME to your app scheme (e.g., SCHEME=ios.dev)." + - sh: test -n "{{.UDID}}" + msg: "Set UDID to your device UDID (see: task ios:device:list)." + - sh: test -n "{{.BUNDLE_ID}}" + msg: "Set BUNDLE_ID to your app's bundle identifier (e.g., com.yourco.wails.ios.dev)." + cmds: + - | + set -euo pipefail + echo "Building for device: UDID={{.UDID}} SCHEME={{.SCHEME}} PROJECT={{.PROJECT}}" + XCB_ARGS=( + -project "{{.PROJECT}}" + -scheme "{{.SCHEME}}" + -configuration "{{.CONFIG}}" + -destination "id={{.UDID}}" + -derivedDataPath "{{.DERIVED}}" + -allowProvisioningUpdates + -allowProvisioningDeviceRegistration + ) + # Optionally inject signing identifiers if provided + if [ -n "{{.TEAM_ID}}" ]; then XCB_ARGS+=(DEVELOPMENT_TEAM={{.TEAM_ID}}); fi + if [ -n "{{.BUNDLE_ID}}" ]; then XCB_ARGS+=(PRODUCT_BUNDLE_IDENTIFIER={{.BUNDLE_ID}}); fi + xcodebuild "${XCB_ARGS[@]}" build | xcpretty || true + # If xcpretty isn't installed, run without it + if [ "${PIPESTATUS[0]}" -ne 0 ]; then + xcodebuild "${XCB_ARGS[@]}" build + fi + # Find built .app + APP_PATH=$(find "{{.DERIVED}}/Build/Products" -type d -name "*.app" -maxdepth 3 | head -n 1) + if [ -z "$APP_PATH" ]; then + echo "Could not locate built .app under {{.DERIVED}}/Build/Products" >&2 + exit 1 + fi + echo "Installing: $APP_PATH" + xcrun devicectl device install app --device "{{.UDID}}" "$APP_PATH" + echo "Launching: {{.BUNDLE_ID}}" + xcrun devicectl device process launch --device "{{.UDID}}" --stderr console --stdout console "{{.BUNDLE_ID}}" diff --git a/v3/examples/android/build/android/Taskfile.yml b/v3/examples/android/build/android/Taskfile.yml new file mode 100644 index 000000000..5005f9f4e --- /dev/null +++ b/v3/examples/android/build/android/Taskfile.yml @@ -0,0 +1,237 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +vars: + APP_ID: '{{.APP_ID | default "com.wails.app"}}' + MIN_SDK: '21' + TARGET_SDK: '34' + NDK_VERSION: 'r26d' + +tasks: + install:deps: + summary: Check and install Android development dependencies + cmds: + - go run build/android/scripts/deps/install_deps.go + env: + TASK_FORCE_YES: '{{if .YES}}true{{else}}false{{end}}' + prompt: This will check and install Android development dependencies. Continue? + + build: + summary: Creates a build of the application for Android + deps: + - task: common:go:mod:tidy + - task: generate:android:bindings + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - echo "Building Android app {{.APP_NAME}}..." + - task: compile:go:shared + vars: + ARCH: '{{.ARCH | default "arm64"}}' + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production,android -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags android,debug -buildvcs=false -gcflags=all="-l"{{end}}' + env: + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + compile:go:shared: + summary: Compile Go code to shared library (.so) + cmds: + - | + NDK_ROOT="${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk/{{.NDK_VERSION}}}" + if [ ! -d "$NDK_ROOT" ]; then + echo "Error: Android NDK not found at $NDK_ROOT" + echo "Please set ANDROID_NDK_HOME or install NDK {{.NDK_VERSION}} via Android Studio" + exit 1 + fi + + # Determine toolchain based on host OS + case "$(uname -s)" in + Darwin) HOST_TAG="darwin-x86_64" ;; + Linux) HOST_TAG="linux-x86_64" ;; + *) echo "Unsupported host OS"; exit 1 ;; + esac + + TOOLCHAIN="$NDK_ROOT/toolchains/llvm/prebuilt/$HOST_TAG" + + # Set compiler based on architecture + case "{{.ARCH}}" in + arm64) + export CC="$TOOLCHAIN/bin/aarch64-linux-android{{.MIN_SDK}}-clang" + export CXX="$TOOLCHAIN/bin/aarch64-linux-android{{.MIN_SDK}}-clang++" + export GOARCH=arm64 + JNI_DIR="arm64-v8a" + ;; + amd64|x86_64) + export CC="$TOOLCHAIN/bin/x86_64-linux-android{{.MIN_SDK}}-clang" + export CXX="$TOOLCHAIN/bin/x86_64-linux-android{{.MIN_SDK}}-clang++" + export GOARCH=amd64 + JNI_DIR="x86_64" + ;; + *) + echo "Unsupported architecture: {{.ARCH}}" + exit 1 + ;; + esac + + export CGO_ENABLED=1 + export GOOS=android + + mkdir -p {{.BIN_DIR}} + mkdir -p build/android/app/src/main/jniLibs/$JNI_DIR + + go build -buildmode=c-shared {{.BUILD_FLAGS}} \ + -o build/android/app/src/main/jniLibs/$JNI_DIR/libwails.so + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production,android -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags android,debug -buildvcs=false -gcflags=all="-l"{{end}}' + + compile:go:all-archs: + summary: Compile Go code for all Android architectures (fat APK) + cmds: + - task: compile:go:shared + vars: + ARCH: arm64 + - task: compile:go:shared + vars: + ARCH: amd64 + + package: + summary: Packages a production build of the application into an APK + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: assemble:apk + + package:fat: + summary: Packages a production build for all architectures (fat APK) + cmds: + - task: compile:go:all-archs + - task: assemble:apk + + assemble:apk: + summary: Assembles the APK using Gradle + cmds: + - | + cd build/android + ./gradlew assembleDebug + cp app/build/outputs/apk/debug/app-debug.apk ../../{{.BIN_DIR}}/{{.APP_NAME}}.apk + echo "APK created: {{.BIN_DIR}}/{{.APP_NAME}}.apk" + + assemble:apk:release: + summary: Assembles a release APK using Gradle + cmds: + - | + cd build/android + ./gradlew assembleRelease + cp app/build/outputs/apk/release/app-release-unsigned.apk ../../{{.BIN_DIR}}/{{.APP_NAME}}-release.apk + echo "Release APK created: {{.BIN_DIR}}/{{.APP_NAME}}-release.apk" + + generate:android:bindings: + internal: true + summary: Generates bindings for Android + sources: + - "**/*.go" + - go.mod + - go.sum + generates: + - frontend/bindings/**/* + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true + env: + GOOS: android + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default "arm64"}}' + + ensure-emulator: + internal: true + summary: Ensure Android Emulator is running + silent: true + cmds: + - | + # Check if an emulator is already running + if adb devices | grep -q "emulator"; then + echo "Emulator already running" + exit 0 + fi + + # Get first available AVD + AVD_NAME=$(emulator -list-avds | head -1) + if [ -z "$AVD_NAME" ]; then + echo "No Android Virtual Devices found." + echo "Create one using: Android Studio > Tools > Device Manager" + exit 1 + fi + + echo "Starting emulator: $AVD_NAME" + emulator -avd "$AVD_NAME" -no-snapshot-load & + + # Wait for emulator to boot (max 60 seconds) + echo "Waiting for emulator to boot..." + adb wait-for-device + + for i in {1..60}; do + BOOT_COMPLETED=$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d '\r') + if [ "$BOOT_COMPLETED" = "1" ]; then + echo "Emulator booted successfully" + exit 0 + fi + sleep 1 + done + + echo "Emulator boot timeout" + exit 1 + preconditions: + - sh: command -v adb + msg: "adb not found. Please install Android SDK and add platform-tools to PATH" + - sh: command -v emulator + msg: "emulator not found. Please install Android SDK and add emulator to PATH" + + deploy-emulator: + summary: Deploy to Android Emulator + deps: [package] + cmds: + - adb uninstall {{.APP_ID}} 2>/dev/null || true + - adb install {{.BIN_DIR}}/{{.APP_NAME}}.apk + - adb shell am start -n {{.APP_ID}}/.MainActivity + + run: + summary: Run the application in Android Emulator + deps: + - task: ensure-emulator + - task: build + vars: + ARCH: x86_64 + cmds: + - task: assemble:apk + - adb uninstall {{.APP_ID}} 2>/dev/null || true + - adb install {{.BIN_DIR}}/{{.APP_NAME}}.apk + - adb shell am start -n {{.APP_ID}}/.MainActivity + + logs: + summary: Stream Android logcat filtered to this app + cmds: + - adb logcat -v time | grep -E "(Wails|{{.APP_NAME}})" + + logs:all: + summary: Stream all Android logcat (verbose) + cmds: + - adb logcat -v time + + clean: + summary: Clean build artifacts + cmds: + - rm -rf {{.BIN_DIR}} + - rm -rf build/android/app/build + - rm -rf build/android/app/src/main/jniLibs/*/libwails.so + - rm -rf build/android/.gradle diff --git a/v3/examples/android/build/android/app/build.gradle b/v3/examples/android/build/android/app/build.gradle new file mode 100644 index 000000000..78fdbf7d9 --- /dev/null +++ b/v3/examples/android/build/android/app/build.gradle @@ -0,0 +1,63 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'com.wails.app' + compileSdk 34 + + buildFeatures { + buildConfig = true + } + + defaultConfig { + applicationId "com.wails.app" + minSdk 21 + targetSdk 34 + versionCode 1 + versionName "1.0" + + // Configure supported ABIs + ndk { + abiFilters 'arm64-v8a', 'x86_64' + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + debug { + debuggable true + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + // Source sets configuration + sourceSets { + main { + // JNI libraries are in jniLibs folder + jniLibs.srcDirs = ['src/main/jniLibs'] + // Assets for the WebView + assets.srcDirs = ['src/main/assets'] + } + } + + // Packaging options + packagingOptions { + // Don't strip Go symbols in debug builds + doNotStrip '*/arm64-v8a/libwails.so' + doNotStrip '*/x86_64/libwails.so' + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.webkit:webkit:1.9.0' + implementation 'com.google.android.material:material:1.11.0' +} diff --git a/v3/examples/android/build/android/app/proguard-rules.pro b/v3/examples/android/build/android/app/proguard-rules.pro new file mode 100644 index 000000000..8b88c3dfd --- /dev/null +++ b/v3/examples/android/build/android/app/proguard-rules.pro @@ -0,0 +1,12 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. + +# Keep native methods +-keepclasseswithmembernames class * { + native ; +} + +# Keep Wails bridge classes +-keep class com.wails.app.WailsBridge { *; } +-keep class com.wails.app.WailsJSBridge { *; } diff --git a/v3/examples/android/build/android/app/src/main/AndroidManifest.xml b/v3/examples/android/build/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..6c7982af1 --- /dev/null +++ b/v3/examples/android/build/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + diff --git a/v3/examples/android/build/android/app/src/main/java/com/wails/app/MainActivity.java b/v3/examples/android/build/android/app/src/main/java/com/wails/app/MainActivity.java new file mode 100644 index 000000000..3067fee09 --- /dev/null +++ b/v3/examples/android/build/android/app/src/main/java/com/wails/app/MainActivity.java @@ -0,0 +1,198 @@ +package com.wails.app; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.util.Log; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.webkit.WebViewAssetLoader; +import com.wails.app.BuildConfig; + +/** + * MainActivity hosts the WebView and manages the Wails application lifecycle. + * It uses WebViewAssetLoader to serve assets from the Go library without + * requiring a network server. + */ +public class MainActivity extends AppCompatActivity { + private static final String TAG = "WailsActivity"; + private static final String WAILS_SCHEME = "https"; + private static final String WAILS_HOST = "wails.localhost"; + + private WebView webView; + private WailsBridge bridge; + private WebViewAssetLoader assetLoader; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // Initialize the native Go library + bridge = new WailsBridge(this); + bridge.initialize(); + + // Set up WebView + setupWebView(); + + // Load the application + loadApplication(); + } + + @SuppressLint("SetJavaScriptEnabled") + private void setupWebView() { + webView = findViewById(R.id.webview); + + // Configure WebView settings + WebSettings settings = webView.getSettings(); + settings.setJavaScriptEnabled(true); + settings.setDomStorageEnabled(true); + settings.setDatabaseEnabled(true); + settings.setAllowFileAccess(false); + settings.setAllowContentAccess(false); + settings.setMediaPlaybackRequiresUserGesture(false); + settings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW); + + // Enable debugging in debug builds + if (BuildConfig.DEBUG) { + WebView.setWebContentsDebuggingEnabled(true); + } + + // Set up asset loader for serving local assets + assetLoader = new WebViewAssetLoader.Builder() + .setDomain(WAILS_HOST) + .addPathHandler("/", new WailsPathHandler(bridge)) + .build(); + + // Set up WebView client to intercept requests + webView.setWebViewClient(new WebViewClient() { + @Nullable + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + String url = request.getUrl().toString(); + Log.d(TAG, "Intercepting request: " + url); + + // Handle wails.localhost requests + if (request.getUrl().getHost() != null && + request.getUrl().getHost().equals(WAILS_HOST)) { + + // For wails API calls (runtime, capabilities, etc.), we need to pass the full URL + // including query string because WebViewAssetLoader.PathHandler strips query params + String path = request.getUrl().getPath(); + if (path != null && path.startsWith("/wails/")) { + // Get full path with query string for runtime calls + String fullPath = path; + String query = request.getUrl().getQuery(); + if (query != null && !query.isEmpty()) { + fullPath = path + "?" + query; + } + Log.d(TAG, "Wails API call detected, full path: " + fullPath); + + // Call bridge directly with full path + byte[] data = bridge.serveAsset(fullPath, request.getMethod(), "{}"); + if (data != null && data.length > 0) { + java.io.InputStream inputStream = new java.io.ByteArrayInputStream(data); + java.util.Map headers = new java.util.HashMap<>(); + headers.put("Access-Control-Allow-Origin", "*"); + headers.put("Cache-Control", "no-cache"); + headers.put("Content-Type", "application/json"); + + return new WebResourceResponse( + "application/json", + "UTF-8", + 200, + "OK", + headers, + inputStream + ); + } + // Return error response if data is null + return new WebResourceResponse( + "application/json", + "UTF-8", + 500, + "Internal Error", + new java.util.HashMap<>(), + new java.io.ByteArrayInputStream("{}".getBytes()) + ); + } + + // For regular assets, use the asset loader + return assetLoader.shouldInterceptRequest(request.getUrl()); + } + + return super.shouldInterceptRequest(view, request); + } + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + Log.d(TAG, "Page loaded: " + url); + // Inject Wails runtime + bridge.injectRuntime(webView, url); + } + }); + + // Add JavaScript interface for Go communication + webView.addJavascriptInterface(new WailsJSBridge(bridge, webView), "wails"); + } + + private void loadApplication() { + // Load the main page from the asset server + String url = WAILS_SCHEME + "://" + WAILS_HOST + "/"; + Log.d(TAG, "Loading URL: " + url); + webView.loadUrl(url); + } + + /** + * Execute JavaScript in the WebView from the Go side + */ + public void executeJavaScript(final String js) { + runOnUiThread(() -> { + if (webView != null) { + webView.evaluateJavascript(js, null); + } + }); + } + + @Override + protected void onResume() { + super.onResume(); + if (bridge != null) { + bridge.onResume(); + } + } + + @Override + protected void onPause() { + super.onPause(); + if (bridge != null) { + bridge.onPause(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (bridge != null) { + bridge.shutdown(); + } + if (webView != null) { + webView.destroy(); + } + } + + @Override + public void onBackPressed() { + if (webView != null && webView.canGoBack()) { + webView.goBack(); + } else { + super.onBackPressed(); + } + } +} diff --git a/v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsBridge.java b/v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsBridge.java new file mode 100644 index 000000000..3dab65247 --- /dev/null +++ b/v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsBridge.java @@ -0,0 +1,214 @@ +package com.wails.app; + +import android.content.Context; +import android.util.Log; +import android.webkit.WebView; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * WailsBridge manages the connection between the Java/Android side and the Go native library. + * It handles: + * - Loading and initializing the native Go library + * - Serving asset requests from Go + * - Passing messages between JavaScript and Go + * - Managing callbacks for async operations + */ +public class WailsBridge { + private static final String TAG = "WailsBridge"; + + static { + // Load the native Go library + System.loadLibrary("wails"); + } + + private final Context context; + private final AtomicInteger callbackIdGenerator = new AtomicInteger(0); + private final ConcurrentHashMap pendingAssetCallbacks = new ConcurrentHashMap<>(); + private final ConcurrentHashMap pendingMessageCallbacks = new ConcurrentHashMap<>(); + private WebView webView; + private volatile boolean initialized = false; + + // Native methods - implemented in Go + private static native void nativeInit(WailsBridge bridge); + private static native void nativeShutdown(); + private static native void nativeOnResume(); + private static native void nativeOnPause(); + private static native void nativeOnPageFinished(String url); + private static native byte[] nativeServeAsset(String path, String method, String headers); + private static native String nativeHandleMessage(String message); + private static native String nativeGetAssetMimeType(String path); + + public WailsBridge(Context context) { + this.context = context; + } + + /** + * Initialize the native Go library + */ + public void initialize() { + if (initialized) { + return; + } + + Log.i(TAG, "Initializing Wails bridge..."); + try { + nativeInit(this); + initialized = true; + Log.i(TAG, "Wails bridge initialized successfully"); + } catch (Exception e) { + Log.e(TAG, "Failed to initialize Wails bridge", e); + } + } + + /** + * Shutdown the native Go library + */ + public void shutdown() { + if (!initialized) { + return; + } + + Log.i(TAG, "Shutting down Wails bridge..."); + try { + nativeShutdown(); + initialized = false; + } catch (Exception e) { + Log.e(TAG, "Error during shutdown", e); + } + } + + /** + * Called when the activity resumes + */ + public void onResume() { + if (initialized) { + nativeOnResume(); + } + } + + /** + * Called when the activity pauses + */ + public void onPause() { + if (initialized) { + nativeOnPause(); + } + } + + /** + * Serve an asset from the Go asset server + * @param path The URL path requested + * @param method The HTTP method + * @param headers The request headers as JSON + * @return The asset data, or null if not found + */ + public byte[] serveAsset(String path, String method, String headers) { + if (!initialized) { + Log.w(TAG, "Bridge not initialized, cannot serve asset: " + path); + return null; + } + + Log.d(TAG, "Serving asset: " + path); + try { + return nativeServeAsset(path, method, headers); + } catch (Exception e) { + Log.e(TAG, "Error serving asset: " + path, e); + return null; + } + } + + /** + * Get the MIME type for an asset + * @param path The asset path + * @return The MIME type string + */ + public String getAssetMimeType(String path) { + if (!initialized) { + return "application/octet-stream"; + } + + try { + String mimeType = nativeGetAssetMimeType(path); + return mimeType != null ? mimeType : "application/octet-stream"; + } catch (Exception e) { + Log.e(TAG, "Error getting MIME type for: " + path, e); + return "application/octet-stream"; + } + } + + /** + * Handle a message from JavaScript + * @param message The message from JavaScript (JSON) + * @return The response to send back to JavaScript (JSON) + */ + public String handleMessage(String message) { + if (!initialized) { + Log.w(TAG, "Bridge not initialized, cannot handle message"); + return "{\"error\":\"Bridge not initialized\"}"; + } + + Log.d(TAG, "Handling message from JS: " + message); + try { + return nativeHandleMessage(message); + } catch (Exception e) { + Log.e(TAG, "Error handling message", e); + return "{\"error\":\"" + e.getMessage() + "\"}"; + } + } + + /** + * Inject the Wails runtime JavaScript into the WebView. + * Called when the page finishes loading. + * @param webView The WebView to inject into + * @param url The URL that finished loading + */ + public void injectRuntime(WebView webView, String url) { + this.webView = webView; + // Notify Go side that page has finished loading so it can inject the runtime + Log.d(TAG, "Page finished loading: " + url + ", notifying Go side"); + if (initialized) { + nativeOnPageFinished(url); + } + } + + /** + * Execute JavaScript in the WebView (called from Go side) + * @param js The JavaScript code to execute + */ + public void executeJavaScript(String js) { + if (webView != null) { + webView.post(() -> webView.evaluateJavascript(js, null)); + } + } + + /** + * Called from Go when an event needs to be emitted to JavaScript + * @param eventName The event name + * @param eventData The event data (JSON) + */ + public void emitEvent(String eventName, String eventData) { + String js = String.format("window.wails && window.wails._emit('%s', %s);", + escapeJsString(eventName), eventData); + executeJavaScript(js); + } + + private String escapeJsString(String str) { + return str.replace("\\", "\\\\") + .replace("'", "\\'") + .replace("\n", "\\n") + .replace("\r", "\\r"); + } + + // Callback interfaces + public interface AssetCallback { + void onAssetReady(byte[] data, String mimeType); + void onAssetError(String error); + } + + public interface MessageCallback { + void onResponse(String response); + void onError(String error); + } +} diff --git a/v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java b/v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java new file mode 100644 index 000000000..98ae5b247 --- /dev/null +++ b/v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java @@ -0,0 +1,142 @@ +package com.wails.app; + +import android.util.Log; +import android.webkit.JavascriptInterface; +import android.webkit.WebView; +import com.wails.app.BuildConfig; + +/** + * WailsJSBridge provides the JavaScript interface that allows the web frontend + * to communicate with the Go backend. This is exposed to JavaScript as the + * `window.wails` object. + * + * Similar to iOS's WKScriptMessageHandler but using Android's addJavascriptInterface. + */ +public class WailsJSBridge { + private static final String TAG = "WailsJSBridge"; + + private final WailsBridge bridge; + private final WebView webView; + + public WailsJSBridge(WailsBridge bridge, WebView webView) { + this.bridge = bridge; + this.webView = webView; + } + + /** + * Send a message to Go and return the response synchronously. + * Called from JavaScript: wails.invoke(message) + * + * @param message The message to send (JSON string) + * @return The response from Go (JSON string) + */ + @JavascriptInterface + public String invoke(String message) { + Log.d(TAG, "Invoke called: " + message); + return bridge.handleMessage(message); + } + + /** + * Send a message to Go asynchronously. + * The response will be sent back via a callback. + * Called from JavaScript: wails.invokeAsync(callbackId, message) + * + * @param callbackId The callback ID to use for the response + * @param message The message to send (JSON string) + */ + @JavascriptInterface + public void invokeAsync(final String callbackId, final String message) { + Log.d(TAG, "InvokeAsync called: " + message); + + // Handle in background thread to not block JavaScript + new Thread(() -> { + try { + String response = bridge.handleMessage(message); + sendCallback(callbackId, response, null); + } catch (Exception e) { + Log.e(TAG, "Error in async invoke", e); + sendCallback(callbackId, null, e.getMessage()); + } + }).start(); + } + + /** + * Log a message from JavaScript to Android's logcat + * Called from JavaScript: wails.log(level, message) + * + * @param level The log level (debug, info, warn, error) + * @param message The message to log + */ + @JavascriptInterface + public void log(String level, String message) { + switch (level.toLowerCase()) { + case "debug": + Log.d(TAG + "/JS", message); + break; + case "info": + Log.i(TAG + "/JS", message); + break; + case "warn": + Log.w(TAG + "/JS", message); + break; + case "error": + Log.e(TAG + "/JS", message); + break; + default: + Log.v(TAG + "/JS", message); + break; + } + } + + /** + * Get the platform name + * Called from JavaScript: wails.platform() + * + * @return "android" + */ + @JavascriptInterface + public String platform() { + return "android"; + } + + /** + * Check if we're running in debug mode + * Called from JavaScript: wails.isDebug() + * + * @return true if debug build, false otherwise + */ + @JavascriptInterface + public boolean isDebug() { + return BuildConfig.DEBUG; + } + + /** + * Send a callback response to JavaScript + */ + private void sendCallback(String callbackId, String result, String error) { + final String js; + if (error != null) { + js = String.format( + "window.wails && window.wails._callback('%s', null, '%s');", + escapeJsString(callbackId), + escapeJsString(error) + ); + } else { + js = String.format( + "window.wails && window.wails._callback('%s', %s, null);", + escapeJsString(callbackId), + result != null ? result : "null" + ); + } + + webView.post(() -> webView.evaluateJavascript(js, null)); + } + + private String escapeJsString(String str) { + if (str == null) return ""; + return str.replace("\\", "\\\\") + .replace("'", "\\'") + .replace("\n", "\\n") + .replace("\r", "\\r"); + } +} diff --git a/v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsPathHandler.java b/v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsPathHandler.java new file mode 100644 index 000000000..326fa9b4d --- /dev/null +++ b/v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsPathHandler.java @@ -0,0 +1,118 @@ +package com.wails.app; + +import android.net.Uri; +import android.util.Log; +import android.webkit.WebResourceResponse; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.webkit.WebViewAssetLoader; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * WailsPathHandler implements WebViewAssetLoader.PathHandler to serve assets + * from the Go asset server. This allows the WebView to load assets without + * using a network server, similar to iOS's WKURLSchemeHandler. + */ +public class WailsPathHandler implements WebViewAssetLoader.PathHandler { + private static final String TAG = "WailsPathHandler"; + + private final WailsBridge bridge; + + public WailsPathHandler(WailsBridge bridge) { + this.bridge = bridge; + } + + @Nullable + @Override + public WebResourceResponse handle(@NonNull String path) { + Log.d(TAG, "Handling path: " + path); + + // Normalize path + if (path.isEmpty() || path.equals("/")) { + path = "/index.html"; + } + + // Get asset from Go + byte[] data = bridge.serveAsset(path, "GET", "{}"); + + if (data == null || data.length == 0) { + Log.w(TAG, "Asset not found: " + path); + return null; // Return null to let WebView handle 404 + } + + // Determine MIME type + String mimeType = bridge.getAssetMimeType(path); + Log.d(TAG, "Serving " + path + " with type " + mimeType + " (" + data.length + " bytes)"); + + // Create response + InputStream inputStream = new ByteArrayInputStream(data); + Map headers = new HashMap<>(); + headers.put("Access-Control-Allow-Origin", "*"); + headers.put("Cache-Control", "no-cache"); + + return new WebResourceResponse( + mimeType, + "UTF-8", + 200, + "OK", + headers, + inputStream + ); + } + + /** + * Determine MIME type from file extension + */ + private String getMimeType(String path) { + String lowerPath = path.toLowerCase(); + + if (lowerPath.endsWith(".html") || lowerPath.endsWith(".htm")) { + return "text/html"; + } else if (lowerPath.endsWith(".js") || lowerPath.endsWith(".mjs")) { + return "application/javascript"; + } else if (lowerPath.endsWith(".css")) { + return "text/css"; + } else if (lowerPath.endsWith(".json")) { + return "application/json"; + } else if (lowerPath.endsWith(".png")) { + return "image/png"; + } else if (lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg")) { + return "image/jpeg"; + } else if (lowerPath.endsWith(".gif")) { + return "image/gif"; + } else if (lowerPath.endsWith(".svg")) { + return "image/svg+xml"; + } else if (lowerPath.endsWith(".ico")) { + return "image/x-icon"; + } else if (lowerPath.endsWith(".woff")) { + return "font/woff"; + } else if (lowerPath.endsWith(".woff2")) { + return "font/woff2"; + } else if (lowerPath.endsWith(".ttf")) { + return "font/ttf"; + } else if (lowerPath.endsWith(".eot")) { + return "application/vnd.ms-fontobject"; + } else if (lowerPath.endsWith(".xml")) { + return "application/xml"; + } else if (lowerPath.endsWith(".txt")) { + return "text/plain"; + } else if (lowerPath.endsWith(".wasm")) { + return "application/wasm"; + } else if (lowerPath.endsWith(".mp3")) { + return "audio/mpeg"; + } else if (lowerPath.endsWith(".mp4")) { + return "video/mp4"; + } else if (lowerPath.endsWith(".webm")) { + return "video/webm"; + } else if (lowerPath.endsWith(".webp")) { + return "image/webp"; + } + + return "application/octet-stream"; + } +} diff --git a/v3/examples/android/build/android/app/src/main/res/layout/activity_main.xml b/v3/examples/android/build/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..f278384c7 --- /dev/null +++ b/v3/examples/android/build/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/v3/examples/android/build/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/v3/examples/android/build/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..9409abebe Binary files /dev/null and b/v3/examples/android/build/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/v3/examples/android/build/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/v3/examples/android/build/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..9409abebe Binary files /dev/null and b/v3/examples/android/build/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/v3/examples/android/build/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/v3/examples/android/build/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..5b6acc048 Binary files /dev/null and b/v3/examples/android/build/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/v3/examples/android/build/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/v3/examples/android/build/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..5b6acc048 Binary files /dev/null and b/v3/examples/android/build/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/v3/examples/android/build/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/v3/examples/android/build/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..1c2c66452 Binary files /dev/null and b/v3/examples/android/build/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/v3/examples/android/build/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/v3/examples/android/build/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..1c2c66452 Binary files /dev/null and b/v3/examples/android/build/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/v3/examples/android/build/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/v3/examples/android/build/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..be557d897 Binary files /dev/null and b/v3/examples/android/build/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/v3/examples/android/build/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/v3/examples/android/build/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..be557d897 Binary files /dev/null and b/v3/examples/android/build/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/v3/examples/android/build/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/v3/examples/android/build/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4507f32a5 Binary files /dev/null and b/v3/examples/android/build/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/v3/examples/android/build/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/v3/examples/android/build/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..4507f32a5 Binary files /dev/null and b/v3/examples/android/build/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/v3/examples/android/build/android/app/src/main/res/values/colors.xml b/v3/examples/android/build/android/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..dd33f3b7d --- /dev/null +++ b/v3/examples/android/build/android/app/src/main/res/values/colors.xml @@ -0,0 +1,8 @@ + + + #3574D4 + #2C5FB8 + #1B2636 + #FFFFFFFF + #FF000000 + diff --git a/v3/examples/android/build/android/app/src/main/res/values/strings.xml b/v3/examples/android/build/android/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..3ed9e4717 --- /dev/null +++ b/v3/examples/android/build/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Wails App + diff --git a/v3/examples/android/build/android/app/src/main/res/values/themes.xml b/v3/examples/android/build/android/app/src/main/res/values/themes.xml new file mode 100644 index 000000000..be8a282b2 --- /dev/null +++ b/v3/examples/android/build/android/app/src/main/res/values/themes.xml @@ -0,0 +1,14 @@ + + + + diff --git a/v3/examples/android/build/android/build.gradle b/v3/examples/android/build/android/build.gradle new file mode 100644 index 000000000..d7fbab39a --- /dev/null +++ b/v3/examples/android/build/android/build.gradle @@ -0,0 +1,4 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id 'com.android.application' version '8.7.3' apply false +} diff --git a/v3/examples/android/build/android/build/reports/problems/problems-report.html b/v3/examples/android/build/android/build/reports/problems/problems-report.html new file mode 100644 index 000000000..2f0196fac --- /dev/null +++ b/v3/examples/android/build/android/build/reports/problems/problems-report.html @@ -0,0 +1,659 @@ + + + + + + + + + + + + + Gradle Configuration Cache + + + +
+ +
+ Loading... +
+ + + + + + diff --git a/v3/examples/android/build/android/gradle.properties b/v3/examples/android/build/android/gradle.properties new file mode 100644 index 000000000..b9d4426d5 --- /dev/null +++ b/v3/examples/android/build/android/gradle.properties @@ -0,0 +1,26 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/build/optimize-your-build#parallel +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true + +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true diff --git a/v3/examples/android/build/android/gradle/wrapper/gradle-wrapper.jar b/v3/examples/android/build/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..f8e1ee312 Binary files /dev/null and b/v3/examples/android/build/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/v3/examples/android/build/android/gradle/wrapper/gradle-wrapper.properties b/v3/examples/android/build/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..23449a2b5 --- /dev/null +++ b/v3/examples/android/build/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/v3/examples/android/build/android/gradlew b/v3/examples/android/build/android/gradlew new file mode 100755 index 000000000..adff685a0 --- /dev/null +++ b/v3/examples/android/build/android/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/v3/examples/android/build/android/gradlew.bat b/v3/examples/android/build/android/gradlew.bat new file mode 100644 index 000000000..e509b2dd8 --- /dev/null +++ b/v3/examples/android/build/android/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/v3/examples/android/build/android/scripts/deps/install_deps.go b/v3/examples/android/build/android/scripts/deps/install_deps.go new file mode 100644 index 000000000..d9dfedf80 --- /dev/null +++ b/v3/examples/android/build/android/scripts/deps/install_deps.go @@ -0,0 +1,151 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +func main() { + fmt.Println("Checking Android development dependencies...") + fmt.Println() + + errors := []string{} + + // Check Go + if !checkCommand("go", "version") { + errors = append(errors, "Go is not installed. Install from https://go.dev/dl/") + } else { + fmt.Println("✓ Go is installed") + } + + // Check ANDROID_HOME + androidHome := os.Getenv("ANDROID_HOME") + if androidHome == "" { + androidHome = os.Getenv("ANDROID_SDK_ROOT") + } + if androidHome == "" { + // Try common default locations + home, _ := os.UserHomeDir() + possiblePaths := []string{ + filepath.Join(home, "Android", "Sdk"), + filepath.Join(home, "Library", "Android", "sdk"), + "/usr/local/share/android-sdk", + } + for _, p := range possiblePaths { + if _, err := os.Stat(p); err == nil { + androidHome = p + break + } + } + } + + if androidHome == "" { + errors = append(errors, "ANDROID_HOME not set. Install Android Studio and set ANDROID_HOME environment variable") + } else { + fmt.Printf("✓ ANDROID_HOME: %s\n", androidHome) + } + + // Check adb + if !checkCommand("adb", "version") { + if androidHome != "" { + platformTools := filepath.Join(androidHome, "platform-tools") + errors = append(errors, fmt.Sprintf("adb not found. Add %s to PATH", platformTools)) + } else { + errors = append(errors, "adb not found. Install Android SDK Platform-Tools") + } + } else { + fmt.Println("✓ adb is installed") + } + + // Check emulator + if !checkCommand("emulator", "-list-avds") { + if androidHome != "" { + emulatorPath := filepath.Join(androidHome, "emulator") + errors = append(errors, fmt.Sprintf("emulator not found. Add %s to PATH", emulatorPath)) + } else { + errors = append(errors, "emulator not found. Install Android Emulator via SDK Manager") + } + } else { + fmt.Println("✓ Android Emulator is installed") + } + + // Check NDK + ndkHome := os.Getenv("ANDROID_NDK_HOME") + if ndkHome == "" && androidHome != "" { + // Look for NDK in default location + ndkDir := filepath.Join(androidHome, "ndk") + if entries, err := os.ReadDir(ndkDir); err == nil { + for _, entry := range entries { + if entry.IsDir() { + ndkHome = filepath.Join(ndkDir, entry.Name()) + break + } + } + } + } + + if ndkHome == "" { + errors = append(errors, "Android NDK not found. Install NDK via Android Studio > SDK Manager > SDK Tools > NDK (Side by side)") + } else { + fmt.Printf("✓ Android NDK: %s\n", ndkHome) + } + + // Check Java + if !checkCommand("java", "-version") { + errors = append(errors, "Java not found. Install JDK 11+ (OpenJDK recommended)") + } else { + fmt.Println("✓ Java is installed") + } + + // Check for AVD (Android Virtual Device) + if checkCommand("emulator", "-list-avds") { + cmd := exec.Command("emulator", "-list-avds") + output, err := cmd.Output() + if err == nil && len(strings.TrimSpace(string(output))) > 0 { + avds := strings.Split(strings.TrimSpace(string(output)), "\n") + fmt.Printf("✓ Found %d Android Virtual Device(s)\n", len(avds)) + } else { + fmt.Println("⚠ No Android Virtual Devices found. Create one via Android Studio > Tools > Device Manager") + } + } + + fmt.Println() + + if len(errors) > 0 { + fmt.Println("❌ Missing dependencies:") + for _, err := range errors { + fmt.Printf(" - %s\n", err) + } + fmt.Println() + fmt.Println("Setup instructions:") + fmt.Println("1. Install Android Studio: https://developer.android.com/studio") + fmt.Println("2. Open SDK Manager and install:") + fmt.Println(" - Android SDK Platform (API 34)") + fmt.Println(" - Android SDK Build-Tools") + fmt.Println(" - Android SDK Platform-Tools") + fmt.Println(" - Android Emulator") + fmt.Println(" - NDK (Side by side)") + fmt.Println("3. Set environment variables:") + if runtime.GOOS == "darwin" { + fmt.Println(" export ANDROID_HOME=$HOME/Library/Android/sdk") + } else { + fmt.Println(" export ANDROID_HOME=$HOME/Android/Sdk") + } + fmt.Println(" export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator") + fmt.Println("4. Create an AVD via Android Studio > Tools > Device Manager") + os.Exit(1) + } + + fmt.Println("✓ All Android development dependencies are installed!") +} + +func checkCommand(name string, args ...string) bool { + cmd := exec.Command(name, args...) + cmd.Stdout = nil + cmd.Stderr = nil + return cmd.Run() == nil +} diff --git a/v3/examples/android/build/android/settings.gradle b/v3/examples/android/build/android/settings.gradle new file mode 100644 index 000000000..a3f3ec3d4 --- /dev/null +++ b/v3/examples/android/build/android/settings.gradle @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "WailsApp" +include ':app' diff --git a/v3/examples/android/build/appicon.png b/v3/examples/android/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/android/build/appicon.png differ diff --git a/v3/examples/android/build/config.yml b/v3/examples/android/build/config.yml new file mode 100644 index 000000000..c8adba60d --- /dev/null +++ b/v3/examples/android/build/config.yml @@ -0,0 +1,75 @@ +# This file contains the configuration for this project. +# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets. +# Note that this will overwrite any changes you have made to the assets. +version: '3' + +# This information is used to generate the build assets. +info: + companyName: "My Company" # The name of the company + productName: "My Product" # The name of the application + productIdentifier: "com.mycompany.myproduct" # The unique product identifier + description: "A program that does X" # The application description + copyright: "(c) 2025, My Company" # Copyright text + comments: "Some Product Comments" # Comments + version: "0.0.1" # The application version + +# Android build configuration (uncomment to customise Android project generation) +# Note: Keys under `android` OVERRIDE values under `info` when set. +# android: +# # The Android application ID used in the generated project (applicationId) +# applicationId: "com.mycompany.myproduct" +# # The display name shown under the app icon +# displayName: "My Product" +# # The app version code (integer, must increment for each release) +# versionCode: 1 +# # The app version name (displayed to users) +# versionName: "0.0.1" +# # Minimum SDK version (API level) +# minSdkVersion: 21 +# # Target SDK version (API level) +# targetSdkVersion: 34 +# # The company/organisation name for templates and project settings +# company: "My Company" + +# Dev mode configuration +dev_mode: + root_path: . + log_level: warn + debounce: 1000 + ignore: + dir: + - .git + - node_modules + - frontend + - bin + file: + - .DS_Store + - .gitignore + - .gitkeep + watched_extension: + - "*.go" + git_ignore: true + executes: + - cmd: wails3 task common:install:frontend:deps + type: once + - cmd: wails3 task common:dev:frontend + type: background + - cmd: go mod tidy + type: blocking + - cmd: wails3 task build + type: blocking + - cmd: wails3 task run + type: primary + +# File Associations +# More information at: https://v3.wails.io/noit/done/yet +fileAssociations: +# - ext: wails +# name: Wails +# description: Wails Application File +# iconName: wailsFileIcon +# role: Editor + +# Other data +other: + - name: My Other Data diff --git a/v3/examples/android/build/darwin/Info.dev.plist b/v3/examples/android/build/darwin/Info.dev.plist new file mode 100644 index 000000000..bd6a537fa --- /dev/null +++ b/v3/examples/android/build/darwin/Info.dev.plist @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + ios + CFBundleIdentifier + com.wails.ios + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © now, My Company + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/examples/android/build/darwin/Info.plist b/v3/examples/android/build/darwin/Info.plist new file mode 100644 index 000000000..fb52df715 --- /dev/null +++ b/v3/examples/android/build/darwin/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + ios + CFBundleIdentifier + com.wails.ios + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © now, My Company + + \ No newline at end of file diff --git a/v3/examples/android/build/darwin/Taskfile.yml b/v3/examples/android/build/darwin/Taskfile.yml new file mode 100644 index 000000000..f0791fea9 --- /dev/null +++ b/v3/examples/android/build/darwin/Taskfile.yml @@ -0,0 +1,81 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Creates a production build of the application + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + env: + GOOS: darwin + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + CGO_CFLAGS: "-mmacosx-version-min=10.15" + CGO_LDFLAGS: "-mmacosx-version-min=10.15" + MACOSX_DEPLOYMENT_TARGET: "10.15" + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + build:universal: + summary: Builds darwin universal binary (arm64 + amd64) + deps: + - task: build + vars: + ARCH: amd64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" + - task: build + vars: + ARCH: arm64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + cmds: + - lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + - rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + + package: + summary: Packages a production build of the application into a `.app` bundle + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:app:bundle + + package:universal: + summary: Packages darwin universal binary (arm64 + amd64) + deps: + - task: build:universal + cmds: + - task: create:app:bundle + + + create:app:bundle: + summary: Creates an `.app` bundle + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS + - cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app + + run: + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS + - cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app + - '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}' diff --git a/v3/examples/android/build/darwin/icons.icns b/v3/examples/android/build/darwin/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/android/build/darwin/icons.icns differ diff --git a/v3/examples/android/build/linux/Taskfile.yml b/v3/examples/android/build/linux/Taskfile.yml new file mode 100644 index 000000000..87fd599cc --- /dev/null +++ b/v3/examples/android/build/linux/Taskfile.yml @@ -0,0 +1,119 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Linux + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: linux + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application for Linux + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:appimage + - task: create:deb + - task: create:rpm + - task: create:aur + + create:appimage: + summary: Creates an AppImage + dir: build/linux/appimage + deps: + - task: build + vars: + PRODUCTION: "true" + - task: generate:dotdesktop + cmds: + - cp {{.APP_BINARY}} {{.APP_NAME}} + - cp ../../appicon.png appicon.png + - wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build + vars: + APP_NAME: '{{.APP_NAME}}' + APP_BINARY: '../../../bin/{{.APP_NAME}}' + ICON: '../../appicon.png' + DESKTOP_FILE: '../{{.APP_NAME}}.desktop' + OUTPUT_DIR: '../../../bin' + + create:deb: + summary: Creates a deb package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:deb + + create:rpm: + summary: Creates a rpm package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:rpm + + create:aur: + summary: Creates a arch linux packager package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:aur + + generate:deb: + summary: Creates a deb package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:rpm: + summary: Creates a rpm package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:aur: + summary: Creates a arch linux packager package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:dotdesktop: + summary: Generates a `.desktop` file + dir: build + cmds: + - mkdir -p {{.ROOT_DIR}}/build/linux/appimage + - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}" + vars: + APP_NAME: '{{.APP_NAME}}' + EXEC: '{{.APP_NAME}}' + ICON: '{{.APP_NAME}}' + CATEGORIES: 'Development;' + OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}' diff --git a/v3/examples/android/build/linux/appimage/build.sh b/v3/examples/android/build/linux/appimage/build.sh new file mode 100644 index 000000000..858f091ab --- /dev/null +++ b/v3/examples/android/build/linux/appimage/build.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Copyright (c) 2018-Present Lea Anthony +# SPDX-License-Identifier: MIT + +# Fail script on any error +set -euxo pipefail + +# Define variables +APP_DIR="${APP_NAME}.AppDir" + +# Create AppDir structure +mkdir -p "${APP_DIR}/usr/bin" +cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/" +cp "${ICON_PATH}" "${APP_DIR}/" +cp "${DESKTOP_FILE}" "${APP_DIR}/" + +if [[ $(uname -m) == *x86_64* ]]; then + # Download linuxdeploy and make it executable + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + + # Run linuxdeploy to bundle the application + ./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage +else + # Download linuxdeploy and make it executable (arm64) + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage + chmod +x linuxdeploy-aarch64.AppImage + + # Run linuxdeploy to bundle the application (arm64) + ./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage +fi + +# Rename the generated AppImage +mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage" diff --git a/v3/examples/android/build/linux/desktop b/v3/examples/android/build/linux/desktop new file mode 100644 index 000000000..e9b30cf39 --- /dev/null +++ b/v3/examples/android/build/linux/desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Version=1.0 +Name=My Product +Comment=My Product Description +# The Exec line includes %u to pass the URL to the application +Exec=/usr/local/bin/ios %u +Terminal=false +Type=Application +Icon=ios +Categories=Utility; +StartupWMClass=ios diff --git a/v3/examples/android/build/linux/nfpm/nfpm.yaml b/v3/examples/android/build/linux/nfpm/nfpm.yaml new file mode 100644 index 000000000..7b78433f4 --- /dev/null +++ b/v3/examples/android/build/linux/nfpm/nfpm.yaml @@ -0,0 +1,67 @@ +# Feel free to remove those if you don't want/need to use them. +# Make sure to check the documentation at https://nfpm.goreleaser.com +# +# The lines below are called `modelines`. See `:help modeline` + +name: "ios" +arch: ${GOARCH} +platform: "linux" +version: "0.1.0" +section: "default" +priority: "extra" +maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> +description: "My Product Description" +vendor: "My Company" +homepage: "https://wails.io" +license: "MIT" +release: "1" + +contents: + - src: "./bin/ios" + dst: "/usr/local/bin/ios" + - src: "./build/appicon.png" + dst: "/usr/share/icons/hicolor/128x128/apps/ios.png" + - src: "./build/linux/ios.desktop" + dst: "/usr/share/applications/ios.desktop" + +# Default dependencies for Debian 12/Ubuntu 22.04+ with WebKit 4.1 +depends: + - libgtk-3-0 + - libwebkit2gtk-4.1-0 + +# Distribution-specific overrides for different package formats and WebKit versions +overrides: + # RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0) + rpm: + depends: + - gtk3 + - webkit2gtk4.1 + + # Arch Linux packages (WebKit 4.1) + archlinux: + depends: + - gtk3 + - webkit2gtk-4.1 + +# scripts section to ensure desktop database is updated after install +scripts: + postinstall: "./build/linux/nfpm/scripts/postinstall.sh" + # You can also add preremove, postremove if needed + # preremove: "./build/linux/nfpm/scripts/preremove.sh" + # postremove: "./build/linux/nfpm/scripts/postremove.sh" + +# replaces: +# - foobar +# provides: +# - bar +# depends: +# - gtk3 +# - libwebkit2gtk +# recommends: +# - whatever +# suggests: +# - something-else +# conflicts: +# - not-foo +# - not-bar +# changelog: "changelog.yaml" diff --git a/v3/examples/android/build/linux/nfpm/scripts/postinstall.sh b/v3/examples/android/build/linux/nfpm/scripts/postinstall.sh new file mode 100644 index 000000000..4bbb815a3 --- /dev/null +++ b/v3/examples/android/build/linux/nfpm/scripts/postinstall.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# Update desktop database for .desktop file changes +# This makes the application appear in application menus and registers its capabilities. +if command -v update-desktop-database >/dev/null 2>&1; then + echo "Updating desktop database..." + update-desktop-database -q /usr/share/applications +else + echo "Warning: update-desktop-database command not found. Desktop file may not be immediately recognized." >&2 +fi + +# Update MIME database for custom URL schemes (x-scheme-handler) +# This ensures the system knows how to handle your custom protocols. +if command -v update-mime-database >/dev/null 2>&1; then + echo "Updating MIME database..." + update-mime-database -n /usr/share/mime +else + echo "Warning: update-mime-database command not found. Custom URL schemes may not be immediately recognized." >&2 +fi + +exit 0 diff --git a/v3/examples/android/build/linux/nfpm/scripts/postremove.sh b/v3/examples/android/build/linux/nfpm/scripts/postremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/android/build/linux/nfpm/scripts/postremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/android/build/linux/nfpm/scripts/preinstall.sh b/v3/examples/android/build/linux/nfpm/scripts/preinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/android/build/linux/nfpm/scripts/preinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/android/build/linux/nfpm/scripts/preremove.sh b/v3/examples/android/build/linux/nfpm/scripts/preremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/android/build/linux/nfpm/scripts/preremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/android/build/windows/Taskfile.yml b/v3/examples/android/build/windows/Taskfile.yml new file mode 100644 index 000000000..19f137616 --- /dev/null +++ b/v3/examples/android/build/windows/Taskfile.yml @@ -0,0 +1,98 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Windows + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - task: generate:syso + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe + - cmd: powershell Remove-item *.syso + platforms: [windows] + - cmd: rm -f *.syso + platforms: [linux, darwin] + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: windows + CGO_ENABLED: 0 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application + cmds: + - |- + if [ "{{.FORMAT | default "nsis"}}" = "msix" ]; then + task: create:msix:package + else + task: create:nsis:installer + fi + vars: + FORMAT: '{{.FORMAT | default "nsis"}}' + + generate:syso: + summary: Generates Windows `.syso` file + dir: build + cmds: + - wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso + vars: + ARCH: '{{.ARCH | default ARCH}}' + + create:nsis:installer: + summary: Creates an NSIS installer + dir: build/windows/nsis + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + # Create the Microsoft WebView2 bootstrapper if it doesn't exist + - wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis" + - makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi + vars: + ARCH: '{{.ARCH | default ARCH}}' + ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}' + + create:msix:package: + summary: Creates an MSIX package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - |- + wails3 tool msix \ + --config "{{.ROOT_DIR}}/wails.json" \ + --name "{{.APP_NAME}}" \ + --executable "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" \ + --arch "{{.ARCH}}" \ + --out "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}-{{.ARCH}}.msix" \ + {{if .CERT_PATH}}--cert "{{.CERT_PATH}}"{{end}} \ + {{if .PUBLISHER}}--publisher "{{.PUBLISHER}}"{{end}} \ + {{if .USE_MSIX_TOOL}}--use-msix-tool{{else}}--use-makeappx{{end}} + vars: + ARCH: '{{.ARCH | default ARCH}}' + CERT_PATH: '{{.CERT_PATH | default ""}}' + PUBLISHER: '{{.PUBLISHER | default ""}}' + USE_MSIX_TOOL: '{{.USE_MSIX_TOOL | default "false"}}' + + install:msix:tools: + summary: Installs tools required for MSIX packaging + cmds: + - wails3 tool msix-install-tools + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}.exe' diff --git a/v3/examples/android/build/windows/icon.ico b/v3/examples/android/build/windows/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/android/build/windows/icon.ico differ diff --git a/v3/examples/android/build/windows/info.json b/v3/examples/android/build/windows/info.json new file mode 100644 index 000000000..850b2b5b0 --- /dev/null +++ b/v3/examples/android/build/windows/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "0.1.0" + }, + "info": { + "0000": { + "ProductVersion": "0.1.0", + "CompanyName": "My Company", + "FileDescription": "My Product Description", + "LegalCopyright": "© now, My Company", + "ProductName": "My Product", + "Comments": "This is a comment" + } + } +} \ No newline at end of file diff --git a/v3/examples/android/build/windows/msix/app_manifest.xml b/v3/examples/android/build/windows/msix/app_manifest.xml new file mode 100644 index 000000000..ecf2506b3 --- /dev/null +++ b/v3/examples/android/build/windows/msix/app_manifest.xml @@ -0,0 +1,52 @@ + + + + + + + My Product + My Company + My Product Description + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v3/examples/android/build/windows/msix/template.xml b/v3/examples/android/build/windows/msix/template.xml new file mode 100644 index 000000000..cab2f31e0 --- /dev/null +++ b/v3/examples/android/build/windows/msix/template.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + false + My Product + My Company + My Product Description + Assets\AppIcon.png + + + + + + + diff --git a/v3/examples/android/build/windows/nsis/project.nsi b/v3/examples/android/build/windows/nsis/project.nsi new file mode 100644 index 000000000..74b8d6ad0 --- /dev/null +++ b/v3/examples/android/build/windows/nsis/project.nsi @@ -0,0 +1,112 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the wails_tools.nsh file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "my-project" # Default "ios" +## !define INFO_COMPANYNAME "My Company" # Default "My Company" +## !define INFO_PRODUCTNAME "My Product Name" # Default "My Product" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.1.0" +## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "© now, My Company" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/v3/examples/android/build/windows/nsis/wails_tools.nsh b/v3/examples/android/build/windows/nsis/wails_tools.nsh new file mode 100644 index 000000000..dc9aebc17 --- /dev/null +++ b/v3/examples/android/build/windows/nsis/wails_tools.nsh @@ -0,0 +1,212 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "ios" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "My Company" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "My Product" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "0.1.0" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "© now, My Company" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + +!macroend \ No newline at end of file diff --git a/v3/examples/android/build/windows/wails.exe.manifest b/v3/examples/android/build/windows/wails.exe.manifest new file mode 100644 index 000000000..025555a69 --- /dev/null +++ b/v3/examples/android/build/windows/wails.exe.manifest @@ -0,0 +1,22 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + + + + + + + + \ No newline at end of file diff --git a/v3/examples/android/frontend/Inter Font License.txt b/v3/examples/android/frontend/Inter Font License.txt new file mode 100644 index 000000000..00287df15 --- /dev/null +++ b/v3/examples/android/frontend/Inter Font License.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/v3/examples/android/frontend/bindings/changeme/greetservice.js b/v3/examples/android/frontend/bindings/changeme/greetservice.js new file mode 100644 index 000000000..0b93e6d75 --- /dev/null +++ b/v3/examples/android/frontend/bindings/changeme/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/examples/android/frontend/bindings/changeme/index.js b/v3/examples/android/frontend/bindings/changeme/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/examples/android/frontend/bindings/changeme/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/examples/android/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventcreate.js b/v3/examples/android/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventcreate.js new file mode 100644 index 000000000..1ea105857 --- /dev/null +++ b/v3/examples/android/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventcreate.js @@ -0,0 +1,9 @@ +//@ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +Object.freeze($Create.Events); diff --git a/v3/examples/android/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts b/v3/examples/android/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts new file mode 100644 index 000000000..3dd1807bd --- /dev/null +++ b/v3/examples/android/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts @@ -0,0 +1,2 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT diff --git a/v3/examples/android/frontend/index.html b/v3/examples/android/frontend/index.html new file mode 100644 index 000000000..f7c8de065 --- /dev/null +++ b/v3/examples/android/frontend/index.html @@ -0,0 +1,110 @@ + + + + + + + + + Wails App + + + + +
+ +

Wails + Javascript

+
+
Demo Screens
+
+ +
+
Please enter your name below 👇
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ + +
+
+ + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+

+          
+ +
+
+ +
+ + + diff --git a/v3/examples/android/frontend/main.js b/v3/examples/android/frontend/main.js new file mode 100644 index 000000000..ad8064576 --- /dev/null +++ b/v3/examples/android/frontend/main.js @@ -0,0 +1,113 @@ +import {GreetService} from "./bindings/changeme"; +import * as Runtime from "@wailsio/runtime"; + +const resultElement = document.getElementById('result'); +const timeElement = document.getElementById('time'); +const deviceInfoElement = document.getElementById('deviceInfo'); +const Events = Runtime.Events; +const IOS = Runtime.IOS; // May be undefined in published package; we guard usages below. + +window.doGreet = () => { + let name = document.getElementById('name').value; + if (!name) { + name = 'anonymous'; + } + GreetService.Greet(name).then((result) => { + resultElement.innerText = result; + }).catch((err) => { + console.log(err); + }); +} + +window.doHaptic = (style) => { + if (!IOS || !IOS.Haptics?.Impact) { + console.warn('IOS runtime not available in @wailsio/runtime. Skipping haptic call.'); + return; + } + IOS.Haptics.Impact(style).catch((err) => { + console.error('Haptics error:', err); + }); +} + +window.getDeviceInfo = async () => { + if (!IOS || !IOS.Device?.Info) { + deviceInfoElement.innerText = 'iOS runtime not available; cannot fetch device info.'; + return; + } + try { + const info = await IOS.Device.Info(); + deviceInfoElement.innerText = JSON.stringify(info, null, 2); + } catch (e) { + deviceInfoElement.innerText = `Error: ${e?.message || e}`; + } +} + +// Generic caller for IOS..(args) +window.iosJsSet = async (methodPath, args) => { + if (!IOS) { + console.warn('IOS runtime not available in @wailsio/runtime.'); + return; + } + try { + const [group, method] = methodPath.split('.'); + const target = IOS?.[group]; + const fn = target?.[method]; + if (typeof fn !== 'function') { + console.warn('IOS method not found:', methodPath); + return; + } + await fn(args); + } catch (e) { + console.error('iosJsSet error for', methodPath, e); + } +} + +// Emit events for Go handlers +window.emitGo = (eventName, data) => { + try { + Events.Emit(eventName, data); + } catch (e) { + console.error('emitGo error:', e); + } +} + +// Toggle helpers for UI switches +window.setGoToggle = (eventName, enabled) => { + emitGo(eventName, { enabled: !!enabled }); +} + +window.setJsToggle = (methodPath, enabled) => { + iosJsSet(methodPath, { enabled: !!enabled }); +} + +Events.On('time', (payload) => { + // payload may be a plain value or an object with a `data` field depending on emitter/runtime + const value = (payload && typeof payload === 'object' && 'data' in payload) ? payload.data : payload; + console.log('[frontend] time event:', payload, '->', value); + timeElement.innerText = value; +}); + +// Simple pane switcher responding to native UITabBar +function showPaneByIndex(index) { + const panes = [ + document.getElementById('screen-bindings'), + document.getElementById('screen-go'), + document.getElementById('screen-js'), + ]; + panes.forEach((el, i) => { + if (!el) return; + if (i === index) el.classList.add('active'); + else el.classList.remove('active'); + }); +} + +// Listen for native tab selection events posted by the iOS layer +window.addEventListener('nativeTabSelected', (e) => { + const idx = (e && e.detail && typeof e.detail.index === 'number') ? e.detail.index : 0; + showPaneByIndex(idx); +}); + +// Ensure default pane is visible on load (index 0) +window.addEventListener('DOMContentLoaded', () => { + showPaneByIndex(0); +}); diff --git a/v3/examples/android/frontend/package-lock.json b/v3/examples/android/frontend/package-lock.json new file mode 100644 index 000000000..b0b7dc68f --- /dev/null +++ b/v3/examples/android/frontend/package-lock.json @@ -0,0 +1,936 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "vite": "^5.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.0.tgz", + "integrity": "sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.0.tgz", + "integrity": "sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.0.tgz", + "integrity": "sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.0.tgz", + "integrity": "sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.0.tgz", + "integrity": "sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.0.tgz", + "integrity": "sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.0.tgz", + "integrity": "sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.0.tgz", + "integrity": "sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.0.tgz", + "integrity": "sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.0.tgz", + "integrity": "sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.0.tgz", + "integrity": "sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.0.tgz", + "integrity": "sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.0.tgz", + "integrity": "sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.0.tgz", + "integrity": "sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.0.tgz", + "integrity": "sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.0.tgz", + "integrity": "sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.0.tgz", + "integrity": "sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.0.tgz", + "integrity": "sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.0.tgz", + "integrity": "sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.0.tgz", + "integrity": "sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz", + "integrity": "sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@wailsio/runtime": { + "version": "3.0.0-alpha.66", + "resolved": "https://registry.npmjs.org/@wailsio/runtime/-/runtime-3.0.0-alpha.66.tgz", + "integrity": "sha512-ENLu8rn1griL1gFHJqkq1i+BVxrrA0JPJHYneUJYuf/s54kjuQViW0RKDEe/WTDo56ABpfykrd/T8OYpPUyXUw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.0.tgz", + "integrity": "sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.50.0", + "@rollup/rollup-android-arm64": "4.50.0", + "@rollup/rollup-darwin-arm64": "4.50.0", + "@rollup/rollup-darwin-x64": "4.50.0", + "@rollup/rollup-freebsd-arm64": "4.50.0", + "@rollup/rollup-freebsd-x64": "4.50.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.50.0", + "@rollup/rollup-linux-arm-musleabihf": "4.50.0", + "@rollup/rollup-linux-arm64-gnu": "4.50.0", + "@rollup/rollup-linux-arm64-musl": "4.50.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.50.0", + "@rollup/rollup-linux-ppc64-gnu": "4.50.0", + "@rollup/rollup-linux-riscv64-gnu": "4.50.0", + "@rollup/rollup-linux-riscv64-musl": "4.50.0", + "@rollup/rollup-linux-s390x-gnu": "4.50.0", + "@rollup/rollup-linux-x64-gnu": "4.50.0", + "@rollup/rollup-linux-x64-musl": "4.50.0", + "@rollup/rollup-openharmony-arm64": "4.50.0", + "@rollup/rollup-win32-arm64-msvc": "4.50.0", + "@rollup/rollup-win32-ia32-msvc": "4.50.0", + "@rollup/rollup-win32-x64-msvc": "4.50.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/v3/examples/android/frontend/package.json b/v3/examples/android/frontend/package.json new file mode 100644 index 000000000..0a118e984 --- /dev/null +++ b/v3/examples/android/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "vite": "^5.0.0" + } +} diff --git a/v3/examples/android/frontend/public/Inter-Medium.ttf b/v3/examples/android/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/android/frontend/public/Inter-Medium.ttf differ diff --git a/v3/examples/android/frontend/public/javascript.svg b/v3/examples/android/frontend/public/javascript.svg new file mode 100644 index 000000000..f9abb2b72 --- /dev/null +++ b/v3/examples/android/frontend/public/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/android/frontend/public/puppertino/LICENSE b/v3/examples/android/frontend/public/puppertino/LICENSE new file mode 100644 index 000000000..ed9065e06 --- /dev/null +++ b/v3/examples/android/frontend/public/puppertino/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Edgar Pérez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/v3/examples/android/frontend/public/puppertino/css/actions.css b/v3/examples/android/frontend/public/puppertino/css/actions.css new file mode 100644 index 000000000..22a9d5a13 --- /dev/null +++ b/v3/examples/android/frontend/public/puppertino/css/actions.css @@ -0,0 +1,149 @@ +:root { + --font: -apple-system, "Inter", sans-serif; + --primary-col-ac: #0f75f5; + --p-modal-bg: rgba(255, 255, 255, 0.8); + --p-modal-bd-color: rgba(0,0,0,.1); + --p-modal-fallback-color: rgba(255,255,255,.95); + --p-actions-static-color: #555761; +} + +.p-modal-opened { + overflow: hidden; +} + +.p-action-background{ + background: rgba(0, 0, 0, 0.7); + height: 100vh; + left: 0; + opacity: 0; + pointer-events: none; + position: fixed; + top: 0; + transition: 0.3s; + width: 100vw; + z-index: 5; +} + +.p-action-background.nowactive { + opacity: 1; + pointer-events: auto; +} + + +.p-action-big-container{ + position:fixed; + width: 100%; + box-sizing: border-box; + padding: 1rem 5vw; + bottom:0; +} + +.p-action-container{ + background: var(--p-modal-bg); + display:block; + margin:auto; + margin-bottom: 10px; + border-radius: 10px; + max-width: 700px; +} + +.p-action-big-container .p-action-container:first-child{ + margin-bottom:10px; +} + +.p-action--intern{ + width: 100%; + display:block; + margin:auto; + font-size: 1rem; + font-weight: 600; + text-align:center; + padding: 15px 0; + border: 0; + border-bottom: 1px solid #bfbfbf; + color: #0f75f5; + text-decoration:none; + background-color: transparent; +} + +.p-action-destructive{ + color: #c6262e; +} + +.p-action-neutral{ + color: var(--p-actions-static-color); +} + +.p-action-cancel, .p-action-container a:last-child{ + border-bottom:none; +} + +.p-action-cancel{ + font-weight:bold; +} + +.p-action-icon{ + position:relative; +} +.p-action-icon svg, .p-action-icon img{ + position:absolute; + left:5%; + top:50%; + transform:translateY(-50%); +} + +.p-action-icon-inline{ + text-align: left; + display: flex; + align-items: center; +} + +.p-action-icon-inline svg, .p-action-icon-inline img{ + margin-left: 5%; + margin-right: 3%; +} + +.p-action-title{ + padding: 30px 15px; + border-bottom: 1px solid #bfbfbf; +} + +.p-action-title--intern,.p-action-text{ + margin:0; + color:var(--p-actions-static-color); +} + +.p-action-title--intern{ + margin-bottom: .3rem; +} + +@supports not (backdrop-filter: blur(10px)) { + .p-action-container { + background: var(--p-modal-fallback-color); + } +} + +.p-action-big-container{ + -webkit-transform: translateY(30%); + transform: translateY(30%); + opacity: 0; + transition: opacity 0.4s, transform 0.4s; + transition-timing-function: ease; + pointer-events: none; +} + +.p-action-big-container.active { + -webkit-transform: translateY(0); + transform: translateY(0); + opacity: 1; + pointer-events: all; +} + + +.p-action-big-container.active .p-action-container { + backdrop-filter: saturate(180%) blur(10px); +} + +.p-action-big-container[aria-hidden="true"] .p-action--intern { + display: none; +} diff --git a/v3/examples/android/frontend/public/puppertino/css/buttons.css b/v3/examples/android/frontend/public/puppertino/css/buttons.css new file mode 100644 index 000000000..4950b0053 --- /dev/null +++ b/v3/examples/android/frontend/public/puppertino/css/buttons.css @@ -0,0 +1,158 @@ +@charset "UTF-8"; +:root{ + --p-btn-border: #cacaca; + --p-btn-def-bg: #FFFFFF; + --p-btn-def-col: #000000; + --p-btn-dir-col: #242424; + --p-prim-text-col: #f5f5f5; + --p-btn-scope-unactive: #212136; + --p-btn-scope-action: #212136; +} + +.p-btn { + background: var(--p-btn-def-bg); + border: 1px solid var(--p-btn-border); + border-radius: 10px; + color: var(--p-btn-def-col); + display: inline-block; + font-family: -apple-system, "Inter", sans-serif; + font-size: 1.1rem; + margin: .7rem; + padding: .4rem 1.2rem; + text-decoration: none; + text-align: center; + box-shadow: 0 1px 0.375px rgba(0, 0, 0, 0.05), 0 0.25px 0.375px rgba(0, 0, 0, 0.15); + user-select: none; + cursor: pointer; +} +.p-btn:focus{ + outline: 2px solid #64baff; +} +.p-btn.p-btn-block{ + display: block; +} +.p-btn.p-btn-sm { + padding: .3rem 1.1rem; + font-size: 1rem; +} +.p-btn.p-btn-md { + padding: .8rem 2.4rem; + font-size: 1.6rem; +} +.p-btn.p-btn-lg { + padding: 1.2rem 2.8rem; + font-size: 1.8rem; +} +.p-btn-destructive{ + color: #FF3B30; +} +.p-btn-mob{ + padding: 10px 40px; + background: #227bec; + color: #fff; + border: 0; + box-shadow: inset 0 1px 1px rgb(255 255 255 / 41%), 0px 2px 3px -2px rgba(0,0,0,.3); +} +.p-btn[disabled], +.p-btn:disabled, +.p-btn-disabled{ + filter:contrast(0.5) grayscale(.5) opacity(.8); + cursor: not-allowed; + box-shadow: none; + pointer-events: none; +} + +.p-prim-col { + position: relative; + background: #007AFF; + border: none; + box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.41), 0px 2px 3px -2px rgba(0, 0, 0, 0.3); + color: var(--p-prim-text-col); + overflow: hidden; /* Ensure the ::before element doesn't overflow */ +} + +.p-prim-col:before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(180deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%); + opacity: 0.17; + pointer-events: none; +} + +.p-btn.p-prim-col:active { + background: #0f75f5; +} + +.p-btn-more::after { + content: "..."; +} + +.p-btn-round { + border: 0; + border-radius: 50px; + box-shadow: inset 0 1px 1px rgb(255 255 255 / 41%); + padding: 10px 30px; +} + +.p-btn-icon { + align-items: center; + background: var(--p-btn-def-bg); + border: 2px solid currentColor; + border-radius: 50%; + color: #0f75f5; + display: inline-flex; + font-weight: 900; + height: 40px; + width: 40px; + justify-content: center; + margin: 5px; + text-align: center; + text-decoration: none; + box-sizing: border-box; + user-select: none; + vertical-align: bottom; +} + +.p-btn-icon.p-btn-icon-no-border{ + border: 0px; +} + +.p-btn-scope { + background: #8e8e8e; + color: #fff; + margin: 5px; + padding: 2px 20px; + box-shadow: none; +} +.p-btn-scope-unactive { + background: transparent; + border-color: transparent; + color: var(--p-btn-scope-unactive); + transition: border-color 0.2s; +} +.p-btn-scope-unactive:hover { + border-color: var(--p-btn-border); +} + +.p-btn-scope-outline { + background: transparent; + color: var(--p-btn-scope-action); + box-shadow: none; +} + +.p-btn-outline { + background: none; + border-color: currentColor; + box-shadow: none; +} + +.p-btn-outline-dash { + background: none; + border-color: currentColor; + border-style: dashed; + box-shadow: none; +} diff --git a/v3/examples/android/frontend/public/puppertino/css/cards.css b/v3/examples/android/frontend/public/puppertino/css/cards.css new file mode 100644 index 000000000..b4fa2e397 --- /dev/null +++ b/v3/examples/android/frontend/public/puppertino/css/cards.css @@ -0,0 +1,55 @@ +:root{ + --p-color-card: #1a1a1a; + --p-bg-card: #fff; + --p-bd-card: #c5c5c55e; +} +.p-card { + background: var(--p-bg-card); + border: 1px solid var(--p-bd-card); + color: var(--p-color-card); + display: block; + margin: 15px; + margin-left:7.5px; + margin-right:7.5px; + text-decoration: none; + border-radius: 25px; + padding: 20px 0px; + transition: .3s ease; + box-shadow: 0 0.5px 1px rgba(0, 0, 0, 0.1); +} +.p-card-image > img { + border-bottom: 3px solid var(--accent-article); + display: block; + margin: auto; + width: 100%; +} +.p-card-tags { + display: flex; + overflow: hidden; + position: relative; + width: 100%; +} +.p-card-tags::before { + background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 75%, white 100%); + content: ""; + height: 100%; + position: absolute; + right: 0; + top: 0; + width: 30%; +} +.p-card-title { + font-size: 2rem; + margin-bottom: 15px; + margin-top: 15px; +} +.p-card-content { + padding: 15px; + padding-top: 15px; +} +.p-card-text { + font-size: 17px; + margin-bottom: 10px; + margin-left: 10px; + margin-top: 0; +} diff --git a/v3/examples/android/frontend/public/puppertino/css/color_palette.css b/v3/examples/android/frontend/public/puppertino/css/color_palette.css new file mode 100644 index 000000000..33a66b91c --- /dev/null +++ b/v3/examples/android/frontend/public/puppertino/css/color_palette.css @@ -0,0 +1,917 @@ +:root{ +--p-strawberry: #c6262e; +--p-strawberry-100: #ff8c82; +--p-strawberry-300: #ed5353; +--p-strawberry-500: #c6262e; +--p-strawberry-700: #a10705; +--p-strawberry-900: #7a0000; + +--p-orange: #f37329; +--p-orange-100: #ffc27d; +--p-orange-300: #ffa154; +--p-orange-500: #f37329; +--p-orange-700: #cc3b02; +--p-orange-900: #a62100; + + +--p-banana: #f9c440; +--p-banana-100: #fff394; +--p-banana-300: #ffe16b; +--p-banana-500: #f9c440; +--p-banana-700: #d48e15; +--p-banana-900: #ad5f00; + +--p-lime: #68b723; +--p-lime-100: #d1ff82; +--p-lime-300: #9bdb4d; +--p-lime-500: #68b723; +--p-lime-700: #3a9104; +--p-lime-900: #206b00; + +--p-mint: #28bca3; +--p-mint-100: #89ffdd; +--p-mint-300: #43d6b5; +--p-mint-500: #28bca3; +--p-mint-700: #0e9a83; +--p-mint-900: #007367; + + +--p-blueberry: #3689e6; +--p-blueberry-100: #8cd5ff; +--p-blueberry-300: #64baff; +--p-blueberry-500: #3689e6; +--p-blueberry-700: #0d52bf; +--p-blueberry-900: #002e99; + +--p-grape: #a56de2; +--p-grape-100: #e4c6fa; +--p-grape-300: #cd9ef7; +--p-grape-500: #a56de2; +--p-grape-700: #7239b3; +--p-grape-900: #452981; + +--p-bubblegum: #de3e80; +--p-bubblegum-100: #fe9ab8; +--p-bubblegum-300: #f4679d; +--p-bubblegum-500: #de3e80; +--p-bubblegum-700: #bc245d; +--p-bubblegum-900: #910e38; + + +--p-cocoa: #715344; +--p-cocoa-100: #a3907c; +--p-cocoa-300: #8a715e; +--p-cocoa-500: #715344; +--p-cocoa-700: #57392d; +--p-cocoa-900: #3d211b; + +--p-silver: #abacae; +--p-silver-100: #fafafa; +--p-silver-300: #d4d4d4; +--p-silver-500: #abacae; +--p-silver-700: #7e8087; +--p-silver-900: #555761; + +--p-slate: #485a6c; +--p-slate-100: #95a3ab; +--p-slate-300: #667885; +--p-slate-500: #485a6c; +--p-slate-700: #273445; +--p-slate-900: #0e141f; + + +--p-dark: #333; +--p-dark-100: #666; +--p-dark-300: #4d4d4d; +--p-dark-500: #333; +--p-dark-700: #1a1a1a; +--p-dark-900: #000; + + +--p-apple-red: rgb(255, 59 , 48); +--p-apple-red-dark: rgb(255, 69 , 58); +--p-apple-orange: rgb(255,149,0); +--p-apple-orange-dark: rgb(255,159,10); +--p-apple-yellow: rgb(255,204,0); +--p-apple-yellow-dark: rgb(255,214,10); +--p-apple-green: rgb(40,205,65); +--p-apple-green-dark: rgb(40,215,75); +--p-apple-mint: rgb(0,199,190); +--p-apple-mint-dark: rgb(102,212,207); +--p-apple-teal: rgb(89, 173, 196); +--p-apple-teal-dark: rgb(106, 196, 220); +--p-apple-cyan: rgb(85,190,240); +--p-apple-cyan-dark: rgb(90,200,245); +--p-apple-blue: rgb(0, 122, 255); +--p-apple-blue-dark: rgb(10, 132, 255); +--p-apple-indigo: rgb(88, 86, 214); +--p-apple-indigo-dark: rgb(94, 92, 230); +--p-apple-purple: rgb(175, 82, 222); +--p-apple-purple-dark: rgb(191, 90, 242); +--p-apple-pink: rgb(255, 45, 85); +--p-apple-pink-dark: rgb(255, 55, 95); +--p-apple-brown: rgb(162, 132, 94); +--p-apple-brown-dark: rgb(172, 142, 104); +--p-apple-gray: rgb(142, 142, 147); +--p-apple-gray-dark: rgb(152, 152, 157); + +} + + +/* +APPLE OFFICIAL COLORS +*/ + +.p-apple-red{ + background: rgb(255, 59 , 48); +} + +.p-apple-red-dark{ + background: rgb(255, 69 , 58); +} + +.p-apple-orange{ + background: rgb(255,149,0); +} + +.p-apple-orange-dark{ + background: rgb(255,159,10); +} + +.p-apple-yellow{ + background: rgb(255,204,0); +} + +.p-apple-yellow-dark{ + background: rgb(255,214,10); +} + +.p-apple-green{ + background: rgb(40,205,65); +} + +.p-apple-green-dark{ + background: rgb(40,215,75); +} + +.p-apple-mint{ + background: rgb(0,199,190); +} + +.p-apple-mint-dark{ + background: rgb(102,212,207); +} + +.p-apple-teal{ + background: rgb(89, 173, 196); +} + +.p-apple-teal-dark{ + background: rgb(106, 196, 220); +} + +.p-apple-cyan{ + background: rgb(85,190,240); +} + +.p-apple-cyan-dark{ + background: rgb(90,200,245); +} + +.p-apple-blue{ + background: rgb(0, 122, 255); +} + +.p-apple-blue-dark{ + background: rgb(10, 132, 255); +} + +.p-apple-indigo{ + background: rgb(88, 86, 214); +} + +.p-apple-indigo-dark{ + background: rgb(94, 92, 230); +} + +.p-apple-purple{ + background: rgb(175, 82, 222); +} + +.p-apple-purple-dark{ + background: rgb(191, 90, 242); +} + +.p-apple-pink{ + background: rgb(255, 45, 85); +} + +.p-apple-pink-dark{ + background: rgb(255, 55, 95); +} + +.p-apple-brown{ + background: rgb(162, 132, 94); +} + +.p-apple-brown-dark{ + background: rgb(172, 142, 104); +} + +.p-apple-gray{ + background: rgb(142, 142, 147); +} + +.p-apple-gray-dark{ + background: rgb(152, 152, 157); +} + +.p-apple-red-color{ + color: rgb(255, 59 , 48); +} + +.p-apple-red-dark-color{ + color: rgb(255, 69 , 58); +} + +.p-apple-orange-color{ + color: rgb(255,149,0); +} + +.p-apple-orange-dark-color{ + color: rgb(255,159,10); +} + +.p-apple-yellow-color{ + color: rgb(255,204,0); +} + +.p-apple-yellow-dark-color{ + color: rgb(255,214,10); +} + +.p-apple-green-color{ + color: rgb(40,205,65); +} + +.p-apple-green-dark-color{ + color: rgb(40,215,75); +} + +.p-apple-mint-color{ + color: rgb(0,199,190); +} + +.p-apple-mint-dark-color{ + color: rgb(102,212,207); +} + +.p-apple-teal-color{ + color: rgb(89, 173, 196); +} + +.p-apple-teal-dark-color{ + color: rgb(106, 196, 220); +} + +.p-apple-cyan-color{ + color: rgb(85,190,240); +} + +.p-apple-cyan-dark-color{ + color: rgb(90,200,245); +} + +.p-apple-blue-color{ + color: rgb(0, 122, 255); +} + +.p-apple-blue-dark-color{ + color: rgb(10, 132, 255); +} + +.p-apple-indigo-color{ + color: rgb(88, 86, 214); +} + +.p-apple-indigo-dark-color{ + color: rgb(94, 92, 230); +} + +.p-apple-purple-color{ + color: rgb(175, 82, 222); +} + +.p-apple-purple-dark-color{ + color: rgb(191, 90, 242); +} + +.p-apple-pink-color{ + color: rgb(255, 45, 85); +} + +.p-apple-pink-dark-color{ + color: rgb(255, 55, 95); +} + +.p-apple-brown-color{ + color: rgb(162, 132, 94); +} + +.p-apple-brown-dark-color{ + color: rgb(172, 142, 104); +} + +.p-apple-gray-color{ + color: rgb(142, 142, 147); +} + +.p-apple-gray-dark-color{ + color: rgb(152, 152, 157); +} + +.p-strawberry { + background: #c6262e; +} + +.p-strawberry-100 { + background: #ff8c82; +} + +.p-strawberry-300 { + background: #ed5353; +} + +.p-strawberry-500 { + background: #c6262e; +} + +.p-strawberry-700 { + background: #a10705; +} + +.p-strawberry-900 { + background: #7a0000; +} + +.p-orange { + background: #f37329; +} + +.p-orange-100 { + background: #ffc27d; +} + +.p-orange-300 { + background: #ffa154; +} + +.p-orange-500 { + background: #f37329; +} + +.p-orange-700 { + background: #cc3b02; +} + +.p-orange-900 { + background: #a62100; +} + +.p-banana { + background: #f9c440; +} + +.p-banana-100 { + background: #fff394; +} + +.p-banana-300 { + background: #ffe16b; +} + +.p-banana-500 { + background: #f9c440; +} + +.p-banana-700 { + background: #d48e15; +} + +.p-banana-900 { + background: #ad5f00; +} + +.p-lime { + background: #68b723; +} + +.p-lime-100 { + background: #d1ff82; +} + +.p-lime-300 { + background: #9bdb4d; +} + +.p-lime-500 { + background: #68b723; +} + +.p-lime-700 { + background: #3a9104; +} + +.p-lime-900 { + background: #206b00; +} + +.p-mint { + background: #28bca3; +} + +.p-mint-100 { + background: #89ffdd; +} + +.p-mint-300 { + background: #43d6b5; +} + +.p-mint-500 { + background: #28bca3; +} + +.p-mint-700 { + background: #0e9a83; +} + +.p-mint-900 { + background: #007367; +} + +.p-blueberry { + background: #3689e6; +} + +.p-blueberry-100 { + background: #8cd5ff; +} + +.p-blueberry-300 { + background: #64baff; +} + +.p-blueberry-500 { + background: #3689e6; +} + +.p-blueberry-700 { + background: #0d52bf; +} + +.p-blueberry-900 { + background: #002e99; +} + +.p-grape { + background: #a56de2; +} + +.p-grape-100 { + background: #e4c6fa; +} + +.p-grape-300 { + background: #cd9ef7; +} + +.p-grape-500 { + background: #a56de2; +} + +.p-grape-700 { + background: #7239b3; +} + +.p-grape-900 { + background: #452981; +} + +.p-bubblegum { + background: #de3e80; +} + +.p-bubblegum-100 { + background: #fe9ab8; +} + +.p-bubblegum-300 { + background: #f4679d; +} + +.p-bubblegum-500 { + background: #de3e80; +} + +.p-bubblegum-700 { + background: #bc245d; +} + +.p-bubblegum-900 { + background: #910e38; +} + +.p-cocoa { + background: #715344; +} + +.p-cocoa-100 { + background: #a3907c; +} + +.p-cocoa-300 { + background: #8a715e; +} + +.p-cocoa-500 { + background: #715344; +} + +.p-cocoa-700 { + background: #57392d; +} + +.p-cocoa-900 { + background: #3d211b; +} + +.p-silver { + background: #abacae; +} + +.p-silver-100 { + background: #fafafa; +} + +.p-silver-300 { + background: #d4d4d4; +} + +.p-silver-500 { + background: #abacae; +} + +.p-silver-700 { + background: #7e8087; +} + +.p-silver-900 { + background: #555761; +} + +.p-slate { + background: #485a6c; +} + +.p-slate-100 { + background: #95a3ab; +} + +.p-slate-300 { + background: #667885; +} + +.p-slate-500 { + background: #485a6c; +} + +.p-slate-700 { + background: #273445; +} + +.p-slate-900 { + background: #0e141f; +} + +.p-dark { + background: #333; +} + +.p-dark-100 { + background: #666; + /* hehe */ +} + +.p-dark-300 { + background: #4d4d4d; +} + +.p-dark-500 { + background: #333; +} + +.p-dark-700 { + background: #1a1a1a; +} + +.p-dark-900 { + background: #000; +} + +.p-white{ + background: #fff; +} + +.p-strawberry-color { + color: #c6262e; +} + +.p-strawberry-100-color { + color: #ff8c82; +} + +.p-strawberry-300-color { + color: #ed5353; +} + +.p-strawberry-500-color { + color: #c6262e; +} + +.p-strawberry-700-color { + color: #a10705; +} + +.p-strawberry-900-color { + color: #7a0000; +} + +.p-orange-color { + color: #f37329; +} + +.p-orange-100-color { + color: #ffc27d; +} + +.p-orange-300-color { + color: #ffa154; +} + +.p-orange-500-color { + color: #f37329; +} + +.p-orange-700-color { + color: #cc3b02; +} + +.p-orange-900-color { + color: #a62100; +} + +.p-banana-color { + color: #f9c440; +} + +.p-banana-100-color { + color: #fff394; +} + +.p-banana-300-color { + color: #ffe16b; +} + +.p-banana-500-color { + color: #f9c440; +} + +.p-banana-700-color { + color: #d48e15; +} + +.p-banana-900-color { + color: #ad5f00; +} + +.p-lime-color { + color: #68b723; +} + +.p-lime-100-color { + color: #d1ff82; +} + +.p-lime-300-color { + color: #9bdb4d; +} + +.p-lime-500-color { + color: #68b723; +} + +.p-lime-700-color { + color: #3a9104; +} + +.p-lime-900-color { + color: #206b00; +} + +.p-mint-color { + color: #28bca3; +} + +.p-mint-100-color { + color: #89ffdd; +} + +.p-mint-300-color { + color: #43d6b5; +} + +.p-mint-500-color { + color: #28bca3; +} + +.p-mint-700-color { + color: #0e9a83; +} + +.p-mint-900-color { + color: #007367; +} + +.p-blueberry-color { + color: #3689e6; +} + +.p-blueberry-100-color { + color: #8cd5ff; +} + +.p-blueberry-300-color { + color: #64baff; +} + +.p-blueberry-500-color { + color: #3689e6; +} + +.p-blueberry-700-color { + color: #0d52bf; +} + +.p-blueberry-900-color { + color: #002e99; +} + +.p-grape-color { + color: #a56de2; +} + +.p-grape-100-color { + color: #e4c6fa; +} + +.p-grape-300-color { + color: #cd9ef7; +} + +.p-grape-500-color { + color: #a56de2; +} + +.p-grape-700-color { + color: #7239b3; +} + +.p-grape-900-color { + color: #452981; +} + +.p-bubblegum-color { + color: #de3e80; +} + +.p-bubblegum-100-color { + color: #fe9ab8; +} + +.p-bubblegum-300-color { + color: #f4679d; +} + +.p-bubblegum-500-color { + color: #de3e80; +} + +.p-bubblegum-700-color { + color: #bc245d; +} + +.p-bubblegum-900-color { + color: #910e38; +} + +.p-cocoa-color { + color: #715344; +} + +.p-cocoa-100-color { + color: #a3907c; +} + +.p-cocoa-300-color { + color: #8a715e; +} + +.p-cocoa-500-color { + color: #715344; +} + +.p-cocoa-700-color { + color: #57392d; +} + +.p-cocoa-900-color { + color: #3d211b; +} + +.p-silver-color { + color: #abacae; +} + +.p-silver-100-color { + color: #fafafa; +} + +.p-silver-300-color { + color: #d4d4d4; +} + +.p-silver-500-color { + color: #abacae; +} + +.p-silver-700-color { + color: #7e8087; +} + +.p-silver-900-color { + color: #555761; +} + +.p-slate-color { + color: #485a6c; +} + +.p-slate-100-color { + color: #95a3ab; +} + +.p-slate-300-color { + color: #667885; +} + +.p-slate-500-color { + color: #485a6c; +} + +.p-slate-700-color { + color: #273445; +} + +.p-slate-900-color { + color: #0e141f; +} + +.p-dark-color { + color: #333; +} + +.p-dark-100-color { + color: #666; + /* hehe */ +} + +.p-dark-300-color { + color: #4d4d4d; +} + +.p-dark-500-color { + color: #333; +} + +.p-dark-700-color { + color: #1a1a1a; +} + +.p-dark-900-color { + color: #000; +} + +.p-white-color{ + color: #fff; +} diff --git a/v3/examples/android/frontend/public/puppertino/css/dark_mode.css b/v3/examples/android/frontend/public/puppertino/css/dark_mode.css new file mode 100644 index 000000000..3c5a03e80 --- /dev/null +++ b/v3/examples/android/frontend/public/puppertino/css/dark_mode.css @@ -0,0 +1 @@ +/* Puppertino dark_mode placeholder - local vendored */ diff --git a/v3/examples/android/frontend/public/puppertino/css/forms.css b/v3/examples/android/frontend/public/puppertino/css/forms.css new file mode 100644 index 000000000..f2320ab1b --- /dev/null +++ b/v3/examples/android/frontend/public/puppertino/css/forms.css @@ -0,0 +1,509 @@ +:root { + --primary-col:linear-gradient(to bottom, #4fc5fa 0%,#0f75f5 100%); + --primary-col-ac:#0f75f5; + --bg-color-input:#fff; + + --p-checkbox-gradient: linear-gradient(180deg, #4B91F7 0%, #367AF6 100%); + --p-checkbox-border: rgba(0, 0, 0, 0.2); + --p-checkbox-border-active: rgba(0, 0, 0, 0.12); + --p-checkbox-bg: transparent; + --p-checkbox-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.15), inset 0px 0px 2px rgba(0, 0, 0, 0.10); + + --p-input-bg:#fff; + --p-input-color: rgba(0,0,0,.85); + --p-input-color-plac:rgba(0,0,0,0.25); + + --p-input-color:#808080; + --p-input-bd:rgba(0,0,0,0.15); + --bg-hover-color:#f9f9f9; + --bg-front-col:#000; + --invalid-color:#d6513c; + --valid-color:#94d63c; +} + +.p-dark-mode{ + --p-checkbox-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.13) 100%); + --p-checkbox-shadow: 0px 0px 1px rgba(0, 0, 0, 0.25), inset 0px 0.5px 0px rgba(255, 255, 255, 0.15); + --p-checkbox-border: rgba(0, 0, 0, 0); + + --p-checkbox-gradient: linear-gradient(180deg, #3168DD 0%, #2C5FC8 100%); + --p-checkbox-border-active: rgba(0, 0, 0, 0); +} + +.p-form-select { + border-radius: 5px; + display: inline-block; + font-family: -apple-system, "Inter", sans-serif; + margin: 10px; + position: relative; +} + +.p-form-select > select:focus{ + outline: 2px solid #64baff; +} + +.p-form-select::after { + background: url("data:image/svg+xml,%3Csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 12'%3E%3Cpath d='M.288 4.117 3.16 1.18c.168-.168.336-.246.54-.246a.731.731 0 0 1 .538.246L7.108 4.12c.125.121.184.27.184.45 0 .359-.293.656-.648.656a.655.655 0 0 1-.48-.211L3.701 2.465l-2.469 2.55a.664.664 0 0 1-.48.212.656.656 0 0 1-.465-1.11Zm3.41 7.324a.73.73 0 0 0 .54-.246l2.87-2.941a.601.601 0 0 0 .184-.45.656.656 0 0 0-.648-.656.677.677 0 0 0-.48.211L3.701 9.91 1.233 7.36a.68.68 0 0 0-.48-.212.656.656 0 0 0-.465 1.11l2.871 2.937c.172.168.336.246.54.246Z' fill='white' style='mix-blend-mode:luminosity'/%3E%3C/svg%3E"), #017AFF; + background-size: 100% 75%; + background-position: center; + background-repeat: no-repeat; + border-radius: 5px; + bottom: 0; + content: ""; + display: block; + height: 80%; + pointer-events: none; + position: absolute; + right: 3%; + top: 10%; + width: 20px; +} + +.p-form-select > select { + -webkit-appearance: none; + appearance: none; + background: var(--p-input-bg); + border: 1px solid var(--p-input-bd); + border-radius: 5px; + font-size: 14px; + margin: 0; + outline: none; + padding: 5px 35px 5px 10px; + position: relative; + width: 100%; + color: var(--p-input-color); +} + +.p-form-text:invalid, +.p-form-text-alt:invalid{ + border-color: var(--invalid-color); +} + +.p-form-text:valid, +.p-form-text-alt:valid{ + border-color: var(--valid-color); +} + +.p-form-text:placeholder-shown, +.p-form-text-alt:placeholder-shown{ + border-color: var(--p-input-bd); +} + +.p-form-text { + color: var(--p-input-color); + -webkit-appearance: none; + appearance: none; + background: var(--p-input-bg); + border: 1px solid var(--p-input-bd); + border-radius: 5px; + font-family: -apple-system, "Inter", sans-serif; + font-size: 13px; + margin: 10px; + outline: 0; + padding: 3px 7px; + resize: none; + transition: border-color 200ms; + box-shadow: 0px 0.5px 2.5px rgba(0,0,0,.3), 0px 0px 0px rgba(0,0,0,.1); +} + +.p-form-text-alt { + color: var(--p-input-color); + -webkit-appearance: none; + appearance: none; + box-shadow: none; + background: var(--p-input-bg); + border: 0px; + border-bottom: 2px solid var(--p-input-bd); + padding: 10px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + margin: 10px; +} + + + +.p-form-text-alt::placeholder, +.p-form-text::placeholder +{ + color: var(--p-input-color-plac); +} + +.p-form-text:active, +.p-form-text:focus +{ + outline: 3px solid rgb(0 122 255 / 50%); +} + +.p-form-text-alt:focus { + outline: 0; + outline: 3px solid rgb(0 122 255 / 50%); + border-color: #3689e6; +} + +.p-form-no-validate:valid, +.p-form-no-validate:invalid{ + border-color: var(--p-input-bd); + color: var(--p-input-color)!important; +} + +.p-form-text:focus { + border-color: rgb(0 122 255); +} + +textarea.p-form-text { + -webkit-appearance: none; + appearance: none; + height: 100px; +} + +.p-form-truncated { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.p-form-text[type=password] { + font-family: caption; +} + +.p-form-label, +.p-form-radio-cont, +.p-form-checkbox-cont, +.p-form-label-inline { + font-family: -apple-system, "Inter", sans-serif; +} + +.p-form-label, .p-form-label-inline { + display: inline-block; +} + +.p-form-label{ + font-size: 11px; +} + +.p-form-label-inline { + background: var(--p-input-bg); + padding: 5px; + border-bottom: 2px solid var(--p-input-bd); + color: #656565; + font-weight: 500; + transition: .3s; +} + +.p-form-label-inline:focus-within { + color: #3689e6; + border-color: #3689e6; +} + +.p-form-label-inline > .p-form-text-alt { + border-bottom: 0px; + padding: 0; + outline: 0; + background: var(--p-input-bg); + +} + +.p-form-label-inline > .p-form-text-alt:-webkit-autofill{ + background: var(--p-input-bg); + -webkit-box-shadow: 0 0 0 30px rgba(0,0,0,0) inset !important; +} + +.p-form-label-inline > .p-form-text-alt:invalid { + color: var(--invalid-color); +} + +.p-form-label-inline > .p-form-text-alt:valid { + color: #3689e6; +} + +.p-form-label-inline > .p-form-text-alt:focus{ + color: var(--p-input-color); +} + +.p-form-radio-cont, +.p-form-checkbox-cont { + align-items: center; + display: inline-flex; + cursor: pointer; + margin: 0 10px; + user-select: none; +} + +.p-form-radio-cont > input + span, +.p-form-checkbox-cont > input + span { + background: var(--p-input-bg); + border: 1px solid var(--p-input-bd); + border-radius: 50%; + display: inline-block; + height: 20px; + margin-right: 5px; + position: relative; + transition: 0.2s; + width: 20px; +} + +.p-form-radio-cont > input + span{ + box-shadow: inset 0px 1px 2px rgba(0,0,0,0.10), inset 0px 0px 2px rgba(0,0,0,0.10); +} + +.p-form-radio-cont > input:focus + span, +.p-form-checkbox-cont > input:focus + span{ + outline: 3px solid rgb(0 122 255 / 50%); +} + +.p-form-radio-cont:hover > input + span{ + background: #f9f9f9; +} + +.p-form-radio-cont > input, +.p-form-checkbox-cont > input { + opacity: 0; + pointer-events: none; + position: absolute; +} + +.p-form-radio-cont > input + span::after { + background: #fff; + border-radius: 50%; + content: ""; + display: block; + height: 30%; + left: calc(50% - 15%); + opacity: 0; + position: absolute; + top: calc(50% - 15%); + transform: scale(2); + transition: opacity 0.2s, transform 0.3s; + width: 30%; +} + +.p-form-radio-cont > input:checked + span { + background: #0f75f5; + box-shadow: 0px 1px 2.5px 1px rgba(0, 122, 255, 0.24), inset 0px 0px 0px 0.5px rgba(0, 122, 255, 0.12); +} + +.p-form-radio-cont > input:checked + span::after { + opacity: 1; + transform: scale(1); +} + +.p-form-checkbox-cont > input + span { + border-radius: 5px; + box-shadow: var(--p-checkbox-shadow); + border: 0.5px solid var(--p-checkbox-border); + background: var(--p-checkbox-bg) +} + +.p-form-checkbox-cont > input:checked + span { + background: var(--p-checkbox-gradient); + border: 0.5px solid var(--p-checkbox-border-active); + box-shadow: 0px 1px 2.5px rgba(0, 122, 255, 0.24), 0px 0px 0px 0.5px rgba(0, 122, 255, 0.12); +} + +.p-form-checkbox-cont > input + span::before{ + content: ""; + display: block; + height: 100%; + width: 100%; + position: absolute; + left: 0%; + top: 0%; + opacity: 0; + transition: opacity 0.2s; + background-image: url("data:image/svg+xml,%3Csvg width='10' height='8' viewBox='0 0 10 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.10791 7.81299C3.83545 7.81299 3.6084 7.70459 3.42676 7.48779L1.15918 4.74121C1.08008 4.65039 1.02441 4.5625 0.992188 4.47754C0.959961 4.39258 0.943848 4.30469 0.943848 4.21387C0.943848 4.00586 1.0127 3.83447 1.15039 3.69971C1.29102 3.56201 1.4668 3.49316 1.67773 3.49316C1.91211 3.49316 2.10693 3.58838 2.26221 3.77881L4.10791 6.04297L7.68066 0.368652C7.77148 0.230957 7.86523 0.134277 7.96191 0.0786133C8.06152 0.0200195 8.18311 -0.00927734 8.32666 -0.00927734C8.5376 -0.00927734 8.71191 0.0581055 8.84961 0.192871C8.9873 0.327637 9.05615 0.497559 9.05615 0.702637C9.05615 0.778809 9.04297 0.85791 9.0166 0.939941C8.99023 1.02197 8.94922 1.10693 8.89355 1.19482L4.80225 7.45703C4.64111 7.69434 4.40967 7.81299 4.10791 7.81299Z' fill='white'/%3E%3C/svg%3E%0A"); + background-size: 70%; + background-position: center; + background-repeat: no-repeat; +} + +.p-form-checkbox-cont > input + span::after{ + content: ''; + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + z-index: 9; +} + +.p-form-checkbox-cont > input + span:active::after{ + border-radius: 5px; + backdrop-filter: brightness(1.2); +} + +.p-form-checkbox-cont > input:checked + span::before{ + opacity: 1; +} + + +.p-form-checkbox-cont > input[disabled] + span, +.p-form-radio-cont > input[disabled] ~ span +{ + opacity: .7; + cursor: not-allowed; +} + +.p-form-button { + -webkit-appearance: none; + appearance: none; + background: #fff; + border: 1px solid var(--p-input-bd); + border-radius: 5px; + color: #333230; + display: inline-block; + font-size: 17px; + margin: 10px; + padding: 5px 20px; + text-decoration: none; +} + +.p-form-send { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border: 0; + color: #fff; +} + +.p-form-send:active { + background: #0f75f5; +} + +.p-form-invalid, +.p-form-invalid:placeholder-shown, +.p-form-invalid:valid, +.p-form-invalid:invalid { + border-color: var(--invalid-color); +} + +.p-form-valid, +.p-form-valid:placeholder-shown, +.p-form-valid:valid, +.p-form-valid:invalid { + border-color: var(--valid-color); +} + +.p-form-switch { + --width: 80px; + cursor: pointer; + display: inline-block; +} + +.p-form-switch > input:checked + span::after { + left: calc(100% - calc(var(--width) / 1.8)); +} + +.p-form-switch > input:checked + span { + background: #60c35b; +} + +.p-form-switch > span { + background: #e0e0e0; + border: 1px solid #d3d3d3; + border-radius: 500px; + display: block; + height: calc(var(--width) / 1.6); + position: relative; + transition: all 0.2s; + width: var(--width); +} + +.p-form-switch > span::after { + background: #f9f9f9; + border-radius: 50%; + border: 0.5px solid rgba(0, 0, 0, 0.101987); + box-shadow: 0px 3px 1px rgba(0, 0, 0, 0.1), 0px 1px 1px rgba(0, 0, 0, 0.16), 0px 3px 8px rgba(0, 0, 0, 0.15); + box-sizing: border-box; + content: ""; + height: 84%; + left: 3%; + position: absolute; + top: 6.5%; + transition: all 0.2s; + width: 52.5%; +} + +.p-form-switch > input { + display: none; +} + +.p-chip input{ + opacity: 0; + pointer-events: none; + position: absolute; +} + +.p-chip span{ + padding: .8rem 1rem; + border-radius: 1.6rem; + display:inline-block; + margin:10px; + background: #e4e4e4ca; + color: #3689e6; + transition: .3s; + user-select: none; + cursor:pointer; + font-family: -apple-system, "Inter", sans-serif; + font-size: 1rem; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -moz-tap-highlight-color: rgba(0, 0, 0, 0); + text-align:center; +} + +.p-chip:focus-within span{ + outline: 2px solid #64baff; +} + +.p-chip svg{ + display:block; + margin:auto; +} + + +.p-chip input:checked + span{ + background: #3689e6; + color:#fff; +} + +.p-chip-outline span, .p-chip-outline-to-bg span{ + background: transparent; + color: #3e3e3e; + border: 1px solid currentColor; +} + +.p-chip-outline input:checked + span{ + background: transparent; + color: #3689e6; +} + +.p-chip-radius-b span{ + border-radius: 5px; +} + +.p-chip-dark span{ + color: #3e3e3e; +} + +.p-chip-dark input:checked + span{ + background: #3e3e3e; +} + +.p-chip input:disabled + span, +.p-chip input[disabled] + span{ + opacity: .5; + cursor: not-allowed; +} + +.p-chip-big span{ + font-size: 1.3rem; + padding: 1.5rem; + min-width: 80px; +} + +.p-form-checkbox-cont[disabled], +.p-form-label[disabled], +.p-form-text[disabled], +.p-form-text-alt[disabled], +.p-form-select[disabled], +.p-form-radio-cont[disabled]{ + filter: grayscale(1) opacity(.3); + pointer-events: none; +} diff --git a/v3/examples/android/frontend/public/puppertino/css/layout.css b/v3/examples/android/frontend/public/puppertino/css/layout.css new file mode 100644 index 000000000..1f9b36845 --- /dev/null +++ b/v3/examples/android/frontend/public/puppertino/css/layout.css @@ -0,0 +1,45 @@ +.p-large-title{ + font-size: 2.75rem; +} + +.p-layout h1 { + font-size: 2.25rem; +} + +.p-layout h2 { + font-size: 1.75rem; +} + +.p-layout h3 { + font-size: 1.58rem; +} + +.p-headline { + font-size: 1.34rem; + font-weight: bold; +} + +.p-layout p { + font-size: 1.15rem; +} + +.p-layout .link, +.p-layout input { + font-size: 0.813rem; +} + +.p-callout { + font-size: 1.14rem; +} + +.p-subhead { + font-size: 1.167rem; +} + +.p-footnote { + font-size: 1.07rem; +} + +.p-caption { + font-size: 0.91rem; +} diff --git a/v3/examples/android/frontend/public/puppertino/css/modals.css b/v3/examples/android/frontend/public/puppertino/css/modals.css new file mode 100644 index 000000000..4d718c4f7 --- /dev/null +++ b/v3/examples/android/frontend/public/puppertino/css/modals.css @@ -0,0 +1 @@ +/* Puppertino modals placeholder - local vendored */ diff --git a/v3/examples/android/frontend/public/puppertino/css/newfull.css b/v3/examples/android/frontend/public/puppertino/css/newfull.css new file mode 100644 index 000000000..622a2f364 --- /dev/null +++ b/v3/examples/android/frontend/public/puppertino/css/newfull.css @@ -0,0 +1,11 @@ +@import url('actions.css'); +@import url('buttons.css'); +@import url('layout.css'); +@import url('cards.css'); +@import url('color_palette.css'); +@import url('forms.css'); +@import url('modals.css'); +@import url('segmented-controls.css'); +@import url('shadows.css'); +@import url('tabs.css'); +@import url('dark_mode.css'); diff --git a/v3/examples/android/frontend/public/puppertino/css/segmented-controls.css b/v3/examples/android/frontend/public/puppertino/css/segmented-controls.css new file mode 100644 index 000000000..22819fd5f --- /dev/null +++ b/v3/examples/android/frontend/public/puppertino/css/segmented-controls.css @@ -0,0 +1 @@ +/* Puppertino segmented-controls placeholder - local vendored */ diff --git a/v3/examples/android/frontend/public/puppertino/css/shadows.css b/v3/examples/android/frontend/public/puppertino/css/shadows.css new file mode 100644 index 000000000..060a61658 --- /dev/null +++ b/v3/examples/android/frontend/public/puppertino/css/shadows.css @@ -0,0 +1 @@ +/* Puppertino shadows placeholder - local vendored */ diff --git a/v3/examples/android/frontend/public/puppertino/css/tabs.css b/v3/examples/android/frontend/public/puppertino/css/tabs.css new file mode 100644 index 000000000..61d1487ca --- /dev/null +++ b/v3/examples/android/frontend/public/puppertino/css/tabs.css @@ -0,0 +1 @@ +/* Puppertino tabs placeholder - local vendored */ diff --git a/v3/examples/android/frontend/public/puppertino/puppertino.css b/v3/examples/android/frontend/public/puppertino/puppertino.css new file mode 100644 index 000000000..905da220e --- /dev/null +++ b/v3/examples/android/frontend/public/puppertino/puppertino.css @@ -0,0 +1,1774 @@ +@charset "UTF-8"; +.p-btn { + background: #fff; + border: 1px solid #cacaca; + border-radius: 5px; + color: #333230; + display: inline-block; + font-family: -apple-system, "Inter", sans-serif; + font-size: 17px; + margin: 10px; + padding: 5px 20px; + text-decoration: none; + /* text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); */ +} +.p-btn-mob{ + padding: 10px 40px; + background: #0f75f5; + color: #fff; +} +.p-btn[disabled] { + background: #d3d3d3; + color: #555; + cursor: not-allowed; +} +.p-btn:disabled { + background: #d3d3d3; + color: #555; + cursor: not-allowed; +} +.p-btn-disabled { + background: #d3d3d3; + color: #555; + cursor: not-allowed; +} + +.p-prim-col { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border: 0; + color: #fff; +} + +.p-btn.p-prim-col:active { + background: #0f75f5; +} + +.p-btn-more::after { + content: "..."; +} + +.p-btn-round { + border: 0; + border-radius: 50px; + padding: 10px 30px; +} + +.p-btn-icon { + align-items: center; + background: #fff; + border: 2px solid currentColor; + border-radius: 50%; + box-shadow: 0 3px 10px -8px #000; + color: #0f75f5; + display: inline-flex; + font-weight: 900; + height: 36px; + justify-content: center; + margin: 5px; + text-align: center; + text-decoration: none; + width: 36px; +} + +.p-btn-scope { + background: #8e8e8e; + color: #fff; + margin: 5px; + padding: 2px 20px; +} +.p-btn-scope-unactive { + background: transparent; + border-color: transparent; + color: #212136; + transition: border-color 0.2s; +} +.p-btn-scope-unactive:hover { + border-color: #cacaca; +} +.p-btn-scope-disabled { + background: transparent; + color: #8e8e8e; + cursor: not-allowed; +} +.p-btn-scope-outline { + background: transparent; + color: #212136; +} + +.p-btn-scope-outline { + background: transparent; + color: #212136; +} + +.p-btn-outline { + background: none; + border-color: currentColor; +} + +.p-btn-outline-dash { + background: none; + border-color: currentColor; + border-style: dashed; +} + +.p-btn-direction { + color: #212136; + padding: 5px; + text-decoration: none; +} + +.p-btn-direction.p-btn-d-back::before { + content: "❬"; +} + +.p-btn-direction.p-btn-d-next::after { + content: "❭"; +} + +@media (max-width: 576px) { + .p-btn-big-sm { + border: 0; + border-radius: 0%; + bottom: 0; + font-size: 50px; + left: 0; + margin: 0; + padding: 10px 0; + position: fixed; + text-align: center; + width: 100%; + } +} + +/*END OF BUTTONS*/ + +.p-card { + background: rgba(255, 255, 255, 0.3); + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 3px; + box-shadow: 0 8px 10px -8px rgba(0, 0, 0, 0.1); + color: #000; + display: block; + margin-top: 30px; + text-decoration: none; +} +.p-card-image > img { + border-bottom: 3px solid var(--accent-article); + display: block; + margin: auto; + width: 100%; +} +.p-card-tags { + display: flex; + overflow: hidden; + position: relative; + width: 100%; +} +.p-card-tags::before { + background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 75%, white 100%); + content: ""; + height: 100%; + position: absolute; + right: 0; + top: 0; + width: 30%; +} +.p-card-tags span, +.p-card-tags a { + border: 1px solid #252525; + border-radius: 50px; + color: #252525; + margin: 5px; + padding: 5px 15px; + text-decoration: none; + transition: all 0.2s; +} +.p-card-tags a:hover { + background: #252525; + color: #000; +} +.p-card-title { + font-size: 2rem; + margin-bottom: 15px; + margin-top: 10px; +} +.p-card-content { + padding: 15px; + padding-top: 5px; +} +.p-card-text { + font-size: 17px; + margin-bottom: 10px; + margin-left: 10px; + margin-top: 0; +} + + +/* END OF CARDS*/ + +.p-strawberry { + background: #c6262e; +} + +.p-strawberry-100 { + background: #ff8c82; +} + +.p-strawberry-300 { + background: #ed5353; +} + +.p-strawberry-500 { + background: #c6262e; +} + +.p-strawberry-700 { + background: #a10705; +} + +.p-strawberry-900 { + background: #7a0000; +} + +.p-orange { + background: #f37329; +} + +.p-orange-100 { + background: #ffc27d; +} + +.p-orange-300 { + background: #ffa154; +} + +.p-orange-500 { + background: #f37329; +} + +.p-orange-700 { + background: #cc3b02; +} + +.p-orange-900 { + background: #a62100; +} + +.p-banana { + background: #f9c440; +} + +.p-banana-100 { + background: #fff394; +} + +.p-banana-300 { + background: #ffe16b; +} + +.p-banana-500 { + background: #f9c440; +} + +.p-banana-700 { + background: #d48e15; +} + +.p-banana-900 { + background: #ad5f00; +} + +.p-lime { + background: #68b723; +} + +.p-lime-100 { + background: #d1ff82; +} + +.p-lime-300 { + background: #9bdb4d; +} + +.p-lime-500 { + background: #68b723; +} + +.p-lime-700 { + background: #3a9104; +} + +.p-lime-900 { + background: #206b00; +} + +.p-mint { + background: #28bca3; +} + +.p-mint-100 { + background: #89ffdd; +} + +.p-mint-300 { + background: #43d6b5; +} + +.p-mint-500 { + background: #28bca3; +} + +.p-mint-700 { + background: #0e9a83; +} + +.p-mint-900 { + background: #007367; +} + +.p-blueberry { + background: #3689e6; +} + +.p-blueberry-100 { + background: #8cd5ff; +} + +.p-blueberry-300 { + background: #64baff; +} + +.p-blueberry-500 { + background: #3689e6; +} + +.p-blueberry-700 { + background: #0d52bf; +} + +.p-blueberry-900 { + background: #002e99; +} + +.p-grape { + background: #a56de2; +} + +.p-grape-100 { + background: #e4c6fa; +} + +.p-grape-300 { + background: #cd9ef7; +} + +.p-grape-500 { + background: #a56de2; +} + +.p-grape-700 { + background: #7239b3; +} + +.p-grape-900 { + background: #452981; +} + +.p-bubblegum { + background: #de3e80; +} + +.p-bubblegum-100 { + background: #fe9ab8; +} + +.p-bubblegum-300 { + background: #f4679d; +} + +.p-bubblegum-500 { + background: #de3e80; +} + +.p-bubblegum-700 { + background: #bc245d; +} + +.p-bubblegum-900 { + background: #910e38; +} + +.p-cocoa { + background: #715344; +} + +.p-cocoa-100 { + background: #a3907c; +} + +.p-cocoa-300 { + background: #8a715e; +} + +.p-cocoa-500 { + background: #715344; +} + +.p-cocoa-700 { + background: #57392d; +} + +.p-cocoa-900 { + background: #3d211b; +} + +.p-silver { + background: #abacae; +} + +.p-silver-100 { + background: #fafafa; +} + +.p-silver-300 { + background: #d4d4d4; +} + +.p-silver-500 { + background: #abacae; +} + +.p-silver-700 { + background: #7e8087; +} + +.p-silver-900 { + background: #555761; +} + +.p-slate { + background: #485a6c; +} + +.p-slate-100 { + background: #95a3ab; +} + +.p-slate-300 { + background: #667885; +} + +.p-slate-500 { + background: #485a6c; +} + +.p-slate-700 { + background: #273445; +} + +.p-slate-900 { + background: #0e141f; +} + +.p-dark { + background: #333; +} + +.p-dark-100 { + background: #666; + /* hehe */ +} + +.p-dark-300 { + background: #4d4d4d; +} + +.p-dark-500 { + background: #333; +} + +.p-dark-700 { + background: #1a1a1a; +} + +.p-dark-900 { + background: #000; +} + +.p-strawberry-color { + color: #c6262e; +} + +.p-strawberry-100-color { + color: #ff8c82; +} + +.p-strawberry-300-color { + color: #ed5353; +} + +.p-strawberry-500-color { + color: #c6262e; +} + +.p-strawberry-700-color { + color: #a10705; +} + +.p-strawberry-900-color { + color: #7a0000; +} + +.p-orange-color { + color: #f37329; +} + +.p-orange-100-color { + color: #ffc27d; +} + +.p-orange-300-color { + color: #ffa154; +} + +.p-orange-500-color { + color: #f37329; +} + +.p-orange-700-color { + color: #cc3b02; +} + +.p-orange-900-color { + color: #a62100; +} + +.p-banana-color { + color: #f9c440; +} + +.p-banana-100-color { + color: #fff394; +} + +.p-banana-300-color { + color: #ffe16b; +} + +.p-banana-500-color { + color: #f9c440; +} + +.p-banana-700-color { + color: #d48e15; +} + +.p-banana-900-color { + color: #ad5f00; +} + +.p-lime-color { + color: #68b723; +} + +.p-lime-100-color { + color: #d1ff82; +} + +.p-lime-300-color { + color: #9bdb4d; +} + +.p-lime-500-color { + color: #68b723; +} + +.p-lime-700-color { + color: #3a9104; +} + +.p-lime-900-color { + color: #206b00; +} + +.p-mint-color { + color: #28bca3; +} + +.p-mint-100-color { + color: #89ffdd; +} + +.p-mint-300-color { + color: #43d6b5; +} + +.p-mint-500-color { + color: #28bca3; +} + +.p-mint-700-color { + color: #0e9a83; +} + +.p-mint-900-color { + color: #007367; +} + +.p-blueberry-color { + color: #3689e6; +} + +.p-blueberry-100-color { + color: #8cd5ff; +} + +.p-blueberry-300-color { + color: #64baff; +} + +.p-blueberry-500-color { + color: #3689e6; +} + +.p-blueberry-700-color { + color: #0d52bf; +} + +.p-blueberry-900-color { + color: #002e99; +} + +.p-grape-color { + color: #a56de2; +} + +.p-grape-100-color { + color: #e4c6fa; +} + +.p-grape-300-color { + color: #cd9ef7; +} + +.p-grape-500-color { + color: #a56de2; +} + +.p-grape-700-color { + color: #7239b3; +} + +.p-grape-900-color { + color: #452981; +} + +.p-bubblegum-color { + color: #de3e80; +} + +.p-bubblegum-100-color { + color: #fe9ab8; +} + +.p-bubblegum-300-color { + color: #f4679d; +} + +.p-bubblegum-500-color { + color: #de3e80; +} + +.p-bubblegum-700-color { + color: #bc245d; +} + +.p-bubblegum-900-color { + color: #910e38; +} + +.p-cocoa-color { + color: #715344; +} + +.p-cocoa-100-color { + color: #a3907c; +} + +.p-cocoa-300-color { + color: #8a715e; +} + +.p-cocoa-500-color { + color: #715344; +} + +.p-cocoa-700-color { + color: #57392d; +} + +.p-cocoa-900-color { + color: #3d211b; +} + +.p-silver-color { + color: #abacae; +} + +.p-silver-100-color { + color: #fafafa; +} + +.p-silver-300-color { + color: #d4d4d4; +} + +.p-silver-500-color { + color: #abacae; +} + +.p-silver-700-color { + color: #7e8087; +} + +.p-silver-900-color { + color: #555761; +} + +.p-slate-color { + color: #485a6c; +} + +.p-slate-100-color { + color: #95a3ab; +} + +.p-slate-300-color { + color: #667885; +} + +.p-slate-500-color { + color: #485a6c; +} + +.p-slate-700-color { + color: #273445; +} + +.p-slate-900-color { + color: #0e141f; +} + +.p-dark-color { + color: #333; +} + +.p-dark-100-color { + color: #666; + /* hehe */ +} + +.p-dark-300-color { + color: #4d4d4d; +} + +.p-dark-500-color { + color: #333; +} + +.p-dark-700-color { + color: #1a1a1a; +} + +.p-dark-900-color { + color: #000; +} + +/* END OF COLORS */ + +:root { + --primary-col:linear-gradient(to bottom, #4fc5fa 0%,#0f75f5 100%); + --primary-col-ac:#0f75f5; + --bg-color:#fff; + --bg-hover-color:#f9f9f9; + --bg-front-col:#000; + --invalid-color:#d6513c; + --valid-color:#94d63c; +} + +.p-form-select { + border-radius: 5px; + display: inline-block; + font-family: -apple-system, "Inter", sans-serif; + margin: 10px; + position: relative; +} + +.p-form-select::before { + border-color: #fff transparent transparent; + border-style: solid; + border-width: 5px; + content: ""; + pointer-events: none; + position: absolute; + right: 5px; + top: calc(50% - 3px); + z-index: 3; +} + +.p-form-select::after { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border-bottom-right-radius: 5px; + border-top-right-radius: 5px; + bottom: 0; + content: ""; + display: block; + height: 100%; + pointer-events: none; + position: absolute; + right: 0; + top: 0; + width: 20px; +} + +.p-form-select > select { + -webkit-appearance: none; + background: #fff; + border: 1px solid #cacaca; + border-radius: 5px; + font-size: 14px; + margin: 0; + outline: none; + padding: 5px 30px 5px 10px; + position: relative; + width: 100%; +} + +.p-form-text:invalid, +.p-form-text-alt:invalid, +.p-form-select > select:invalid { + border-color: var(--invalid-color); +} + +.p-form-text:valid, +.p-form-text-alt:valid, +.p-form-select > select:valid { + border-color: var(--valid-color); +} + +.p-form-text:placeholder-shown, +.p-form-text-alt:placeholder-shown, +.p-form-select > select:placeholder-shown { + border-color: #cacaca; +} + +.p-form-text { + -webkit-appearance: none; + box-shadow: none; + background: #fff; + border: 1px solid #cacaca; + border-radius: 5px; + font-family: -apple-system, "Inter", sans-serif; + margin: 10px; + outline: 0; + padding: 5px; + resize: none; + transition: border-color 200ms; +} + +.p-form-text-alt { + -webkit-appearance: none; + box-shadow: none; + background: #fff; + border: 0px; + border-bottom: 2px solid #cacaca; + padding: 10px; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + margin: 10px; +} + +.p-form-text-alt::placeholder { + color: #cacaca; +} + +.p-form-text-alt:focus { + outline: 3px solid #bed8f9; +} + +.p-form-no-validate:valid, +.p-form-no-validate:invalid, +.p-form-no-validate > select:valid, +.p-form-no-validate > select:invalid { + border-color: #cacaca; +} + +.p-form-text:focus { + border-color: #0f75f5; +} + +textarea.p-form-text { + -webkit-appearance: none; + height: 100px; +} + +.p-form-truncated { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.p-form-text[type=password] { + font-family: caption; +} + +.p-form-label, +.p-form-radio-cont, +.p-form-checkbox-cont { + font-family: -apple-system, "Inter", sans-serif; +} + +.p-form-label { + display: block; +} + +.p-form-radio-cont, +.p-form-checkbox-cont { + align-items: center; + display: inline-flex; + margin: 0 10px; +} + +.p-form-radio-cont > input + span, +.p-form-checkbox-cont > input + span { + background: #fff; + border: 1px solid #cacaca; + border-radius: 50%; + cursor: pointer; + display: inline-block; + height: 20px; + margin-right: 5px; + position: relative; + transition: background 0.2s; + width: 20px; +} + +.p-form-radio-cont > input + span:hover { + background: #f9f9f9; +} + +.p-form-radio-cont > input, +.p-form-checkbox-cont > input { + opacity: 0; + pointer-events: none; + position: absolute; +} + +.p-form-radio-cont > input + span::after { + background: #fff; + border-radius: 50%; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + content: ""; + display: block; + height: 30%; + left: calc(50% - 15%); + opacity: 0; + position: absolute; + top: calc(50% - 15%); + transform: scale(2); + transition: opacity 0.2s, transform 0.3s; + width: 30%; +} + +.p-form-radio-cont > input:checked + span { + background: #0f75f5; +} + +.p-form-radio-cont > input:checked + span::after { + opacity: 1; + transform: scale(1); +} + +.p-form-checkbox-cont > input + span { + border-radius: 5px; +} + +.p-form-checkbox-cont > input:checked + span { + background: #0f75f5; +} + +.p-form-checkbox-cont > input + span::before, +.p-form-checkbox-cont > input + span::after { + background: #fff; + border-radius: 20px; + content: ""; + display: block; + height: 8%; + position: absolute; +} + +.p-form-checkbox-cont > input + span::before { + right: 30%; + top: 15%; + transform: rotate(-65deg); + transform-origin: top right; + width: 70%; +} + +.p-form-checkbox-cont > input + span::after { + left: 30%; + top: 43%; + transform: rotate(60deg); + transform-origin: top left; + width: 40%; +} + +.p-form-button { + -webkit-appearance: none; + background: #fff; + border: 1px solid #cacaca; + border-radius: 5px; + color: #333230; + display: inline-block; + font-size: 17px; + margin: 10px; + padding: 5px 20px; + text-decoration: none; + /* text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); */ +} + +.p-form-send { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border: 0; + color: #fff; +} + +.p-form-send:active { + background: #0f75f5; +} + +.p-form-invalid, +.p-form-invalid:placeholder-shown, +.p-form-invalid:valid, +.p-form-invalid:invalid { + border-color: var(--invalid-color); +} + +.p-form-valid, +.p-form-valid:placeholder-shown, +.p-form-valid:valid, +.p-form-valid:invalid { + border-color: var(--valid-color); +} + +.p-form-switch { + --width: 80px; + cursor: pointer; + display: inline-block; +} + +.p-form-switch > input:checked + span::after { + left: calc(100% - calc(var(--width) / 2.1)); +} + +.p-form-switch > input:checked + span { + background: #60c35b; +} + +.p-form-switch > span { + background: #e0e0e0; + border: 1px solid #d3d3d3; + border-radius: 500px; + display: block; + height: calc(var(--width) / 2); + overflow: hidden; + position: relative; + transition: all 0.2s; + width: var(--width); +} + +.p-form-switch > span::after { + background: #f9f9f9; + border-radius: 50%; + box-shadow: 0px 3px 1px rgba(0, 0, 0, 0.1), 0px 1px 1px rgba(0, 0, 0, 0.16), 0px 3px 8px rgba(0, 0, 0, 0.15); + content: ""; + height: 90%; + left: 3%; + position: absolute; + top: 4.5%; + transition: all 0.2s; + width: 45%; +} + +.p-form-switch > input { + display: none; +} + +input[type=range].p-form-range { + width: 100%; + margin: 11.5px 0; + background-color: transparent; + -webkit-appearance: none; +} +input[type=range].p-form-range:focus { + outline: none; +} +input[type=range].p-form-range::-webkit-slider-runnable-track { + background: #cacaca; + border: 0; + width: 100%; + height: 2px; + cursor: pointer; +} +input[type=range].p-form-range::-webkit-slider-thumb { + margin-top: -11.5px; + width: 25px; + height: 25px; + background: #ffffff; + border: 1px solid rgba(115, 115, 115, 0.6); + border-radius: 30px; + cursor: pointer; + box-shadow: 0 3px 1px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.16), 0 3px 8px rgba(0, 0, 0, 0.15); + -webkit-appearance: none; +} +input[type=range].p-form-range:focus::-webkit-slider-runnable-track { + background: #d7d7d7; +} +input[type=range].p-form-range::-moz-range-track { + background: #cacaca; + border: 0; + width: 100%; + height: 2px; + cursor: pointer; +} +input[type=range].p-form-range::-moz-range-thumb { + width: 25px; + height: 25px; + background: #ffffff; + border: 1px solid rgba(115, 115, 115, 0.6); + border-radius: 30px; + box-shadow: 0 3px 1px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.16), 0 3px 8px rgba(0, 0, 0, 0.15); + cursor: pointer; +} +input[type=range].p-form-range::-ms-track { + background: transparent; + border-color: transparent; + border-width: 26.5px 0; + color: transparent; + width: 100%; + height: 2px; + cursor: pointer; +} +input[type=range].p-form-range::-ms-fill-lower { + background: #bdbdbd; + border: 0; +} +input[type=range].p-form-range::-ms-fill-upper { + background: #cacaca; + border: 0; +} +input[type=range].p-form-range::-ms-thumb { + width: 25px; + height: 25px; + background: #ffffff; + border: 1px solid rgba(115, 115, 115, 0.6); + border-radius: 30px; + cursor: pointer; + box-shadow: 0 3px 1px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.16), 0 3px 8px rgba(0, 0, 0, 0.15); + margin-top: 0px; + /*Needed to keep the Edge thumb centred*/ +} +input[type=range].p-form-range:focus::-ms-fill-lower { + background: #cacaca; +} +input[type=range].p-form-range:focus::-ms-fill-upper { + background: #d7d7d7; +} +/*TODO: Use one of the selectors from https://stackoverflow.com/a/20541859/7077589 and figure out +how to remove the virtical space around the range input in IE*/ +@supports (-ms-ime-align:auto) { + /* Pre-Chromium Edge only styles, selector taken from hhttps://stackoverflow.com/a/32202953/7077589 */ + input[type=range].p-form-range { + margin: 0; + /*Edge starts the margin from the thumb, not the track as other browsers do*/ + } +} + + +/* END OF FORMS */ + +.p-layout .p-large-title { + font-size: 2.75rem; +} + +.p-layout h1 { + font-size: 2.25rem; +} + +.p-layout h2 { + font-size: 1.75rem; +} + +.p-layout h3 { + font-size: 1.58rem; +} + +.p-layout .p-headline { + font-size: 1.34rem; + font-weight: bold; +} + +.p-layout p { + font-size: 1.15rem; +} + +.p-layout a, +.p-layout input { + font-size: 1.14rem; +} + +.p-layout .p-callout { + font-size: 1.14rem; +} + +.p-layout .p-subhead { + font-size: 1.167rem; +} + +.p-layout .p-footnote { + font-size: 1.07rem; +} + +.p-layout .p-caption { + font-size: 0.91rem; +} + +/* END OF LAYOUT */ + +:root { + --font: -apple-system, "Inter", sans-serif; + --bg-hover-color: #f9f9f9; + --primary-col-ac: #0f75f5; +} + +.p-modal-opened { + overflow: hidden; +} + +.p-modal-background { + background: rgba(0, 0, 0, 0.3); + height: 100vh; + left: 0; + opacity: 0; + pointer-events: none; + position: fixed; + top: 0; + transition: all 0.3s; + width: 100vw; + z-index: 5; +} + +.p-modal { + background: rgba(255, 255, 255, 0.85); + border-radius: 20px; + top: calc(50% - 20vh); + bottom: unset; + box-shadow: 0 10px 20px -15px; + font-family: var(--font); + left: calc(50% - 20vw); + opacity: 0; + overflow: hidden; + pointer-events: none; + position: fixed; + text-align: center; + transform: scale(1.5); + transition: opacity 0.3s, transform 0.3s; + width: 40vw; + z-index: 9; +} + +.p-modal.active { + backdrop-filter: saturate(180%) blur(10px); + opacity: 1; + pointer-events: auto; + transform: scale(1); +} + +.p-modal-button-container { + border-radius: 20px; + display: flex; +} + +.p-modal-button-container > a { + border-top: 1px solid rgba(0, 0, 0, 0.1); + color: var(--primary-col-ac); + padding: 30px 0%; + text-decoration: none; + width: 100%; +} + +.p-modal-button-container > a:nth-child(2), +.p-modal-button-container > a:nth-child(3) { + border-left: 1px solid rgba(0, 0, 0, 0.1); +} + +.nowactive { + opacity: 1; + pointer-events: auto; +} + +.p-modal p { + padding: 0% 5%; +} + +@supports not (backdrop-filter: blur(5px)) { + .p-modal { + background: #fff; + } +} +@media (max-width: 568px) { + .p-modal { + bottom: 20%; + left: 15%; + top: unset; + width: 70vw; + } + + .p-modal p { + font-size: 15px; + padding: 0% 10%; + } + + .p-modal-button-container { + display: block; + } + + .p-modal-button-container > a { + border-left: 0 !important; + display: block; + padding: 2vh 0%; + } +} + +/* END OF MODALS */ + +.p-segmented-controls { + --color-segmented: #3689e6; + --color-lighter-segment: #d2e3f9; + background: #fff; + border: 1px solid var(--color-segmented); + border-radius: 5px; + display: flex; + flex-wrap: wrap; + font-family: -apple-system, "Inter", sans-serif; + margin-top: 10px; + overflow: hidden; + width: 100%; +} +.p-segmented-controls a { + color: var(--color-segmented); + flex: auto; + padding: 10px; + text-align: center; + text-decoration: none; + transition: all 0.5s; +} +.p-segmented-controls a.active { + background: var(--color-segmented); + color: #fff; +} +.p-segmented-controls a:not(:first-child) { + border-left: 1px solid currentColor; +} + +.p-segmented-radius { + border-radius: 30px; +} + +.p-segmented-internal-radius a, +.p-segmented-internal-radius a:not(:first-child) { + border: 0; + border-radius: 30px; +} + +.p-segmented-controls-alt a:not(:first-child) { + border: 0; +} +.p-segmented-controls-alt a:not(:first-child).active { + background: var(--color-lighter-segment); + color: var(--color-segmented); + font-weight: bold; +} + +.p-segmented-outline { + border: 2px solid var(--color-segmented); +} +.p-segmented-outline a:not(:first-child) { + border-left: 2px solid var(--color-segmented); +} + +.p-segmented-controls-outline-alt a:not(:first-child) { + border: 2px solid transparent; +} + +.p-segmented-controls-outline-alt { + border-radius: 30px; +} +.p-segmented-controls-outline-alt a { + border: 2px solid transparent; + border-radius: 30px; +} +.p-segmented-controls-outline-alt a.active { + background: #fff; + border-color: var(--color-segmented); + border-radius: 30px; + color: var(--color-segmented); + font-weight: bold; +} + +.p-segmented-grey { + --color-segmented: #555761; + --color-lighter-segment: #d4d4d4; +} + +/* END OF SEGMENTED CONTROLS */ + +.p-shadow-1 { + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); +} + +.p-shadow-2 { + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); +} + +.p-shadow-3 { + box-shadow: 0 10px 18px rgba(0, 0, 0, 0.3); +} + +.p-shadow-4 { + box-shadow: 0 25px 30px rgba(0, 0, 0, 0.2); +} + +.p-to-shadow-4, +.p-to-shadow-3, +.p-to-shadow-2, +.p-to-shadow-1 { + transition-timing-function: ease; + transition: box-shadow 0.5s; +} + +.p-to-shadow-1:hover { + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);> +} + +.p-to-shadow-2:hover { + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); +} + +.p-to-shadow-3:hover { + box-shadow: 0 10px 18px rgba(0, 0, 0, 0.3); +} + +.p-to-shadow-4:hover { + box-shadow: 0 25px 30px rgba(0, 0, 0, 0.2); +} + + +/* END OF SHADOWS */ + +.p-tabs-container { + background: #e3e3e3; + border: 1px solid #e0e0e0; + padding: 1em; +} + +.p-tabs-container.p-light { + background: none; + border: none; +} + +.p-tabs-container.p-light .p-panels { + margin-top: 0; + border-radius: 0; + padding: 0; +} + +.p-tabs { + display: flex; + justify-content: center; +} + +.p-tabs > :nth-of-type(1) { + border-radius: 5px 0 0 5px; +} + +.p-tabs > :last-child { + border-radius: 0 5px 5px 0; +} + +.p-tab { + margin: 0; + padding: 5px 35px; + background: #fff; + color: #333230; + text-decoration: none; + /* text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); */ + border: 1px solid #cacaca; + display: inline-block; + font-size: 17px; + font-family: -apple-system, "Inter", sans-serif; + cursor: pointer; +} + +.p-tab:focus { + outline: 0; +} + +.p-is-active { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border: 0; + color: #fff; +} + +.p-panels { + margin-top: 1em; + background: #fff; + border-radius: 3px; + position: relative; + padding: 0.8em; + overflow: hidden; +} + +.p-panel.p-is-active { + opacity: 1; + pointer-events: all; + background: none; + color: inherit; + position: static; +} + +.p-panel { + position: absolute; + opacity: 0; + pointer-events: none; +} + +@media (max-width: 768px) { + .p-tabs { + overflow: auto; + } + .p-tab { + font-size: 0.8em; + padding: 5px 28px; + } + .p-tabs-container { + padding: 0.8em; + } + + .p-panels { + padding: 0.8em; + } +} + +@media screen and (max-width: 496px) { + .p-tab { + text-align: center; + padding: 5px 18px; + } + .p-tabs-container { + padding: 0.5em; + } + + .p-panels { + padding: 0.5em; + margin-top: 0.5em; + } +} + +@media screen and (max-width: 378px) { + .p-tab { + text-align: center; + padding: 5px 10px; + } + .p-tabs-container { + padding: 0.5em; + } + + .p-panels { + padding: 0.5em; + margin-top: 0.5; + } +} + +.p-mobile-tabs { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + padding: 15px 0px; + border-top: 1px solid #949494; + background: rgba(202, 202, 202, 0.8); + backdrop-filter: blur(10px); + display: flex; + font-family: -apple-system, "Inter", sans-serif; +} + +.p-mobile-tabs > div { + flex: auto; + text-align: center; +} + +.p-mobile-tabs a { + text-decoration: none; + color: #555; + transition: color 0.5s; + display: inline-block; + font-size: 0.8rem; +} + +.p-mobile-tabs a.active { + color: #0f75f5; + font-weight: 600; +} + +.p-mobile-tabs svg { + display: block; + margin: auto; + margin-bottom: 0.2rem; +} + +.p-mobile-tabs--content { + display: none; +} + +.p-mobile-tabs--content.active { + display: block; +} + +/* END OF TABS */ + + +.p-action-background{ + background: rgba(0, 0, 0, 0.3); + height: 100vh; + left: 0; + opacity: 0; + pointer-events: none; + position: fixed; + top: 0; + transition: all 0.3s; + width: 100vw; + z-index: 5; +} + +.p-action-background.nowactive { + opacity: 1; + pointer-events: auto; +} + + +.p-action-big-container{ + position:fixed; + width: 100%; + box-sizing: border-box; + padding: 1rem 5vw; + bottom:0; +} + +.p-action-container{ + background: rgba(255, 255, 255, 0.8); + backdrop-filter: blur(10px); + display:block; + margin:auto; + margin-bottom: 10px; + border-radius: 10px; +} + +.p-action-big-container .p-action-container:first-child{ + margin-bottom:10px; +} + +.p-action--intern{ + display:block; + margin:auto; + text-align:center; + padding: 15px 0; + border-bottom: 1px solid #bfbfbf; + font-weight: 500; + color: #0f75f5; + text-decoration:none; +} + +.p-action-destructive{ + color: #c6262e; +} + +.p-action-neutral{ + color: #555761; +} + +.p-action-cancel, .p-action-container a:last-child{ + border-bottom:none; +} + +.p-action-cancel{ + font-weight:bold; +} + +.p-action-icon{ + position:relative; +} +.p-action-icon svg, .p-action-icon img{ + position:absolute; + left:5%; + top:50%; + transform:translateY(-50%); +} + +.p-action-icon-inline{ + text-align: left; + display: flex; + align-items: center; +} + +.p-action-icon-inline svg, .p-action-icon-inline img{ + margin-left: 5%; + margin-right: 3%; +} + +.p-action-title{ + padding: 30px 15px; + border-bottom: 1px solid #bfbfbf; +} + +.p-action-title--intern,.p-action-text{ + margin:0; + color:#555761; +} + +.p-action-title--intern{ + margin-bottom: .3rem; +} + +@supports not (backdrop-filter: blur(10px)) { + .p-action-container { + background: rgba(255,255,255,.95); + } +} + +.p-action-big-container{ + -webkit-transform: translateY(30%); + transform: translateY(30%); + opacity: 0; + transition: opacity 0.4s, transform 0.4s; + transition-timing-function: ease-in-out; +} + +.p-action-big-container.active { +-webkit-transform: translateY(0); + transform: translateY(0); + opacity: 1; +} + + +/* END OF ACTIONS */ diff --git a/v3/examples/android/frontend/public/style.css b/v3/examples/android/frontend/public/style.css new file mode 100644 index 000000000..8e4ccbd00 --- /dev/null +++ b/v3/examples/android/frontend/public/style.css @@ -0,0 +1,328 @@ +:root { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, + sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + /* Desktop defaults (mobile overrides below) */ + --bg: #ffffff; + --fg: #213547; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +/* Mobile bottom tabs layout (works on all themes) */ +.mobile-pane { + display: flex; + flex-direction: column; + width: 100%; + max-width: 520px; + height: 70vh; /* fixed-height main screen */ + border-radius: 12px; +} +.mobile-pane .p-mobile-tabs-content { + position: relative; + flex: 1 1 auto; + overflow: hidden; /* contain panels */ + display: flex; + flex-direction: column; +} +.p-mobile-tabs-content .p-mobile-tabs--content { + display: none; + overflow: auto; /* scroll inside content area */ + -webkit-overflow-scrolling: touch; + padding: 8px 8px 16px; +} +.p-mobile-tabs-content .p-mobile-tabs--content.active { display: block; } + +*, *::before, *::after { + box-sizing: border-box; +} + +/* Prefer system fonts on mobile; remove custom font to reduce bundle size */ + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +/* Remove generic button styling to allow Puppertino .btn to control buttons */ + +.result { + height: 20px; + line-height: 20px; +} + +html, +body { + height: 100%; + width: 100%; + overflow-x: hidden; /* prevent horizontal overflow */ + overflow-y: auto; /* allow vertical scroll if needed */ +} + +body { + margin: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-width: 320px; + /* Use small viewport units to avoid iOS Safari URL bar issues */ + min-height: 100svh; + height: auto; /* avoid forcing overflow */ + /* Equal responsive spacing top & bottom */ + padding-block: clamp(8px, 4vh, 48px); + color: var(--fg); + background-color: var(--bg); +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + /* Responsive spacing between elements */ + gap: clamp(8px, 2vh, 24px); + width: 100%; + max-width: 480px; + padding-inline: 16px; +} + +h1 { + /* Responsive heading size */ + font-size: clamp(1.6rem, 6vw, 3.2rem); + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + /* Responsive inner padding: horizontal only, no extra top/bottom */ + padding: 0 clamp(12px, 4vw, 32px); + text-align: center; +} + +.logo { + /* Consistent visual size across images: fix height, auto width */ + height: clamp(80px, 18vh, 140px); + width: auto; + max-width: 80vw; + padding: 0.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +/* Tabs: default hide panels, show one matching the checked radio */ +.tabs .tabs-content .tab-panel { display: none; } +.tabs input#tab-js:checked ~ .tabs-content [data-tab="tab-js"] { display: block; } +.tabs input#tab-go:checked ~ .tabs-content [data-tab="tab-go"] { display: block; } + +/* Sticky tabs header */ +.tabs .tabs-header { + position: sticky; + top: env(safe-area-inset-top); + z-index: 5; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + padding: 8px 0; + background: var(--bg); + backdrop-filter: saturate(1.2) blur(4px); +} + +/* Subtle divider under sticky header */ +.tabs .tabs-header::after { + content: ""; + grid-column: 1 / -1; + height: 1px; + background: rgba(0,0,0,0.08); + margin-top: 8px; +} + +/* Mobile-specific light mode */ +@media (max-width: 768px) and (prefers-color-scheme: light) { + :root { + --fg: #213547; + --bg: #ffffff; + } + + a:hover { + color: #747bff; + } + + /* allow Puppertino to style .btn */ + + .input-box .input { + color: #111827; + background-color: #f3f4f6; + border: 1px solid #e5e7eb; /* show border in light mode */ + border-radius: 8px; + } + + button:hover { + border-color: #d1d5db; /* slate-300 */ + } + + .input-box .input:focus { + border-color: #9ca3af; /* gray-400 */ + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); /* subtle focus ring */ + } +} + +/* let Puppertino handle .btn hover */ + +.input-box .input { + border: 1px solid transparent; /* default; themed in media queries */ + border-radius: 8px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + background-color: rgba(255, 255, 255, 1); + outline: 2px solid transparent; + outline-offset: 2px; +} + +/* Mobile-specific dark mode */ +@media (max-width: 768px) and (prefers-color-scheme: dark) { + :root { + color: rgba(255, 255, 255, 0.88); + --fg: rgba(255, 255, 255, 0.88); + --bg: #0f1115; /* deep dark background */ + } + + a { + color: #8ea2ff; + } + + a:hover { + color: #aab6ff; + } + + /* allow Puppertino to style .btn in dark mode */ + + .input-box .input { + background-color: #111827; /* gray-900 */ + color: #e5e7eb; + caret-color: #ffffff; + border: 1px solid #374151; /* slate-700 */ + } + + .input-box .input:hover, + .input-box .input:focus { + background-color: #0b1220; + border-color: #4b5563; /* slate-600 */ + } + + /* allow Puppertino to handle active state */ +} + +/* Mobile baseline overrides (apply to both light and dark) */ +@media (max-width: 768px) { + /* Prevent iOS zoom on focus */ + input, textarea, select, button { font-size: 16px; } + + /* Make layout vertical and scrollable */ + html, body { + height: auto; + min-height: 100svh; + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + } + + body { + padding-top: env(safe-area-inset-top); + padding-bottom: env(safe-area-inset-bottom); + position: static; + } + + .container { + width: 100%; + max-width: 520px; /* allow a bit wider on phones */ + align-items: center; /* keep content centered on mobile */ + text-align: center; + } + + /* Stack controls vertically with full-width tap targets */ + .input-box { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 10px; + } + + .input-box .input, + .input, + button, + .p-btn { + width: 100%; + min-height: 44px; /* comfortable touch target */ + } + + /* Tabs vertical and full-width */ + .tabs { + display: grid; + grid-template-columns: 1fr; + gap: 8px; + } + .tabs .p-btn { width: 100%; } + + /* Cap device info height for readability */ + #deviceInfo { + max-height: 30vh; + overflow: auto; + padding: 8px; + border-radius: 8px; + background: rgba(0,0,0,0.04); + } +} \ No newline at end of file diff --git a/v3/examples/android/frontend/public/wails.png b/v3/examples/android/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/android/frontend/public/wails.png differ diff --git a/v3/examples/android/frontend/vite.config.js b/v3/examples/android/frontend/vite.config.js new file mode 100644 index 000000000..87b093bc3 --- /dev/null +++ b/v3/examples/android/frontend/vite.config.js @@ -0,0 +1,11 @@ +import { defineConfig } from 'vite'; +import path from 'path'; + +export default defineConfig({ + resolve: { + alias: { + // Use the local repo runtime sources instead of the published package + '@wailsio/runtime': path.resolve(__dirname, '../../../internal/runtime/desktop/@wailsio/runtime/src/index.ts'), + }, + }, +}); diff --git a/v3/examples/android/go.mod b/v3/examples/android/go.mod new file mode 100644 index 000000000..ebca95414 --- /dev/null +++ b/v3/examples/android/go.mod @@ -0,0 +1,49 @@ +module changeme + +go 1.25 + +require github.com/wailsapp/wails/v3 v3.0.0-dev + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-git/go-git/v5 v5.16.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/lmittmann/tint v1.1.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.52.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/wailsapp/go-webview2 v1.0.23 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../../ diff --git a/v3/examples/android/go.sum b/v3/examples/android/go.sum new file mode 100644 index 000000000..56f1153ea --- /dev/null +++ b/v3/examples/android/go.sum @@ -0,0 +1,147 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= +github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/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-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/examples/android/greetservice.go b/v3/examples/android/greetservice.go new file mode 100644 index 000000000..8972c39cd --- /dev/null +++ b/v3/examples/android/greetservice.go @@ -0,0 +1,7 @@ +package main + +type GreetService struct{} + +func (g *GreetService) Greet(name string) string { + return "Hello " + name + "!" +} diff --git a/v3/examples/android/main.go b/v3/examples/android/main.go new file mode 100644 index 000000000..ba20103da --- /dev/null +++ b/v3/examples/android/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "embed" + _ "embed" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// Wails uses Go's `embed` package to embed the frontend files into the binary. +// Any files in the frontend/dist folder will be embedded into the binary and +// made available to the frontend. +// See https://pkg.go.dev/embed for more information. + +//go:embed all:frontend/dist +var assets embed.FS + +// main function serves as the application's entry point. It initializes the application, creates a window, +// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and +// logs any error that might occur. +func main() { + + // Create a new Wails application by providing the necessary options. + // Variables 'Name' and 'Description' are for application metadata. + // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files. + // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances. + app := application.New(application.Options{ + Name: "android", + Description: "A demo of using raw HTML & CSS", + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Android: application.AndroidOptions{ + // Android-specific options will go here + }, + }) + + // Create a new window with the necessary options. + // 'Title' is the title of the window. + // 'BackgroundColour' is the background colour of the window. + // 'URL' is the URL that will be loaded into the webview. + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + BackgroundColour: application.NewRGB(27, 38, 54), + URL: "/", + }) + + // Create a goroutine that emits an event containing the current time every second. + // The frontend can listen to this event and update the UI accordingly. + go func() { + for { + now := time.Now().Format(time.RFC1123) + app.Event.Emit("time", now) + time.Sleep(time.Second) + } + }() + + // Run the application. This blocks until the application has been exited. + err := app.Run() + + // If an error occurred while running the application, log it and exit. + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/android/main_android.go b/v3/examples/android/main_android.go new file mode 100644 index 000000000..70a716473 --- /dev/null +++ b/v3/examples/android/main_android.go @@ -0,0 +1,11 @@ +//go:build android + +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +func init() { + // Register main function to be called when the Android app initializes + // This is necessary because in c-shared build mode, main() is not automatically called + application.RegisterAndroidMain(main) +} diff --git a/v3/examples/badge-custom/README.md b/v3/examples/badge-custom/README.md new file mode 100644 index 000000000..ab4c5a3fb --- /dev/null +++ b/v3/examples/badge-custom/README.md @@ -0,0 +1,128 @@ +# Welcome to Your New Wails3 Project! +Now that you have your project set up, it's time to explore the custom badge features that Wails3 offers on **Windows**. + +## Exploring Custom Badge Features + +### Creating the Service with Custom Options (Windows Only) + +On Windows, you can customize the badge appearance with various options: + +```go +import "github.com/wailsapp/wails/v3/pkg/application" +import "github.com/wailsapp/wails/v3/pkg/services/badge" +import "image/color" + +// Create a badge service with custom options +options := badge.Options{ + TextColour: color.RGBA{255, 255, 255, 255}, // White text + BackgroundColour: color.RGBA{0, 0, 255, 255}, // Green background + FontName: "consolab.ttf", // Bold Consolas font + FontSize: 20, // Font size for single character + SmallFontSize: 14, // Font size for multiple characters +} + +badgeService := badge.NewWithOptions(options) + +// Register the service with the application +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(badgeService), + }, +}) +``` + +## Badge Operations + +### Setting a Badge + +Set a badge on the application tile/dock icon with the global options applied: + +#### Go +```go +// Set a default badge +badgeService.SetBadge("") + +// Set a numeric badge +badgeService.SetBadge("3") + +// Set a text badge +badgeService.SetBadge("New") +``` + +#### JS +```js +import {SetBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service"; + +// Set a default badge +SetBadge("") + +// Set a numeric badge +SetBadge("3") + +// Set a text badge +SetBadge("New") +``` + +### Setting a Custom Badge + +Set a badge on the application tile/dock icon with one-off options applied: + +#### Go +```go +// Set a default badge +badgeService.SetCustomBadge("") + +// Set a numeric badge +badgeService.SetCustomBadge("3") + +// Set a text badge +badgeService.SetCustomBadge("New") +``` + +#### JS +```js +import {SetCustomBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service"; + +const options = { + BackgroundColour: RGBA.createFrom({ + R: 0, + G: 255, + B: 255, + A: 255, + }), + FontName: "arialb.ttf", // System font + FontSize: 16, + SmallFontSize: 10, + TextColour: RGBA.createFrom({ + R: 0, + G: 0, + B: 0, + A: 255, + }), +} + +// Set a default badge +SetCustomBadge("", options) + +// Set a numeric badge +SetCustomBadge("3", options) + +// Set a text badge +SetCustomBadge("New", options) +``` + +### Removing a Badge + +Remove the badge from the application icon: + +#### Go +```go +badgeService.RemoveBadge() +``` + +#### JS +```js +import {RemoveBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service"; + +RemoveBadge() +``` \ No newline at end of file diff --git a/v3/examples/badge-custom/Taskfile.yml b/v3/examples/badge-custom/Taskfile.yml new file mode 100644 index 000000000..d1bcfaacf --- /dev/null +++ b/v3/examples/badge-custom/Taskfile.yml @@ -0,0 +1,34 @@ +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + +vars: + APP_NAME: "badge" + BIN_DIR: "bin" + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{OS}}:package" + + run: + summary: Runs the application + cmds: + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} + diff --git a/v3/examples/badge-custom/build/Taskfile.yml b/v3/examples/badge-custom/build/Taskfile.yml new file mode 100644 index 000000000..5f3517efc --- /dev/null +++ b/v3/examples/badge-custom/build/Taskfile.yml @@ -0,0 +1,86 @@ +version: '3' + +tasks: + go:mod:tidy: + summary: Runs `go mod tidy` + internal: true + cmds: + - go mod tidy + + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + label: build:frontend (PRODUCTION={{.PRODUCTION}}) + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/**/* + deps: + - task: install:frontend:deps + - task: generate:bindings + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + cmds: + - npm run {{.BUILD_COMMAND}} -q + env: + PRODUCTION: '{{.PRODUCTION | default "false"}}' + vars: + BUILD_COMMAND: '{{if eq .PRODUCTION "true"}}build{{else}}build:dev{{end}}' + + + generate:bindings: + label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}}) + summary: Generates bindings for the frontend + deps: + - task: go:mod:tidy + sources: + - "**/*.[jt]s" + - exclude: frontend/**/* + - frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output + - "**/*.go" + - go.mod + - go.sum + generates: + - frontend/bindings/**/* + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true -ts + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + sources: + - "appicon.png" + generates: + - "darwin/icons.icns" + - "windows/icon.ico" + cmds: + - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico + + dev:frontend: + summary: Runs the frontend in development mode + dir: frontend + deps: + - task: install:frontend:deps + cmds: + - npm run dev -- --port {{.VITE_PORT}} --strictPort + + update:build-assets: + summary: Updates the build assets + dir: build + cmds: + - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir . diff --git a/v3/examples/badge-custom/build/appicon.png b/v3/examples/badge-custom/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/badge-custom/build/appicon.png differ diff --git a/v3/examples/badge-custom/build/config.yml b/v3/examples/badge-custom/build/config.yml new file mode 100644 index 000000000..aaa3240fb --- /dev/null +++ b/v3/examples/badge-custom/build/config.yml @@ -0,0 +1,63 @@ +# This file contains the configuration for this project. +# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets. +# Note that this will overwrite any changes you have made to the assets. +version: '3' + +# This information is used to generate the build assets. +info: + companyName: "My Company" # The name of the company + productName: "My Product" # The name of the application + productIdentifier: "com.mycompany.myproduct" # The unique product identifier + description: "A program that does X" # The application description + copyright: "(c) 2025, My Company" # Copyright text + comments: "Some Product Comments" # Comments + version: "0.0.1" # The application version + +# Dev mode configuration +dev_mode: + root_path: . + log_level: warn + debounce: 1000 + ignore: + dir: + - .git + - node_modules + - frontend + - bin + file: + - .DS_Store + - .gitignore + - .gitkeep + watched_extension: + - "*.go" + git_ignore: true + executes: + - cmd: wails3 task common:install:frontend:deps + type: once + - cmd: wails3 task common:dev:frontend + type: background + - cmd: go mod tidy + type: blocking + - cmd: wails3 task build + type: blocking + - cmd: wails3 task run + type: primary + +# File Associations +# More information at: https://v3.wails.io/noit/done/yet +fileAssociations: +# - ext: wails +# name: Wails +# description: Wails Application File +# iconName: wailsFileIcon +# role: Editor +# - ext: jpg +# name: JPEG +# description: Image File +# iconName: jpegFileIcon +# role: Editor +# mimeType: image/jpeg # (optional) + +# Other data +other: + - name: My Other Data \ No newline at end of file diff --git a/v3/examples/badge-custom/build/darwin/Info.dev.plist b/v3/examples/badge-custom/build/darwin/Info.dev.plist new file mode 100644 index 000000000..4caddf720 --- /dev/null +++ b/v3/examples/badge-custom/build/darwin/Info.dev.plist @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + badge + CFBundleIdentifier + com.wails.badge + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © now, My Company + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/examples/badge-custom/build/darwin/Info.plist b/v3/examples/badge-custom/build/darwin/Info.plist new file mode 100644 index 000000000..0dc90b2e7 --- /dev/null +++ b/v3/examples/badge-custom/build/darwin/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + badge + CFBundleIdentifier + com.wails.badge + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © now, My Company + + \ No newline at end of file diff --git a/v3/examples/badge-custom/build/darwin/Taskfile.yml b/v3/examples/badge-custom/build/darwin/Taskfile.yml new file mode 100644 index 000000000..f0791fea9 --- /dev/null +++ b/v3/examples/badge-custom/build/darwin/Taskfile.yml @@ -0,0 +1,81 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Creates a production build of the application + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + env: + GOOS: darwin + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + CGO_CFLAGS: "-mmacosx-version-min=10.15" + CGO_LDFLAGS: "-mmacosx-version-min=10.15" + MACOSX_DEPLOYMENT_TARGET: "10.15" + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + build:universal: + summary: Builds darwin universal binary (arm64 + amd64) + deps: + - task: build + vars: + ARCH: amd64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" + - task: build + vars: + ARCH: arm64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + cmds: + - lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + - rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + + package: + summary: Packages a production build of the application into a `.app` bundle + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:app:bundle + + package:universal: + summary: Packages darwin universal binary (arm64 + amd64) + deps: + - task: build:universal + cmds: + - task: create:app:bundle + + + create:app:bundle: + summary: Creates an `.app` bundle + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS + - cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app + + run: + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS + - cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app + - '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}' diff --git a/v3/examples/badge-custom/build/darwin/icons.icns b/v3/examples/badge-custom/build/darwin/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/badge-custom/build/darwin/icons.icns differ diff --git a/v3/examples/badge-custom/build/linux/Taskfile.yml b/v3/examples/badge-custom/build/linux/Taskfile.yml new file mode 100644 index 000000000..560cc9c92 --- /dev/null +++ b/v3/examples/badge-custom/build/linux/Taskfile.yml @@ -0,0 +1,119 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Linux + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: linux + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application for Linux + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:appimage + - task: create:deb + - task: create:rpm + - task: create:aur + + create:appimage: + summary: Creates an AppImage + dir: build/linux/appimage + deps: + - task: build + vars: + PRODUCTION: "true" + - task: generate:dotdesktop + cmds: + - cp {{.APP_BINARY}} {{.APP_NAME}} + - cp ../../appicon.png appicon.png + - wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build + vars: + APP_NAME: '{{.APP_NAME}}' + APP_BINARY: '../../../bin/{{.APP_NAME}}' + ICON: '../../appicon.png' + DESKTOP_FILE: '../{{.APP_NAME}}.desktop' + OUTPUT_DIR: '../../../bin' + + create:deb: + summary: Creates a deb package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:deb + + create:rpm: + summary: Creates a rpm package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:rpm + + create:aur: + summary: Creates a arch linux packager package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:aur + + generate:deb: + summary: Creates a deb package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:rpm: + summary: Creates a rpm package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:aur: + summary: Creates a arch linux packager package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:dotdesktop: + summary: Generates a `.desktop` file + dir: build + cmds: + - mkdir -p {{.ROOT_DIR}}/build/linux/appimage + - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}" + vars: + APP_NAME: '{{.APP_NAME}}' + EXEC: '{{.APP_NAME}}' + ICON: 'appicon' + CATEGORIES: 'Development;' + OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}' diff --git a/v3/examples/badge-custom/build/linux/appimage/build.sh b/v3/examples/badge-custom/build/linux/appimage/build.sh new file mode 100644 index 000000000..85901c34e --- /dev/null +++ b/v3/examples/badge-custom/build/linux/appimage/build.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Copyright (c) 2018-Present Lea Anthony +# SPDX-License-Identifier: MIT + +# Fail script on any error +set -euxo pipefail + +# Define variables +APP_DIR="${APP_NAME}.AppDir" + +# Create AppDir structure +mkdir -p "${APP_DIR}/usr/bin" +cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/" +cp "${ICON_PATH}" "${APP_DIR}/" +cp "${DESKTOP_FILE}" "${APP_DIR}/" + +if [[ $(uname -m) == *x86_64* ]]; then + # Download linuxdeploy and make it executable + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + + # Run linuxdeploy to bundle the application + ./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage +else + # Download linuxdeploy and make it executable (arm64) + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage + chmod +x linuxdeploy-aarch64.AppImage + + # Run linuxdeploy to bundle the application (arm64) + ./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage +fi + +# Rename the generated AppImage +mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage" + diff --git a/v3/examples/badge-custom/build/linux/nfpm/nfpm.yaml b/v3/examples/badge-custom/build/linux/nfpm/nfpm.yaml new file mode 100644 index 000000000..5b7ea9d02 --- /dev/null +++ b/v3/examples/badge-custom/build/linux/nfpm/nfpm.yaml @@ -0,0 +1,50 @@ +# Feel free to remove those if you don't want/need to use them. +# Make sure to check the documentation at https://nfpm.goreleaser.com +# +# The lines below are called `modelines`. See `:help modeline` + +name: "badge" +arch: ${GOARCH} +platform: "linux" +version: "0.1.0" +section: "default" +priority: "extra" +maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> +description: "My Product Description" +vendor: "My Company" +homepage: "https://wails.io" +license: "MIT" +release: "1" + +contents: + - src: "./bin/badge" + dst: "/usr/local/bin/badge" + - src: "./build/appicon.png" + dst: "/usr/share/icons/hicolor/128x128/apps/badge.png" + - src: "./build/linux/badge.desktop" + dst: "/usr/share/applications/badge.desktop" + +depends: + - gtk3 + - libwebkit2gtk + +# replaces: +# - foobar +# provides: +# - bar +# depends: +# - gtk3 +# - libwebkit2gtk +# recommends: +# - whatever +# suggests: +# - something-else +# conflicts: +# - not-foo +# - not-bar +# changelog: "changelog.yaml" +# scripts: +# preinstall: ./build/linux/nfpm/scripts/preinstall.sh +# postinstall: ./build/linux/nfpm/scripts/postinstall.sh +# preremove: ./build/linux/nfpm/scripts/preremove.sh +# postremove: ./build/linux/nfpm/scripts/postremove.sh diff --git a/v3/examples/badge-custom/build/linux/nfpm/scripts/postinstall.sh b/v3/examples/badge-custom/build/linux/nfpm/scripts/postinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge-custom/build/linux/nfpm/scripts/postinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge-custom/build/linux/nfpm/scripts/postremove.sh b/v3/examples/badge-custom/build/linux/nfpm/scripts/postremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge-custom/build/linux/nfpm/scripts/postremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge-custom/build/linux/nfpm/scripts/preinstall.sh b/v3/examples/badge-custom/build/linux/nfpm/scripts/preinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge-custom/build/linux/nfpm/scripts/preinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge-custom/build/linux/nfpm/scripts/preremove.sh b/v3/examples/badge-custom/build/linux/nfpm/scripts/preremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge-custom/build/linux/nfpm/scripts/preremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge-custom/build/windows/Taskfile.yml b/v3/examples/badge-custom/build/windows/Taskfile.yml new file mode 100644 index 000000000..534f4fb31 --- /dev/null +++ b/v3/examples/badge-custom/build/windows/Taskfile.yml @@ -0,0 +1,63 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Windows + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - task: generate:syso + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe + - cmd: powershell Remove-item *.syso + platforms: [windows] + - cmd: rm -f *.syso + platforms: [linux, darwin] + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: windows + CGO_ENABLED: 0 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application into a `.exe` bundle + cmds: + - task: create:nsis:installer + + generate:syso: + summary: Generates Windows `.syso` file + dir: build + cmds: + - wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso + vars: + ARCH: '{{.ARCH | default ARCH}}' + + create:nsis:installer: + summary: Creates an NSIS installer + dir: build/windows/nsis + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + # Create the Microsoft WebView2 bootstrapper if it doesn't exist + - wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis" + - makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi + vars: + ARCH: '{{.ARCH | default ARCH}}' + ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}.exe' diff --git a/v3/examples/badge-custom/build/windows/icon.ico b/v3/examples/badge-custom/build/windows/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/badge-custom/build/windows/icon.ico differ diff --git a/v3/examples/badge-custom/build/windows/info.json b/v3/examples/badge-custom/build/windows/info.json new file mode 100644 index 000000000..850b2b5b0 --- /dev/null +++ b/v3/examples/badge-custom/build/windows/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "0.1.0" + }, + "info": { + "0000": { + "ProductVersion": "0.1.0", + "CompanyName": "My Company", + "FileDescription": "My Product Description", + "LegalCopyright": "© now, My Company", + "ProductName": "My Product", + "Comments": "This is a comment" + } + } +} \ No newline at end of file diff --git a/v3/examples/badge-custom/build/windows/nsis/project.nsi b/v3/examples/badge-custom/build/windows/nsis/project.nsi new file mode 100644 index 000000000..985b8e207 --- /dev/null +++ b/v3/examples/badge-custom/build/windows/nsis/project.nsi @@ -0,0 +1,112 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the wails_tools.nsh file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "my-project" # Default "badge" +## !define INFO_COMPANYNAME "My Company" # Default "My Company" +## !define INFO_PRODUCTNAME "My Product Name" # Default "My Product" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.1.0" +## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "© now, My Company" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/v3/examples/badge-custom/build/windows/nsis/wails_tools.nsh b/v3/examples/badge-custom/build/windows/nsis/wails_tools.nsh new file mode 100644 index 000000000..6fc10ab79 --- /dev/null +++ b/v3/examples/badge-custom/build/windows/nsis/wails_tools.nsh @@ -0,0 +1,212 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "badge" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "My Company" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "My Product" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "0.1.0" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "© now, My Company" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + +!macroend \ No newline at end of file diff --git a/v3/examples/badge-custom/build/windows/wails.exe.manifest b/v3/examples/badge-custom/build/windows/wails.exe.manifest new file mode 100644 index 000000000..1d8992a3d --- /dev/null +++ b/v3/examples/badge-custom/build/windows/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/v3/examples/badge-custom/frontend/Inter Font License.txt b/v3/examples/badge-custom/frontend/Inter Font License.txt new file mode 100644 index 000000000..b525cbf3a --- /dev/null +++ b/v3/examples/badge-custom/frontend/Inter Font License.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/dockservice.ts b/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/dockservice.ts new file mode 100644 index 000000000..abf1f22e4 --- /dev/null +++ b/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/dockservice.ts @@ -0,0 +1,53 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Service represents the dock service + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * HideAppIcon hides the app icon in the dock/taskbar. + */ +export function HideAppIcon(): $CancellablePromise { + return $Call.ByID(3413658144); +} + +/** + * RemoveBadge removes the badge label from the application icon. + * This method ensures the badge call is made on the main thread to avoid crashes. + */ +export function RemoveBadge(): $CancellablePromise { + return $Call.ByID(2752757297); +} + +/** + * SetBadge sets the badge label on the application icon. + * This method ensures the badge call is made on the main thread to avoid crashes. + */ +export function SetBadge(label: string): $CancellablePromise { + return $Call.ByID(1717705661, label); +} + +/** + * SetCustomBadge sets the badge label on the application icon with custom options. + * This method ensures the badge call is made on the main thread to avoid crashes. + */ +export function SetCustomBadge(label: string, options: $models.BadgeOptions): $CancellablePromise { + return $Call.ByID(2730169760, label, options); +} + +/** + * ShowAppIcon shows the app icon in the dock/taskbar. + */ +export function ShowAppIcon(): $CancellablePromise { + return $Call.ByID(3409697379); +} diff --git a/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/index.ts b/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/index.ts new file mode 100644 index 000000000..fbdaf19f3 --- /dev/null +++ b/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as DockService from "./dockservice.js"; +export { + DockService +}; + +export { + BadgeOptions +} from "./models.js"; diff --git a/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/models.ts b/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/models.ts new file mode 100644 index 000000000..f97c8a4c6 --- /dev/null +++ b/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/models.ts @@ -0,0 +1,61 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as color$0 from "../../../../../../../image/color/models.js"; + +/** + * BadgeOptions represents options for customizing badge appearance + */ +export class BadgeOptions { + "TextColour": color$0.RGBA; + "BackgroundColour": color$0.RGBA; + "FontName": string; + "FontSize": number; + "SmallFontSize": number; + + /** Creates a new BadgeOptions instance. */ + constructor($$source: Partial = {}) { + if (!("TextColour" in $$source)) { + this["TextColour"] = (new color$0.RGBA()); + } + if (!("BackgroundColour" in $$source)) { + this["BackgroundColour"] = (new color$0.RGBA()); + } + if (!("FontName" in $$source)) { + this["FontName"] = ""; + } + if (!("FontSize" in $$source)) { + this["FontSize"] = 0; + } + if (!("SmallFontSize" in $$source)) { + this["SmallFontSize"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new BadgeOptions instance from a string or object. + */ + static createFrom($$source: any = {}): BadgeOptions { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("TextColour" in $$parsedSource) { + $$parsedSource["TextColour"] = $$createField0_0($$parsedSource["TextColour"]); + } + if ("BackgroundColour" in $$parsedSource) { + $$parsedSource["BackgroundColour"] = $$createField1_0($$parsedSource["BackgroundColour"]); + } + return new BadgeOptions($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = color$0.RGBA.createFrom; diff --git a/v3/examples/badge-custom/frontend/bindings/image/color/index.ts b/v3/examples/badge-custom/frontend/bindings/image/color/index.ts new file mode 100644 index 000000000..97b507b08 --- /dev/null +++ b/v3/examples/badge-custom/frontend/bindings/image/color/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + RGBA +} from "./models.js"; diff --git a/v3/examples/badge-custom/frontend/bindings/image/color/models.ts b/v3/examples/badge-custom/frontend/bindings/image/color/models.ts new file mode 100644 index 000000000..0d4eab56d --- /dev/null +++ b/v3/examples/badge-custom/frontend/bindings/image/color/models.ts @@ -0,0 +1,46 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +/** + * RGBA represents a traditional 32-bit alpha-premultiplied color, having 8 + * bits for each of red, green, blue and alpha. + * + * An alpha-premultiplied color component C has been scaled by alpha (A), so + * has valid values 0 <= C <= A. + */ +export class RGBA { + "R": number; + "G": number; + "B": number; + "A": number; + + /** Creates a new RGBA instance. */ + constructor($$source: Partial = {}) { + if (!("R" in $$source)) { + this["R"] = 0; + } + if (!("G" in $$source)) { + this["G"] = 0; + } + if (!("B" in $$source)) { + this["B"] = 0; + } + if (!("A" in $$source)) { + this["A"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new RGBA instance from a string or object. + */ + static createFrom($$source: any = {}): RGBA { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new RGBA($$parsedSource as Partial); + } +} diff --git a/v3/examples/badge-custom/frontend/dist/Inter-Medium.ttf b/v3/examples/badge-custom/frontend/dist/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/badge-custom/frontend/dist/Inter-Medium.ttf differ diff --git a/v3/examples/badge-custom/frontend/dist/assets/index-DOJs-2ns.js b/v3/examples/badge-custom/frontend/dist/assets/index-DOJs-2ns.js new file mode 100644 index 000000000..780f115b6 --- /dev/null +++ b/v3/examples/badge-custom/frontend/dist/assets/index-DOJs-2ns.js @@ -0,0 +1,1415 @@ +var __defProp = Object.defineProperty; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); +(function polyfill() { + const relList = document.createElement("link").relList; + if (relList && relList.supports && relList.supports("modulepreload")) { + return; + } + for (const link of document.querySelectorAll('link[rel="modulepreload"]')) { + processPreload(link); + } + new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type !== "childList") { + continue; + } + for (const node of mutation.addedNodes) { + if (node.tagName === "LINK" && node.rel === "modulepreload") + processPreload(node); + } + } + }).observe(document, { childList: true, subtree: true }); + function getFetchOpts(link) { + const fetchOpts = {}; + if (link.integrity) fetchOpts.integrity = link.integrity; + if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy; + if (link.crossOrigin === "use-credentials") + fetchOpts.credentials = "include"; + else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit"; + else fetchOpts.credentials = "same-origin"; + return fetchOpts; + } + function processPreload(link) { + if (link.ep) + return; + link.ep = true; + const fetchOpts = getFetchOpts(link); + fetch(link.href, fetchOpts); + } +})(); +const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; +function nanoid(size = 21) { + let id = ""; + let i = size | 0; + while (i--) { + id += urlAlphabet[Math.random() * 64 | 0]; + } + return id; +} +const runtimeURL = window.location.origin + "/wails/runtime"; +const objectNames = Object.freeze({ + Call: 0, + Clipboard: 1, + Application: 2, + Events: 3, + ContextMenu: 4, + Dialog: 5, + Window: 6, + Screens: 7, + System: 8, + Browser: 9, + CancelCall: 10 +}); +let clientId = nanoid(); +function newRuntimeCaller(object, windowName = "") { + return function(method, args = null) { + return runtimeCallWithID(object, method, windowName, args); + }; +} +async function runtimeCallWithID(objectID, method, windowName, args) { + var _a2, _b; + let url = new URL(runtimeURL); + url.searchParams.append("object", objectID.toString()); + url.searchParams.append("method", method.toString()); + if (args) { + url.searchParams.append("args", JSON.stringify(args)); + } + let headers = { + ["x-wails-client-id"]: clientId + }; + if (windowName) { + headers["x-wails-window-name"] = windowName; + } + let response = await fetch(url, { headers }); + if (!response.ok) { + throw new Error(await response.text()); + } + if (((_b = (_a2 = response.headers.get("Content-Type")) === null || _a2 === void 0 ? void 0 : _a2.indexOf("application/json")) !== null && _b !== void 0 ? _b : -1) !== -1) { + return response.json(); + } else { + return response.text(); + } +} +newRuntimeCaller(objectNames.System); +const _invoke = function() { + var _a2, _b, _c, _d, _e; + try { + if ((_b = (_a2 = window.chrome) === null || _a2 === void 0 ? void 0 : _a2.webview) === null || _b === void 0 ? void 0 : _b.postMessage) { + return window.chrome.webview.postMessage.bind(window.chrome.webview); + } else if ((_e = (_d = (_c = window.webkit) === null || _c === void 0 ? void 0 : _c.messageHandlers) === null || _d === void 0 ? void 0 : _d["external"]) === null || _e === void 0 ? void 0 : _e.postMessage) { + return window.webkit.messageHandlers["external"].postMessage.bind(window.webkit.messageHandlers["external"]); + } + } catch (e) { + } + console.warn("\n%c⚠️ Browser Environment Detected %c\n\n%cOnly UI previews are available in the browser. For full functionality, please run the application in desktop mode.\nMore information at: https://v3.wails.io/learn/build/#using-a-browser-for-development\n", "background: #ffffff; color: #000000; font-weight: bold; padding: 4px 8px; border-radius: 4px; border: 2px solid #000000;", "background: transparent;", "color: #ffffff; font-style: italic; font-weight: bold;"); + return null; +}(); +function invoke(msg) { + _invoke === null || _invoke === void 0 ? void 0 : _invoke(msg); +} +function IsWindows() { + return window._wails.environment.OS === "windows"; +} +function IsDebug() { + return Boolean(window._wails.environment.Debug); +} +function canTrackButtons() { + return new MouseEvent("mousedown").buttons === 0; +} +function eventTarget(event) { + var _a2; + if (event.target instanceof HTMLElement) { + return event.target; + } else if (!(event.target instanceof HTMLElement) && event.target instanceof Node) { + return (_a2 = event.target.parentElement) !== null && _a2 !== void 0 ? _a2 : document.body; + } else { + return document.body; + } +} +document.addEventListener("DOMContentLoaded", () => { +}); +window.addEventListener("contextmenu", contextMenuHandler); +const call$2 = newRuntimeCaller(objectNames.ContextMenu); +const ContextMenuOpen = 0; +function openContextMenu(id, x, y, data) { + void call$2(ContextMenuOpen, { id, x, y, data }); +} +function contextMenuHandler(event) { + const target = eventTarget(event); + const customContextMenu = window.getComputedStyle(target).getPropertyValue("--custom-contextmenu").trim(); + if (customContextMenu) { + event.preventDefault(); + const data = window.getComputedStyle(target).getPropertyValue("--custom-contextmenu-data"); + openContextMenu(customContextMenu, event.clientX, event.clientY, data); + } else { + processDefaultContextMenu(event, target); + } +} +function processDefaultContextMenu(event, target) { + if (IsDebug()) { + return; + } + switch (window.getComputedStyle(target).getPropertyValue("--default-contextmenu").trim()) { + case "show": + return; + case "hide": + event.preventDefault(); + return; + } + if (target.isContentEditable) { + return; + } + const selection = window.getSelection(); + const hasSelection = selection && selection.toString().length > 0; + if (hasSelection) { + for (let i = 0; i < selection.rangeCount; i++) { + const range = selection.getRangeAt(i); + const rects = range.getClientRects(); + for (let j = 0; j < rects.length; j++) { + const rect = rects[j]; + if (document.elementFromPoint(rect.left, rect.top) === target) { + return; + } + } + } + } + if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) { + if (hasSelection || !target.readOnly && !target.disabled) { + return; + } + } + event.preventDefault(); +} +function GetFlag(key) { + try { + return window._wails.flags[key]; + } catch (e) { + throw new Error("Unable to retrieve flag '" + key + "': " + e, { cause: e }); + } +} +let canDrag = false; +let dragging = false; +let resizable = false; +let canResize = false; +let resizing = false; +let resizeEdge = ""; +let defaultCursor = "auto"; +let buttons = 0; +const buttonsTracked = canTrackButtons(); +window._wails = window._wails || {}; +window._wails.setResizable = (value) => { + resizable = value; + if (!resizable) { + canResize = resizing = false; + setResize(); + } +}; +window.addEventListener("mousedown", update, { capture: true }); +window.addEventListener("mousemove", update, { capture: true }); +window.addEventListener("mouseup", update, { capture: true }); +for (const ev of ["click", "contextmenu", "dblclick"]) { + window.addEventListener(ev, suppressEvent, { capture: true }); +} +function suppressEvent(event) { + if (dragging || resizing) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } +} +const MouseDown = 0; +const MouseUp = 1; +const MouseMove = 2; +function update(event) { + let eventType, eventButtons = event.buttons; + switch (event.type) { + case "mousedown": + eventType = MouseDown; + if (!buttonsTracked) { + eventButtons = buttons | 1 << event.button; + } + break; + case "mouseup": + eventType = MouseUp; + if (!buttonsTracked) { + eventButtons = buttons & ~(1 << event.button); + } + break; + default: + eventType = MouseMove; + if (!buttonsTracked) { + eventButtons = buttons; + } + break; + } + let released = buttons & ~eventButtons; + let pressed = eventButtons & ~buttons; + buttons = eventButtons; + if (eventType === MouseDown && !(pressed & event.button)) { + released |= 1 << event.button; + pressed |= 1 << event.button; + } + if (eventType !== MouseMove && resizing || dragging && (eventType === MouseDown || event.button !== 0)) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } + if (released & 1) { + primaryUp(); + } + if (pressed & 1) { + primaryDown(event); + } + if (eventType === MouseMove) { + onMouseMove(event); + } +} +function primaryDown(event) { + canDrag = false; + canResize = false; + if (!IsWindows()) { + if (event.type === "mousedown" && event.button === 0 && event.detail !== 1) { + return; + } + } + if (resizeEdge) { + canResize = true; + return; + } + const target = eventTarget(event); + const style = window.getComputedStyle(target); + canDrag = style.getPropertyValue("--wails-draggable").trim() === "drag" && (event.offsetX - parseFloat(style.paddingLeft) < target.clientWidth && event.offsetY - parseFloat(style.paddingTop) < target.clientHeight); +} +function primaryUp(event) { + canDrag = false; + dragging = false; + canResize = false; + resizing = false; +} +const cursorForEdge = Object.freeze({ + "se-resize": "nwse-resize", + "sw-resize": "nesw-resize", + "nw-resize": "nwse-resize", + "ne-resize": "nesw-resize", + "w-resize": "ew-resize", + "n-resize": "ns-resize", + "s-resize": "ns-resize", + "e-resize": "ew-resize" +}); +function setResize(edge) { + if (edge) { + if (!resizeEdge) { + defaultCursor = document.body.style.cursor; + } + document.body.style.cursor = cursorForEdge[edge]; + } else if (!edge && resizeEdge) { + document.body.style.cursor = defaultCursor; + } + resizeEdge = edge || ""; +} +function onMouseMove(event) { + if (canResize && resizeEdge) { + resizing = true; + invoke("wails:resize:" + resizeEdge); + } else if (canDrag) { + dragging = true; + invoke("wails:drag"); + } + if (dragging || resizing) { + canDrag = canResize = false; + return; + } + if (!resizable || !IsWindows()) { + if (resizeEdge) { + setResize(); + } + return; + } + const resizeHandleHeight = GetFlag("system.resizeHandleHeight") || 5; + const resizeHandleWidth = GetFlag("system.resizeHandleWidth") || 5; + const cornerExtra = GetFlag("resizeCornerExtra") || 10; + const rightBorder = window.outerWidth - event.clientX < resizeHandleWidth; + const leftBorder = event.clientX < resizeHandleWidth; + const topBorder = event.clientY < resizeHandleHeight; + const bottomBorder = window.outerHeight - event.clientY < resizeHandleHeight; + const rightCorner = window.outerWidth - event.clientX < resizeHandleWidth + cornerExtra; + const leftCorner = event.clientX < resizeHandleWidth + cornerExtra; + const topCorner = event.clientY < resizeHandleHeight + cornerExtra; + const bottomCorner = window.outerHeight - event.clientY < resizeHandleHeight + cornerExtra; + if (!leftCorner && !topCorner && !bottomCorner && !rightCorner) { + setResize(); + } else if (rightCorner && bottomCorner) + setResize("se-resize"); + else if (leftCorner && bottomCorner) + setResize("sw-resize"); + else if (leftCorner && topCorner) + setResize("nw-resize"); + else if (topCorner && rightCorner) + setResize("ne-resize"); + else if (leftBorder) + setResize("w-resize"); + else if (topBorder) + setResize("n-resize"); + else if (bottomBorder) + setResize("s-resize"); + else if (rightBorder) + setResize("e-resize"); + else + setResize(); +} +var fnToStr = Function.prototype.toString; +var reflectApply = typeof Reflect === "object" && Reflect !== null && Reflect.apply; +var badArrayLike; +var isCallableMarker; +if (typeof reflectApply === "function" && typeof Object.defineProperty === "function") { + try { + badArrayLike = Object.defineProperty({}, "length", { + get: function() { + throw isCallableMarker; + } + }); + isCallableMarker = {}; + reflectApply(function() { + throw 42; + }, null, badArrayLike); + } catch (_) { + if (_ !== isCallableMarker) { + reflectApply = null; + } + } +} else { + reflectApply = null; +} +var constructorRegex = /^\s*class\b/; +var isES6ClassFn = function isES6ClassFunction(value) { + try { + var fnStr = fnToStr.call(value); + return constructorRegex.test(fnStr); + } catch (e) { + return false; + } +}; +var tryFunctionObject = function tryFunctionToStr(value) { + try { + if (isES6ClassFn(value)) { + return false; + } + fnToStr.call(value); + return true; + } catch (e) { + return false; + } +}; +var toStr = Object.prototype.toString; +var objectClass = "[object Object]"; +var fnClass = "[object Function]"; +var genClass = "[object GeneratorFunction]"; +var ddaClass = "[object HTMLAllCollection]"; +var ddaClass2 = "[object HTML document.all class]"; +var ddaClass3 = "[object HTMLCollection]"; +var hasToStringTag = typeof Symbol === "function" && !!Symbol.toStringTag; +var isIE68 = !(0 in [,]); +var isDDA = function isDocumentDotAll() { + return false; +}; +if (typeof document === "object") { + var all = document.all; + if (toStr.call(all) === toStr.call(document.all)) { + isDDA = function isDocumentDotAll2(value) { + if ((isIE68 || !value) && (typeof value === "undefined" || typeof value === "object")) { + try { + var str = toStr.call(value); + return (str === ddaClass || str === ddaClass2 || str === ddaClass3 || str === objectClass) && value("") == null; + } catch (e) { + } + } + return false; + }; + } +} +function isCallableRefApply(value) { + if (isDDA(value)) { + return true; + } + if (!value) { + return false; + } + if (typeof value !== "function" && typeof value !== "object") { + return false; + } + try { + reflectApply(value, null, badArrayLike); + } catch (e) { + if (e !== isCallableMarker) { + return false; + } + } + return !isES6ClassFn(value) && tryFunctionObject(value); +} +function isCallableNoRefApply(value) { + if (isDDA(value)) { + return true; + } + if (!value) { + return false; + } + if (typeof value !== "function" && typeof value !== "object") { + return false; + } + if (hasToStringTag) { + return tryFunctionObject(value); + } + if (isES6ClassFn(value)) { + return false; + } + var strClass = toStr.call(value); + if (strClass !== fnClass && strClass !== genClass && !/^\[object HTML/.test(strClass)) { + return false; + } + return tryFunctionObject(value); +} +const isCallable = reflectApply ? isCallableRefApply : isCallableNoRefApply; +var _a; +class CancelError extends Error { + /** + * Constructs a new `CancelError` instance. + * @param message - The error message. + * @param options - Options to be forwarded to the Error constructor. + */ + constructor(message, options) { + super(message, options); + this.name = "CancelError"; + } +} +class CancelledRejectionError extends Error { + /** + * Constructs a new `CancelledRejectionError` instance. + * @param promise - The promise that caused the error originally. + * @param reason - The rejection reason. + * @param info - An optional informative message specifying the circumstances in which the error was thrown. + * Defaults to the string `"Unhandled rejection in cancelled promise."`. + */ + constructor(promise, reason, info) { + super((info !== null && info !== void 0 ? info : "Unhandled rejection in cancelled promise.") + " Reason: " + errorMessage(reason), { cause: reason }); + this.promise = promise; + this.name = "CancelledRejectionError"; + } +} +const barrierSym = Symbol("barrier"); +const cancelImplSym = Symbol("cancelImpl"); +const species = (_a = Symbol.species) !== null && _a !== void 0 ? _a : Symbol("speciesPolyfill"); +class CancellablePromise extends Promise { + /** + * Creates a new `CancellablePromise`. + * + * @param executor - A callback used to initialize the promise. This callback is passed two arguments: + * a `resolve` callback used to resolve the promise with a value + * or the result of another promise (possibly cancellable), + * and a `reject` callback used to reject the promise with a provided reason or error. + * If the value provided to the `resolve` callback is a thenable _and_ cancellable object + * (it has a `then` _and_ a `cancel` method), + * cancellation requests will be forwarded to that object and the oncancelled will not be invoked anymore. + * If any one of the two callbacks is called _after_ the promise has been cancelled, + * the provided values will be cancelled and resolved as usual, + * but their results will be discarded. + * However, if the resolution process ultimately ends up in a rejection + * that is not due to cancellation, the rejection reason + * will be wrapped in a {@link CancelledRejectionError} + * and bubbled up as an unhandled rejection. + * @param oncancelled - It is the caller's responsibility to ensure that any operation + * started by the executor is properly halted upon cancellation. + * This optional callback can be used to that purpose. + * It will be called _synchronously_ with a cancellation cause + * when cancellation is requested, _after_ the promise has already rejected + * with a {@link CancelError}, but _before_ + * any {@link then}/{@link catch}/{@link finally} callback runs. + * If the callback returns a thenable, the promise returned from {@link cancel} + * will only fulfill after the former has settled. + * Unhandled exceptions or rejections from the callback will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as unhandled rejections. + * If the `resolve` callback is called before cancellation with a cancellable promise, + * cancellation requests on this promise will be diverted to that promise, + * and the original `oncancelled` callback will be discarded. + */ + constructor(executor, oncancelled) { + let resolve; + let reject; + super((res, rej) => { + resolve = res; + reject = rej; + }); + if (this.constructor[species] !== Promise) { + throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property."); + } + let promise = { + promise: this, + resolve, + reject, + get oncancelled() { + return oncancelled !== null && oncancelled !== void 0 ? oncancelled : null; + }, + set oncancelled(cb) { + oncancelled = cb !== null && cb !== void 0 ? cb : void 0; + } + }; + const state = { + get root() { + return state; + }, + resolving: false, + settled: false + }; + void Object.defineProperties(this, { + [barrierSym]: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + [cancelImplSym]: { + configurable: false, + enumerable: false, + writable: false, + value: cancellerFor(promise, state) + } + }); + const rejector = rejectorFor(promise, state); + try { + executor(resolverFor(promise, state), rejector); + } catch (err) { + if (state.resolving) { + console.log("Unhandled exception in CancellablePromise executor.", err); + } else { + rejector(err); + } + } + } + /** + * Cancels immediately the execution of the operation associated with this promise. + * The promise rejects with a {@link CancelError} instance as reason, + * with the {@link CancelError#cause} property set to the given argument, if any. + * + * Has no effect if called after the promise has already settled; + * repeated calls in particular are safe, but only the first one + * will set the cancellation cause. + * + * The `CancelError` exception _need not_ be handled explicitly _on the promises that are being cancelled:_ + * cancelling a promise with no attached rejection handler does not trigger an unhandled rejection event. + * Therefore, the following idioms are all equally correct: + * ```ts + * new CancellablePromise((resolve, reject) => { ... }).cancel(); + * new CancellablePromise((resolve, reject) => { ... }).then(...).cancel(); + * new CancellablePromise((resolve, reject) => { ... }).then(...).catch(...).cancel(); + * ``` + * Whenever some cancelled promise in a chain rejects with a `CancelError` + * with the same cancellation cause as itself, the error will be discarded silently. + * However, the `CancelError` _will still be delivered_ to all attached rejection handlers + * added by {@link then} and related methods: + * ```ts + * let cancellable = new CancellablePromise((resolve, reject) => { ... }); + * cancellable.then(() => { ... }).catch(console.log); + * cancellable.cancel(); // A CancelError is printed to the console. + * ``` + * If the `CancelError` is not handled downstream by the time it reaches + * a _non-cancelled_ promise, it _will_ trigger an unhandled rejection event, + * just like normal rejections would: + * ```ts + * let cancellable = new CancellablePromise((resolve, reject) => { ... }); + * let chained = cancellable.then(() => { ... }).then(() => { ... }); // No catch... + * cancellable.cancel(); // Unhandled rejection event on chained! + * ``` + * Therefore, it is important to either cancel whole promise chains from their tail, + * as shown in the correct idioms above, or take care of handling errors everywhere. + * + * @returns A cancellable promise that _fulfills_ after the cancel callback (if any) + * and all handlers attached up to the call to cancel have run. + * If the cancel callback returns a thenable, the promise returned by `cancel` + * will also wait for that thenable to settle. + * This enables callers to wait for the cancelled operation to terminate + * without being forced to handle potential errors at the call site. + * ```ts + * cancellable.cancel().then(() => { + * // Cleanup finished, it's safe to do something else. + * }, (err) => { + * // Unreachable: the promise returned from cancel will never reject. + * }); + * ``` + * Note that the returned promise will _not_ handle implicitly any rejection + * that might have occurred already in the cancelled chain. + * It will just track whether registered handlers have been executed or not. + * Therefore, unhandled rejections will never be silently handled by calling cancel. + */ + cancel(cause) { + return new CancellablePromise((resolve) => { + Promise.all([ + this[cancelImplSym](new CancelError("Promise cancelled.", { cause })), + currentBarrier(this) + ]).then(() => resolve(), () => resolve()); + }); + } + /** + * Binds promise cancellation to the abort event of the given {@link AbortSignal}. + * If the signal has already aborted, the promise will be cancelled immediately. + * When either condition is verified, the cancellation cause will be set + * to the signal's abort reason (see {@link AbortSignal#reason}). + * + * Has no effect if called (or if the signal aborts) _after_ the promise has already settled. + * Only the first signal to abort will set the cancellation cause. + * + * For more details about the cancellation process, + * see {@link cancel} and the `CancellablePromise` constructor. + * + * This method enables `await`ing cancellable promises without having + * to store them for future cancellation, e.g.: + * ```ts + * await longRunningOperation().cancelOn(signal); + * ``` + * instead of: + * ```ts + * let promiseToBeCancelled = longRunningOperation(); + * await promiseToBeCancelled; + * ``` + * + * @returns This promise, for method chaining. + */ + cancelOn(signal) { + if (signal.aborted) { + void this.cancel(signal.reason); + } else { + signal.addEventListener("abort", () => void this.cancel(signal.reason), { capture: true }); + } + return this; + } + /** + * Attaches callbacks for the resolution and/or rejection of the `CancellablePromise`. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * When the parent promise rejects or is cancelled, the `onrejected` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A `CancellablePromise` for the completion of whichever callback is executed. + * The returned promise is hooked up to propagate cancellation requests up the chain, but not down: + * + * - if the parent promise is cancelled, the `onrejected` handler will be invoked with a `CancelError` + * and the returned promise _will resolve regularly_ with its result; + * - conversely, if the returned promise is cancelled, _the parent promise is cancelled too;_ + * the `onrejected` handler will still be invoked with the parent's `CancelError`, + * but its result will be discarded + * and the returned promise will reject with a `CancelError` as well. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If either callback returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + */ + then(onfulfilled, onrejected, oncancelled) { + if (!(this instanceof CancellablePromise)) { + throw new TypeError("CancellablePromise.prototype.then called on an invalid object."); + } + if (!isCallable(onfulfilled)) { + onfulfilled = identity; + } + if (!isCallable(onrejected)) { + onrejected = thrower; + } + if (onfulfilled === identity && onrejected == thrower) { + return new CancellablePromise((resolve) => resolve(this)); + } + const barrier = {}; + this[barrierSym] = barrier; + return new CancellablePromise((resolve, reject) => { + void super.then((value) => { + var _a2; + if (this[barrierSym] === barrier) { + this[barrierSym] = null; + } + (_a2 = barrier.resolve) === null || _a2 === void 0 ? void 0 : _a2.call(barrier); + try { + resolve(onfulfilled(value)); + } catch (err) { + reject(err); + } + }, (reason) => { + var _a2; + if (this[barrierSym] === barrier) { + this[barrierSym] = null; + } + (_a2 = barrier.resolve) === null || _a2 === void 0 ? void 0 : _a2.call(barrier); + try { + resolve(onrejected(reason)); + } catch (err) { + reject(err); + } + }); + }, async (cause) => { + try { + return oncancelled === null || oncancelled === void 0 ? void 0 : oncancelled(cause); + } finally { + await this.cancel(cause); + } + }); + } + /** + * Attaches a callback for only the rejection of the Promise. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * When the parent promise rejects or is cancelled, the `onrejected` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * It is equivalent to + * ```ts + * cancellablePromise.then(undefined, onrejected, oncancelled); + * ``` + * and the same caveats apply. + * + * @returns A Promise for the completion of the callback. + * Cancellation requests on the returned promise + * will propagate up the chain to the parent promise, + * but not in the other direction. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If `onrejected` returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + * See {@link then} for more details. + */ + catch(onrejected, oncancelled) { + return this.then(void 0, onrejected, oncancelled); + } + /** + * Attaches a callback that is invoked when the CancellablePromise is settled (fulfilled or rejected). The + * resolved value cannot be accessed or modified from the callback. + * The returned promise will settle in the same state as the original one + * after the provided callback has completed execution, + * unless the callback throws or returns a rejecting promise, + * in which case the returned promise will reject as well. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * Once the parent promise settles, the `onfinally` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * This method is implemented in terms of {@link then} and the same caveats apply. + * It is polyfilled, hence available in every OS/webview version. + * + * @returns A Promise for the completion of the callback. + * Cancellation requests on the returned promise + * will propagate up the chain to the parent promise, + * but not in the other direction. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If `onfinally` returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + * See {@link then} for more details. + */ + finally(onfinally, oncancelled) { + if (!(this instanceof CancellablePromise)) { + throw new TypeError("CancellablePromise.prototype.finally called on an invalid object."); + } + if (!isCallable(onfinally)) { + return this.then(onfinally, onfinally, oncancelled); + } + return this.then((value) => CancellablePromise.resolve(onfinally()).then(() => value), (reason) => CancellablePromise.resolve(onfinally()).then(() => { + throw reason; + }), oncancelled); + } + /** + * We use the `[Symbol.species]` static property, if available, + * to disable the built-in automatic subclassing features from {@link Promise}. + * It is critical for performance reasons that extenders do not override this. + * Once the proposal at https://github.com/tc39/proposal-rm-builtin-subclassing + * is either accepted or retired, this implementation will have to be revised accordingly. + * + * @ignore + * @internal + */ + static get [species]() { + return Promise; + } + static all(values) { + let collected = Array.from(values); + const promise = collected.length === 0 ? CancellablePromise.resolve(collected) : new CancellablePromise((resolve, reject) => { + void Promise.all(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + static allSettled(values) { + let collected = Array.from(values); + const promise = collected.length === 0 ? CancellablePromise.resolve(collected) : new CancellablePromise((resolve, reject) => { + void Promise.allSettled(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + static any(values) { + let collected = Array.from(values); + const promise = collected.length === 0 ? CancellablePromise.resolve(collected) : new CancellablePromise((resolve, reject) => { + void Promise.any(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + static race(values) { + let collected = Array.from(values); + const promise = new CancellablePromise((resolve, reject) => { + void Promise.race(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + /** + * Creates a new cancelled CancellablePromise for the provided cause. + * + * @group Static Methods + */ + static cancel(cause) { + const p = new CancellablePromise(() => { + }); + p.cancel(cause); + return p; + } + /** + * Creates a new CancellablePromise that cancels + * after the specified timeout, with the provided cause. + * + * If the {@link AbortSignal.timeout} factory method is available, + * it is used to base the timeout on _active_ time rather than _elapsed_ time. + * Otherwise, `timeout` falls back to {@link setTimeout}. + * + * @group Static Methods + */ + static timeout(milliseconds, cause) { + const promise = new CancellablePromise(() => { + }); + if (AbortSignal && typeof AbortSignal === "function" && AbortSignal.timeout && typeof AbortSignal.timeout === "function") { + AbortSignal.timeout(milliseconds).addEventListener("abort", () => void promise.cancel(cause)); + } else { + setTimeout(() => void promise.cancel(cause), milliseconds); + } + return promise; + } + static sleep(milliseconds, value) { + return new CancellablePromise((resolve) => { + setTimeout(() => resolve(value), milliseconds); + }); + } + /** + * Creates a new rejected CancellablePromise for the provided reason. + * + * @group Static Methods + */ + static reject(reason) { + return new CancellablePromise((_, reject) => reject(reason)); + } + static resolve(value) { + if (value instanceof CancellablePromise) { + return value; + } + return new CancellablePromise((resolve) => resolve(value)); + } + /** + * Creates a new CancellablePromise and returns it in an object, along with its resolve and reject functions + * and a getter/setter for the cancellation callback. + * + * This method is polyfilled, hence available in every OS/webview version. + * + * @group Static Methods + */ + static withResolvers() { + let result = { oncancelled: null }; + result.promise = new CancellablePromise((resolve, reject) => { + result.resolve = resolve; + result.reject = reject; + }, (cause) => { + var _a2; + (_a2 = result.oncancelled) === null || _a2 === void 0 ? void 0 : _a2.call(result, cause); + }); + return result; + } +} +function cancellerFor(promise, state) { + let cancellationPromise = void 0; + return (reason) => { + if (!state.settled) { + state.settled = true; + state.reason = reason; + promise.reject(reason); + void Promise.prototype.then.call(promise.promise, void 0, (err) => { + if (err !== reason) { + throw err; + } + }); + } + if (!state.reason || !promise.oncancelled) { + return; + } + cancellationPromise = new Promise((resolve) => { + try { + resolve(promise.oncancelled(state.reason.cause)); + } catch (err) { + Promise.reject(new CancelledRejectionError(promise.promise, err, "Unhandled exception in oncancelled callback.")); + } + }).catch((reason2) => { + Promise.reject(new CancelledRejectionError(promise.promise, reason2, "Unhandled rejection in oncancelled callback.")); + }); + promise.oncancelled = null; + return cancellationPromise; + }; +} +function resolverFor(promise, state) { + return (value) => { + if (state.resolving) { + return; + } + state.resolving = true; + if (value === promise.promise) { + if (state.settled) { + return; + } + state.settled = true; + promise.reject(new TypeError("A promise cannot be resolved with itself.")); + return; + } + if (value != null && (typeof value === "object" || typeof value === "function")) { + let then; + try { + then = value.then; + } catch (err) { + state.settled = true; + promise.reject(err); + return; + } + if (isCallable(then)) { + try { + let cancel = value.cancel; + if (isCallable(cancel)) { + const oncancelled = (cause) => { + Reflect.apply(cancel, value, [cause]); + }; + if (state.reason) { + void cancellerFor(Object.assign(Object.assign({}, promise), { oncancelled }), state)(state.reason); + } else { + promise.oncancelled = oncancelled; + } + } + } catch (_a2) { + } + const newState = { + root: state.root, + resolving: false, + get settled() { + return this.root.settled; + }, + set settled(value2) { + this.root.settled = value2; + }, + get reason() { + return this.root.reason; + } + }; + const rejector = rejectorFor(promise, newState); + try { + Reflect.apply(then, value, [resolverFor(promise, newState), rejector]); + } catch (err) { + rejector(err); + } + return; + } + } + if (state.settled) { + return; + } + state.settled = true; + promise.resolve(value); + }; +} +function rejectorFor(promise, state) { + return (reason) => { + if (state.resolving) { + return; + } + state.resolving = true; + if (state.settled) { + try { + if (reason instanceof CancelError && state.reason instanceof CancelError && Object.is(reason.cause, state.reason.cause)) { + return; + } + } catch (_a2) { + } + void Promise.reject(new CancelledRejectionError(promise.promise, reason)); + } else { + state.settled = true; + promise.reject(reason); + } + }; +} +function cancelAll(parent, values, cause) { + const results = []; + for (const value of values) { + let cancel; + try { + if (!isCallable(value.then)) { + continue; + } + cancel = value.cancel; + if (!isCallable(cancel)) { + continue; + } + } catch (_a2) { + continue; + } + let result; + try { + result = Reflect.apply(cancel, value, [cause]); + } catch (err) { + Promise.reject(new CancelledRejectionError(parent, err, "Unhandled exception in cancel method.")); + continue; + } + if (!result) { + continue; + } + results.push((result instanceof Promise ? result : Promise.resolve(result)).catch((reason) => { + Promise.reject(new CancelledRejectionError(parent, reason, "Unhandled rejection in cancel method.")); + })); + } + return Promise.all(results); +} +function identity(x) { + return x; +} +function thrower(reason) { + throw reason; +} +function errorMessage(err) { + try { + if (err instanceof Error || typeof err !== "object" || err.toString !== Object.prototype.toString) { + return "" + err; + } + } catch (_a2) { + } + try { + return JSON.stringify(err); + } catch (_b) { + } + try { + return Object.prototype.toString.call(err); + } catch (_c) { + } + return ""; +} +function currentBarrier(promise) { + var _a2; + let pwr = (_a2 = promise[barrierSym]) !== null && _a2 !== void 0 ? _a2 : {}; + if (!("promise" in pwr)) { + Object.assign(pwr, promiseWithResolvers()); + } + if (promise[barrierSym] == null) { + pwr.resolve(); + promise[barrierSym] = pwr; + } + return pwr.promise; +} +let promiseWithResolvers = Promise.withResolvers; +if (promiseWithResolvers && typeof promiseWithResolvers === "function") { + promiseWithResolvers = promiseWithResolvers.bind(Promise); +} else { + promiseWithResolvers = function() { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; + }; +} +window._wails = window._wails || {}; +window._wails.callResultHandler = resultHandler; +window._wails.callErrorHandler = errorHandler; +const call$1 = newRuntimeCaller(objectNames.Call); +const cancelCall = newRuntimeCaller(objectNames.CancelCall); +const callResponses = /* @__PURE__ */ new Map(); +const CallBinding = 0; +const CancelMethod = 0; +class RuntimeError extends Error { + /** + * Constructs a new RuntimeError instance. + * @param message - The error message. + * @param options - Options to be forwarded to the Error constructor. + */ + constructor(message, options) { + super(message, options); + this.name = "RuntimeError"; + } +} +function resultHandler(id, data, isJSON) { + const resolvers = getAndDeleteResponse(id); + if (!resolvers) { + return; + } + if (!data) { + resolvers.resolve(void 0); + } else if (!isJSON) { + resolvers.resolve(data); + } else { + try { + resolvers.resolve(JSON.parse(data)); + } catch (err) { + resolvers.reject(new TypeError("could not parse result: " + err.message, { cause: err })); + } + } +} +function errorHandler(id, data, isJSON) { + const resolvers = getAndDeleteResponse(id); + if (!resolvers) { + return; + } + if (!isJSON) { + resolvers.reject(new Error(data)); + } else { + let error; + try { + error = JSON.parse(data); + } catch (err) { + resolvers.reject(new TypeError("could not parse error: " + err.message, { cause: err })); + return; + } + let options = {}; + if (error.cause) { + options.cause = error.cause; + } + let exception; + switch (error.kind) { + case "ReferenceError": + exception = new ReferenceError(error.message, options); + break; + case "TypeError": + exception = new TypeError(error.message, options); + break; + case "RuntimeError": + exception = new RuntimeError(error.message, options); + break; + default: + exception = new Error(error.message, options); + break; + } + resolvers.reject(exception); + } +} +function getAndDeleteResponse(id) { + const response = callResponses.get(id); + callResponses.delete(id); + return response; +} +function generateID() { + let result; + do { + result = nanoid(); + } while (callResponses.has(result)); + return result; +} +function Call(options) { + const id = generateID(); + const result = CancellablePromise.withResolvers(); + callResponses.set(id, { resolve: result.resolve, reject: result.reject }); + const request = call$1(CallBinding, Object.assign({ "call-id": id }, options)); + let running = false; + request.then(() => { + running = true; + }, (err) => { + callResponses.delete(id); + result.reject(err); + }); + const cancel = () => { + callResponses.delete(id); + return cancelCall(CancelMethod, { "call-id": id }).catch((err) => { + console.error("Error while requesting binding call cancellation:", err); + }); + }; + result.oncancelled = () => { + if (running) { + return cancel(); + } else { + return request.then(cancel); + } + }; + return result.promise; +} +function ByID(methodID, ...args) { + return Call({ methodID, args }); +} +const eventListeners = /* @__PURE__ */ new Map(); +class Listener { + constructor(eventName, callback, maxCallbacks) { + this.eventName = eventName; + this.callback = callback; + this.maxCallbacks = maxCallbacks || -1; + } + dispatch(data) { + try { + this.callback(data); + } catch (err) { + console.error(err); + } + if (this.maxCallbacks === -1) + return false; + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + } +} +function listenerOff(listener) { + let listeners = eventListeners.get(listener.eventName); + if (!listeners) { + return; + } + listeners = listeners.filter((l) => l !== listener); + if (listeners.length === 0) { + eventListeners.delete(listener.eventName); + } else { + eventListeners.set(listener.eventName, listeners); + } +} +window._wails = window._wails || {}; +window._wails.dispatchWailsEvent = dispatchWailsEvent; +const call = newRuntimeCaller(objectNames.Events); +const EmitMethod = 0; +class WailsEvent { + constructor(name, data = null) { + this.name = name; + this.data = data; + } +} +function dispatchWailsEvent(event) { + let listeners = eventListeners.get(event.name); + if (!listeners) { + return; + } + let wailsEvent = new WailsEvent(event.name, event.data); + if ("sender" in event) { + wailsEvent.sender = event.sender; + } + listeners = listeners.filter((listener) => !listener.dispatch(wailsEvent)); + if (listeners.length === 0) { + eventListeners.delete(event.name); + } else { + eventListeners.set(event.name, listeners); + } +} +function OnMultiple(eventName, callback, maxCallbacks) { + let listeners = eventListeners.get(eventName) || []; + const thisListener = new Listener(eventName, callback, maxCallbacks); + listeners.push(thisListener); + eventListeners.set(eventName, listeners); + return () => listenerOff(thisListener); +} +function On(eventName, callback) { + return OnMultiple(eventName, callback, -1); +} +function Emit(event) { + return call(EmitMethod, event); +} +window._wails = window._wails || {}; +window._wails.invoke = invoke; +invoke("wails:runtime:ready"); +function RemoveBadge() { + return ByID(2752757297); +} +function SetBadge(label) { + return ByID(1717705661, label); +} +function SetCustomBadge(label, options) { + return ByID(2730169760, label, options); +} +class RGBA { + /** Creates a new RGBA instance. */ + constructor($$source = {}) { + __publicField(this, "R"); + __publicField(this, "G"); + __publicField(this, "B"); + __publicField(this, "A"); + if (!("R" in $$source)) { + this["R"] = 0; + } + if (!("G" in $$source)) { + this["G"] = 0; + } + if (!("B" in $$source)) { + this["B"] = 0; + } + if (!("A" in $$source)) { + this["A"] = 0; + } + Object.assign(this, $$source); + } + /** + * Creates a new RGBA instance from a string or object. + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === "string" ? JSON.parse($$source) : $$source; + return new RGBA($$parsedSource); + } +} +const setCustomButton = document.getElementById("set-custom"); +const setButton = document.getElementById("set"); +const removeButton = document.getElementById("remove"); +const setButtonUsingGo = document.getElementById("set-go"); +const removeButtonUsingGo = document.getElementById("remove-go"); +const labelElement = document.getElementById("label"); +const timeElement = document.getElementById("time"); +setCustomButton.addEventListener("click", () => { + console.log("click!"); + let label = labelElement.value; + SetCustomBadge(label, { + BackgroundColour: RGBA.createFrom({ + R: 0, + G: 255, + B: 255, + A: 255 + }), + FontName: "arialb.ttf", + // System font + FontSize: 16, + SmallFontSize: 10, + TextColour: RGBA.createFrom({ + R: 0, + G: 0, + B: 0, + A: 255 + }) + }); +}); +setButton.addEventListener("click", () => { + let label = labelElement.value; + SetBadge(label); +}); +removeButton.addEventListener("click", () => { + RemoveBadge(); +}); +setButtonUsingGo.addEventListener("click", () => { + let label = labelElement.value; + void Emit({ + name: "set:badge", + data: label + }); +}); +removeButtonUsingGo.addEventListener("click", () => { + void Emit({ name: "remove:badge", data: null }); +}); +On("time", (time) => { + timeElement.innerText = time.data; +}); diff --git a/v3/examples/badge-custom/frontend/dist/index.html b/v3/examples/badge-custom/frontend/dist/index.html new file mode 100644 index 000000000..11b9fcd50 --- /dev/null +++ b/v3/examples/badge-custom/frontend/dist/index.html @@ -0,0 +1,39 @@ + + + + + + + + Wails App + + + +
+ +

Wails + Typescript

+
Set a badge label below 👇
+
+
+ + + + + + +
+
+ +
+ + diff --git a/v3/examples/badge-custom/frontend/dist/style.css b/v3/examples/badge-custom/frontend/dist/style.css new file mode 100644 index 000000000..6ce81cad2 --- /dev/null +++ b/v3/examples/badge-custom/frontend/dist/style.css @@ -0,0 +1,155 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/examples/badge-custom/frontend/dist/typescript.svg b/v3/examples/badge-custom/frontend/dist/typescript.svg new file mode 100644 index 000000000..d91c910cc --- /dev/null +++ b/v3/examples/badge-custom/frontend/dist/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/badge-custom/frontend/dist/wails.png b/v3/examples/badge-custom/frontend/dist/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/badge-custom/frontend/dist/wails.png differ diff --git a/v3/examples/badge-custom/frontend/index.html b/v3/examples/badge-custom/frontend/index.html new file mode 100644 index 000000000..e4a9ec4a2 --- /dev/null +++ b/v3/examples/badge-custom/frontend/index.html @@ -0,0 +1,39 @@ + + + + + + + + Wails App + + +
+ +

Wails + Typescript

+
Set a badge label below 👇
+
+
+ + + + + + +
+
+ +
+ + + diff --git a/v3/examples/badge-custom/frontend/package.json b/v3/examples/badge-custom/frontend/package.json new file mode 100644 index 000000000..b39da7ece --- /dev/null +++ b/v3/examples/badge-custom/frontend/package.json @@ -0,0 +1,19 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "tsc && vite build --minify false --mode development", + "build": "tsc && vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "typescript": "^4.9.3", + "vite": "^5.0.0" + } +} diff --git a/v3/examples/badge-custom/frontend/public/Inter-Medium.ttf b/v3/examples/badge-custom/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/badge-custom/frontend/public/Inter-Medium.ttf differ diff --git a/v3/examples/badge-custom/frontend/public/style.css b/v3/examples/badge-custom/frontend/public/style.css new file mode 100644 index 000000000..6ce81cad2 --- /dev/null +++ b/v3/examples/badge-custom/frontend/public/style.css @@ -0,0 +1,155 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/examples/badge-custom/frontend/public/typescript.svg b/v3/examples/badge-custom/frontend/public/typescript.svg new file mode 100644 index 000000000..d91c910cc --- /dev/null +++ b/v3/examples/badge-custom/frontend/public/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/badge-custom/frontend/public/wails.png b/v3/examples/badge-custom/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/badge-custom/frontend/public/wails.png differ diff --git a/v3/examples/badge-custom/frontend/src/main.ts b/v3/examples/badge-custom/frontend/src/main.ts new file mode 100644 index 000000000..cbb4cd842 --- /dev/null +++ b/v3/examples/badge-custom/frontend/src/main.ts @@ -0,0 +1,56 @@ +import {Events} from "@wailsio/runtime"; +import {SetBadge, RemoveBadge, SetCustomBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/dock/dockservice"; +import { RGBA } from "../bindings/image/color/models"; + +const setCustomButton = document.getElementById('set-custom')! as HTMLButtonElement; +const setButton = document.getElementById('set')! as HTMLButtonElement; +const removeButton = document.getElementById('remove')! as HTMLButtonElement; +const setButtonUsingGo = document.getElementById('set-go')! as HTMLButtonElement; +const removeButtonUsingGo = document.getElementById('remove-go')! as HTMLButtonElement; +const labelElement : HTMLInputElement = document.getElementById('label')! as HTMLInputElement; +const timeElement = document.getElementById('time')! as HTMLDivElement; + +setCustomButton.addEventListener('click', () => { + console.log("click!") + let label = (labelElement as HTMLInputElement).value + SetCustomBadge(label, { + BackgroundColour: RGBA.createFrom({ + R: 0, + G: 255, + B: 255, + A: 255, + }), + FontName: "arialb.ttf", // System font + FontSize: 16, + SmallFontSize: 10, + TextColour: RGBA.createFrom({ + R: 0, + G: 0, + B: 0, + A: 255, + }), + }); +}) + +setButton.addEventListener('click', () => { + let label = (labelElement as HTMLInputElement).value + SetBadge(label); +}); + +removeButton.addEventListener('click', () => { + RemoveBadge(); +}); + +setButtonUsingGo.addEventListener('click', () => { + let label = (labelElement as HTMLInputElement).value + void Events.Emit("set:badge", label); +}) + +removeButtonUsingGo.addEventListener('click', () => { + void Events.Emit("remove:badge"); +}) + +Events.On('time', (time: {data: any}) => { + timeElement.innerText = time.data; +}); + diff --git a/v3/examples/badge-custom/frontend/src/vite-env.d.ts b/v3/examples/badge-custom/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/examples/badge-custom/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/examples/badge-custom/frontend/tsconfig.json b/v3/examples/badge-custom/frontend/tsconfig.json new file mode 100644 index 000000000..c267ecf24 --- /dev/null +++ b/v3/examples/badge-custom/frontend/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "noImplicitReturns": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/v3/examples/badge-custom/main.go b/v3/examples/badge-custom/main.go new file mode 100644 index 000000000..93a32d1ff --- /dev/null +++ b/v3/examples/badge-custom/main.go @@ -0,0 +1,108 @@ +package main + +import ( + "embed" + _ "embed" + "image/color" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/services/dock" +) + +// Wails uses Go's `embed` package to embed the frontend files into the binary. +// Any files in the frontend/dist folder will be embedded into the binary and +// made available to the frontend. +// See https://pkg.go.dev/embed for more information. + +//go:embed all:frontend/dist +var assets embed.FS + +// main function serves as the application's entry point. It initializes the application, creates a window, +// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and +// logs any error that might occur. +func main() { + // Create a new Wails application by providing the necessary options. + // Variables 'Name' and 'Description' are for application metadata. + // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files. + // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances. + // 'Mac' options tailor the application when running an macOS. + + dockService := dock.NewWithOptions(dock.BadgeOptions{ + TextColour: color.RGBA{255, 255, 204, 255}, + BackgroundColour: color.RGBA{16, 124, 16, 255}, + FontName: "consolab.ttf", + FontSize: 20, + SmallFontSize: 14, + }) + + app := application.New(application.Options{ + Name: "badge", + Description: "A demo of using raw HTML & CSS", + Services: []application.Service{ + application.NewService(dockService), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a new window with the necessary options. + // 'Title' is the title of the window. + // 'Mac' options tailor the window when running on macOS. + // 'BackgroundColour' is the background colour of the window. + // 'URL' is the URL that will be loaded into the webview. + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + BackgroundColour: application.NewRGB(27, 38, 54), + URL: "/", + }) + + app.Event.On("remove:badge", func(event *application.CustomEvent) { + err := dockService.RemoveBadge() + if err != nil { + log.Fatal(err) + } + }) + + app.Event.On("set:badge", func(event *application.CustomEvent) { + text := event.Data.(string) + err := dockService.SetBadge(text) + if err != nil { + log.Fatal(err) + } + }) + + // Create a goroutine that emits an event containing the current time every second. + // The frontend can listen to this event and update the UI accordingly. + go func() { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + now := time.Now().Format(time.RFC1123) + app.Event.Emit("time", now) + case <-app.Context().Done(): + return + } + } + }() + + // Run the application. This blocks until the application has been exited. + err := app.Run() + + // If an error occurred while running the application, log it and exit. + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/badge/README.md b/v3/examples/badge/README.md new file mode 100644 index 000000000..abb7b9653 --- /dev/null +++ b/v3/examples/badge/README.md @@ -0,0 +1,71 @@ +# Welcome to Your New Wails3 Project! +Now that you have your project set up, it's time to explore the basic badge features that Wails3 offers on **macOS** and **Windows**. + +## Exploring Badge Features + +### Creating the Service + +First, initialize the badge service: + +```go +import "github.com/wailsapp/wails/v3/pkg/application" +import "github.com/wailsapp/wails/v3/pkg/services/badge" + +// Create a new badge service +badgeService := badge.New() + +// Register the service with the application +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(badgeService), + }, +}) +``` + +## Badge Operations + +### Setting a Badge + +Set a badge on the application tile/dock icon: + +#### Go +```go +// Set a default badge +badgeService.SetBadge("") + +// Set a numeric badge +badgeService.SetBadge("3") + +// Set a text badge +badgeService.SetBadge("New") +``` + +#### JS +```js +import {SetBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service"; + +// Set a default badge +SetBadge("") + +// Set a numeric badge +SetBadge("3") + +// Set a text badge +SetBadge("New") +``` + +### Removing a Badge + +Remove the badge from the application icon: + +#### Go +```go +badgeService.RemoveBadge() +``` + +#### JS +```js +import {RemoveBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service"; + +RemoveBadge() +``` \ No newline at end of file diff --git a/v3/examples/badge/Taskfile.yml b/v3/examples/badge/Taskfile.yml new file mode 100644 index 000000000..d1bcfaacf --- /dev/null +++ b/v3/examples/badge/Taskfile.yml @@ -0,0 +1,34 @@ +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + +vars: + APP_NAME: "badge" + BIN_DIR: "bin" + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{OS}}:package" + + run: + summary: Runs the application + cmds: + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} + diff --git a/v3/examples/badge/build/Taskfile.yml b/v3/examples/badge/build/Taskfile.yml new file mode 100644 index 000000000..5f3517efc --- /dev/null +++ b/v3/examples/badge/build/Taskfile.yml @@ -0,0 +1,86 @@ +version: '3' + +tasks: + go:mod:tidy: + summary: Runs `go mod tidy` + internal: true + cmds: + - go mod tidy + + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + label: build:frontend (PRODUCTION={{.PRODUCTION}}) + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/**/* + deps: + - task: install:frontend:deps + - task: generate:bindings + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + cmds: + - npm run {{.BUILD_COMMAND}} -q + env: + PRODUCTION: '{{.PRODUCTION | default "false"}}' + vars: + BUILD_COMMAND: '{{if eq .PRODUCTION "true"}}build{{else}}build:dev{{end}}' + + + generate:bindings: + label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}}) + summary: Generates bindings for the frontend + deps: + - task: go:mod:tidy + sources: + - "**/*.[jt]s" + - exclude: frontend/**/* + - frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output + - "**/*.go" + - go.mod + - go.sum + generates: + - frontend/bindings/**/* + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true -ts + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + sources: + - "appicon.png" + generates: + - "darwin/icons.icns" + - "windows/icon.ico" + cmds: + - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico + + dev:frontend: + summary: Runs the frontend in development mode + dir: frontend + deps: + - task: install:frontend:deps + cmds: + - npm run dev -- --port {{.VITE_PORT}} --strictPort + + update:build-assets: + summary: Updates the build assets + dir: build + cmds: + - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir . diff --git a/v3/examples/badge/build/appicon.png b/v3/examples/badge/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/badge/build/appicon.png differ diff --git a/v3/examples/badge/build/config.yml b/v3/examples/badge/build/config.yml new file mode 100644 index 000000000..aaa3240fb --- /dev/null +++ b/v3/examples/badge/build/config.yml @@ -0,0 +1,63 @@ +# This file contains the configuration for this project. +# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets. +# Note that this will overwrite any changes you have made to the assets. +version: '3' + +# This information is used to generate the build assets. +info: + companyName: "My Company" # The name of the company + productName: "My Product" # The name of the application + productIdentifier: "com.mycompany.myproduct" # The unique product identifier + description: "A program that does X" # The application description + copyright: "(c) 2025, My Company" # Copyright text + comments: "Some Product Comments" # Comments + version: "0.0.1" # The application version + +# Dev mode configuration +dev_mode: + root_path: . + log_level: warn + debounce: 1000 + ignore: + dir: + - .git + - node_modules + - frontend + - bin + file: + - .DS_Store + - .gitignore + - .gitkeep + watched_extension: + - "*.go" + git_ignore: true + executes: + - cmd: wails3 task common:install:frontend:deps + type: once + - cmd: wails3 task common:dev:frontend + type: background + - cmd: go mod tidy + type: blocking + - cmd: wails3 task build + type: blocking + - cmd: wails3 task run + type: primary + +# File Associations +# More information at: https://v3.wails.io/noit/done/yet +fileAssociations: +# - ext: wails +# name: Wails +# description: Wails Application File +# iconName: wailsFileIcon +# role: Editor +# - ext: jpg +# name: JPEG +# description: Image File +# iconName: jpegFileIcon +# role: Editor +# mimeType: image/jpeg # (optional) + +# Other data +other: + - name: My Other Data \ No newline at end of file diff --git a/v3/examples/badge/build/darwin/Info.dev.plist b/v3/examples/badge/build/darwin/Info.dev.plist new file mode 100644 index 000000000..4caddf720 --- /dev/null +++ b/v3/examples/badge/build/darwin/Info.dev.plist @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + badge + CFBundleIdentifier + com.wails.badge + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © now, My Company + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/examples/badge/build/darwin/Info.plist b/v3/examples/badge/build/darwin/Info.plist new file mode 100644 index 000000000..0dc90b2e7 --- /dev/null +++ b/v3/examples/badge/build/darwin/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + badge + CFBundleIdentifier + com.wails.badge + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © now, My Company + + \ No newline at end of file diff --git a/v3/examples/badge/build/darwin/Taskfile.yml b/v3/examples/badge/build/darwin/Taskfile.yml new file mode 100644 index 000000000..f0791fea9 --- /dev/null +++ b/v3/examples/badge/build/darwin/Taskfile.yml @@ -0,0 +1,81 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Creates a production build of the application + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + env: + GOOS: darwin + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + CGO_CFLAGS: "-mmacosx-version-min=10.15" + CGO_LDFLAGS: "-mmacosx-version-min=10.15" + MACOSX_DEPLOYMENT_TARGET: "10.15" + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + build:universal: + summary: Builds darwin universal binary (arm64 + amd64) + deps: + - task: build + vars: + ARCH: amd64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" + - task: build + vars: + ARCH: arm64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + cmds: + - lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + - rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + + package: + summary: Packages a production build of the application into a `.app` bundle + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:app:bundle + + package:universal: + summary: Packages darwin universal binary (arm64 + amd64) + deps: + - task: build:universal + cmds: + - task: create:app:bundle + + + create:app:bundle: + summary: Creates an `.app` bundle + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS + - cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app + + run: + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS + - cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app + - '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}' diff --git a/v3/examples/badge/build/darwin/icons.icns b/v3/examples/badge/build/darwin/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/badge/build/darwin/icons.icns differ diff --git a/v3/examples/badge/build/linux/Taskfile.yml b/v3/examples/badge/build/linux/Taskfile.yml new file mode 100644 index 000000000..560cc9c92 --- /dev/null +++ b/v3/examples/badge/build/linux/Taskfile.yml @@ -0,0 +1,119 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Linux + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: linux + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application for Linux + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:appimage + - task: create:deb + - task: create:rpm + - task: create:aur + + create:appimage: + summary: Creates an AppImage + dir: build/linux/appimage + deps: + - task: build + vars: + PRODUCTION: "true" + - task: generate:dotdesktop + cmds: + - cp {{.APP_BINARY}} {{.APP_NAME}} + - cp ../../appicon.png appicon.png + - wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build + vars: + APP_NAME: '{{.APP_NAME}}' + APP_BINARY: '../../../bin/{{.APP_NAME}}' + ICON: '../../appicon.png' + DESKTOP_FILE: '../{{.APP_NAME}}.desktop' + OUTPUT_DIR: '../../../bin' + + create:deb: + summary: Creates a deb package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:deb + + create:rpm: + summary: Creates a rpm package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:rpm + + create:aur: + summary: Creates a arch linux packager package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:aur + + generate:deb: + summary: Creates a deb package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:rpm: + summary: Creates a rpm package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:aur: + summary: Creates a arch linux packager package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:dotdesktop: + summary: Generates a `.desktop` file + dir: build + cmds: + - mkdir -p {{.ROOT_DIR}}/build/linux/appimage + - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}" + vars: + APP_NAME: '{{.APP_NAME}}' + EXEC: '{{.APP_NAME}}' + ICON: 'appicon' + CATEGORIES: 'Development;' + OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}' diff --git a/v3/examples/badge/build/linux/appimage/build.sh b/v3/examples/badge/build/linux/appimage/build.sh new file mode 100644 index 000000000..85901c34e --- /dev/null +++ b/v3/examples/badge/build/linux/appimage/build.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Copyright (c) 2018-Present Lea Anthony +# SPDX-License-Identifier: MIT + +# Fail script on any error +set -euxo pipefail + +# Define variables +APP_DIR="${APP_NAME}.AppDir" + +# Create AppDir structure +mkdir -p "${APP_DIR}/usr/bin" +cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/" +cp "${ICON_PATH}" "${APP_DIR}/" +cp "${DESKTOP_FILE}" "${APP_DIR}/" + +if [[ $(uname -m) == *x86_64* ]]; then + # Download linuxdeploy and make it executable + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + + # Run linuxdeploy to bundle the application + ./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage +else + # Download linuxdeploy and make it executable (arm64) + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage + chmod +x linuxdeploy-aarch64.AppImage + + # Run linuxdeploy to bundle the application (arm64) + ./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage +fi + +# Rename the generated AppImage +mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage" + diff --git a/v3/examples/badge/build/linux/nfpm/nfpm.yaml b/v3/examples/badge/build/linux/nfpm/nfpm.yaml new file mode 100644 index 000000000..5b7ea9d02 --- /dev/null +++ b/v3/examples/badge/build/linux/nfpm/nfpm.yaml @@ -0,0 +1,50 @@ +# Feel free to remove those if you don't want/need to use them. +# Make sure to check the documentation at https://nfpm.goreleaser.com +# +# The lines below are called `modelines`. See `:help modeline` + +name: "badge" +arch: ${GOARCH} +platform: "linux" +version: "0.1.0" +section: "default" +priority: "extra" +maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> +description: "My Product Description" +vendor: "My Company" +homepage: "https://wails.io" +license: "MIT" +release: "1" + +contents: + - src: "./bin/badge" + dst: "/usr/local/bin/badge" + - src: "./build/appicon.png" + dst: "/usr/share/icons/hicolor/128x128/apps/badge.png" + - src: "./build/linux/badge.desktop" + dst: "/usr/share/applications/badge.desktop" + +depends: + - gtk3 + - libwebkit2gtk + +# replaces: +# - foobar +# provides: +# - bar +# depends: +# - gtk3 +# - libwebkit2gtk +# recommends: +# - whatever +# suggests: +# - something-else +# conflicts: +# - not-foo +# - not-bar +# changelog: "changelog.yaml" +# scripts: +# preinstall: ./build/linux/nfpm/scripts/preinstall.sh +# postinstall: ./build/linux/nfpm/scripts/postinstall.sh +# preremove: ./build/linux/nfpm/scripts/preremove.sh +# postremove: ./build/linux/nfpm/scripts/postremove.sh diff --git a/v3/examples/badge/build/linux/nfpm/scripts/postinstall.sh b/v3/examples/badge/build/linux/nfpm/scripts/postinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge/build/linux/nfpm/scripts/postinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge/build/linux/nfpm/scripts/postremove.sh b/v3/examples/badge/build/linux/nfpm/scripts/postremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge/build/linux/nfpm/scripts/postremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge/build/linux/nfpm/scripts/preinstall.sh b/v3/examples/badge/build/linux/nfpm/scripts/preinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge/build/linux/nfpm/scripts/preinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge/build/linux/nfpm/scripts/preremove.sh b/v3/examples/badge/build/linux/nfpm/scripts/preremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge/build/linux/nfpm/scripts/preremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge/build/windows/Taskfile.yml b/v3/examples/badge/build/windows/Taskfile.yml new file mode 100644 index 000000000..534f4fb31 --- /dev/null +++ b/v3/examples/badge/build/windows/Taskfile.yml @@ -0,0 +1,63 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Windows + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - task: generate:syso + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe + - cmd: powershell Remove-item *.syso + platforms: [windows] + - cmd: rm -f *.syso + platforms: [linux, darwin] + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: windows + CGO_ENABLED: 0 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application into a `.exe` bundle + cmds: + - task: create:nsis:installer + + generate:syso: + summary: Generates Windows `.syso` file + dir: build + cmds: + - wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso + vars: + ARCH: '{{.ARCH | default ARCH}}' + + create:nsis:installer: + summary: Creates an NSIS installer + dir: build/windows/nsis + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + # Create the Microsoft WebView2 bootstrapper if it doesn't exist + - wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis" + - makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi + vars: + ARCH: '{{.ARCH | default ARCH}}' + ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}.exe' diff --git a/v3/examples/badge/build/windows/icon.ico b/v3/examples/badge/build/windows/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/badge/build/windows/icon.ico differ diff --git a/v3/examples/badge/build/windows/info.json b/v3/examples/badge/build/windows/info.json new file mode 100644 index 000000000..850b2b5b0 --- /dev/null +++ b/v3/examples/badge/build/windows/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "0.1.0" + }, + "info": { + "0000": { + "ProductVersion": "0.1.0", + "CompanyName": "My Company", + "FileDescription": "My Product Description", + "LegalCopyright": "© now, My Company", + "ProductName": "My Product", + "Comments": "This is a comment" + } + } +} \ No newline at end of file diff --git a/v3/examples/badge/build/windows/nsis/project.nsi b/v3/examples/badge/build/windows/nsis/project.nsi new file mode 100644 index 000000000..985b8e207 --- /dev/null +++ b/v3/examples/badge/build/windows/nsis/project.nsi @@ -0,0 +1,112 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the wails_tools.nsh file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "my-project" # Default "badge" +## !define INFO_COMPANYNAME "My Company" # Default "My Company" +## !define INFO_PRODUCTNAME "My Product Name" # Default "My Product" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.1.0" +## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "© now, My Company" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/v3/examples/badge/build/windows/nsis/wails_tools.nsh b/v3/examples/badge/build/windows/nsis/wails_tools.nsh new file mode 100644 index 000000000..6fc10ab79 --- /dev/null +++ b/v3/examples/badge/build/windows/nsis/wails_tools.nsh @@ -0,0 +1,212 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "badge" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "My Company" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "My Product" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "0.1.0" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "© now, My Company" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + +!macroend \ No newline at end of file diff --git a/v3/examples/badge/build/windows/wails.exe.manifest b/v3/examples/badge/build/windows/wails.exe.manifest new file mode 100644 index 000000000..fcfd2fc46 --- /dev/null +++ b/v3/examples/badge/build/windows/wails.exe.manifest @@ -0,0 +1,22 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + + + + + + + + \ No newline at end of file diff --git a/v3/examples/badge/frontend/Inter Font License.txt b/v3/examples/badge/frontend/Inter Font License.txt new file mode 100644 index 000000000..b525cbf3a --- /dev/null +++ b/v3/examples/badge/frontend/Inter Font License.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/dockservice.ts b/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/dockservice.ts new file mode 100644 index 000000000..abf1f22e4 --- /dev/null +++ b/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/dockservice.ts @@ -0,0 +1,53 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Service represents the dock service + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * HideAppIcon hides the app icon in the dock/taskbar. + */ +export function HideAppIcon(): $CancellablePromise { + return $Call.ByID(3413658144); +} + +/** + * RemoveBadge removes the badge label from the application icon. + * This method ensures the badge call is made on the main thread to avoid crashes. + */ +export function RemoveBadge(): $CancellablePromise { + return $Call.ByID(2752757297); +} + +/** + * SetBadge sets the badge label on the application icon. + * This method ensures the badge call is made on the main thread to avoid crashes. + */ +export function SetBadge(label: string): $CancellablePromise { + return $Call.ByID(1717705661, label); +} + +/** + * SetCustomBadge sets the badge label on the application icon with custom options. + * This method ensures the badge call is made on the main thread to avoid crashes. + */ +export function SetCustomBadge(label: string, options: $models.BadgeOptions): $CancellablePromise { + return $Call.ByID(2730169760, label, options); +} + +/** + * ShowAppIcon shows the app icon in the dock/taskbar. + */ +export function ShowAppIcon(): $CancellablePromise { + return $Call.ByID(3409697379); +} diff --git a/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/index.ts b/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/index.ts new file mode 100644 index 000000000..fbdaf19f3 --- /dev/null +++ b/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as DockService from "./dockservice.js"; +export { + DockService +}; + +export { + BadgeOptions +} from "./models.js"; diff --git a/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/models.ts b/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/models.ts new file mode 100644 index 000000000..f97c8a4c6 --- /dev/null +++ b/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/models.ts @@ -0,0 +1,61 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as color$0 from "../../../../../../../image/color/models.js"; + +/** + * BadgeOptions represents options for customizing badge appearance + */ +export class BadgeOptions { + "TextColour": color$0.RGBA; + "BackgroundColour": color$0.RGBA; + "FontName": string; + "FontSize": number; + "SmallFontSize": number; + + /** Creates a new BadgeOptions instance. */ + constructor($$source: Partial = {}) { + if (!("TextColour" in $$source)) { + this["TextColour"] = (new color$0.RGBA()); + } + if (!("BackgroundColour" in $$source)) { + this["BackgroundColour"] = (new color$0.RGBA()); + } + if (!("FontName" in $$source)) { + this["FontName"] = ""; + } + if (!("FontSize" in $$source)) { + this["FontSize"] = 0; + } + if (!("SmallFontSize" in $$source)) { + this["SmallFontSize"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new BadgeOptions instance from a string or object. + */ + static createFrom($$source: any = {}): BadgeOptions { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("TextColour" in $$parsedSource) { + $$parsedSource["TextColour"] = $$createField0_0($$parsedSource["TextColour"]); + } + if ("BackgroundColour" in $$parsedSource) { + $$parsedSource["BackgroundColour"] = $$createField1_0($$parsedSource["BackgroundColour"]); + } + return new BadgeOptions($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = color$0.RGBA.createFrom; diff --git a/v3/examples/badge/frontend/bindings/image/color/index.ts b/v3/examples/badge/frontend/bindings/image/color/index.ts new file mode 100644 index 000000000..97b507b08 --- /dev/null +++ b/v3/examples/badge/frontend/bindings/image/color/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + RGBA +} from "./models.js"; diff --git a/v3/examples/badge/frontend/bindings/image/color/models.ts b/v3/examples/badge/frontend/bindings/image/color/models.ts new file mode 100644 index 000000000..0d4eab56d --- /dev/null +++ b/v3/examples/badge/frontend/bindings/image/color/models.ts @@ -0,0 +1,46 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +/** + * RGBA represents a traditional 32-bit alpha-premultiplied color, having 8 + * bits for each of red, green, blue and alpha. + * + * An alpha-premultiplied color component C has been scaled by alpha (A), so + * has valid values 0 <= C <= A. + */ +export class RGBA { + "R": number; + "G": number; + "B": number; + "A": number; + + /** Creates a new RGBA instance. */ + constructor($$source: Partial = {}) { + if (!("R" in $$source)) { + this["R"] = 0; + } + if (!("G" in $$source)) { + this["G"] = 0; + } + if (!("B" in $$source)) { + this["B"] = 0; + } + if (!("A" in $$source)) { + this["A"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new RGBA instance from a string or object. + */ + static createFrom($$source: any = {}): RGBA { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new RGBA($$parsedSource as Partial); + } +} diff --git a/v3/examples/badge/frontend/dist/Inter-Medium.ttf b/v3/examples/badge/frontend/dist/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/badge/frontend/dist/Inter-Medium.ttf differ diff --git a/v3/examples/badge/frontend/dist/assets/index-CtfGGYXM.js b/v3/examples/badge/frontend/dist/assets/index-CtfGGYXM.js new file mode 100644 index 000000000..6149df5db --- /dev/null +++ b/v3/examples/badge/frontend/dist/assets/index-CtfGGYXM.js @@ -0,0 +1,1357 @@ +(function polyfill() { + const relList = document.createElement("link").relList; + if (relList && relList.supports && relList.supports("modulepreload")) { + return; + } + for (const link of document.querySelectorAll('link[rel="modulepreload"]')) { + processPreload(link); + } + new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type !== "childList") { + continue; + } + for (const node of mutation.addedNodes) { + if (node.tagName === "LINK" && node.rel === "modulepreload") + processPreload(node); + } + } + }).observe(document, { childList: true, subtree: true }); + function getFetchOpts(link) { + const fetchOpts = {}; + if (link.integrity) fetchOpts.integrity = link.integrity; + if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy; + if (link.crossOrigin === "use-credentials") + fetchOpts.credentials = "include"; + else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit"; + else fetchOpts.credentials = "same-origin"; + return fetchOpts; + } + function processPreload(link) { + if (link.ep) + return; + link.ep = true; + const fetchOpts = getFetchOpts(link); + fetch(link.href, fetchOpts); + } +})(); +const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; +function nanoid(size = 21) { + let id = ""; + let i = size | 0; + while (i--) { + id += urlAlphabet[Math.random() * 64 | 0]; + } + return id; +} +const runtimeURL = window.location.origin + "/wails/runtime"; +const objectNames = Object.freeze({ + Call: 0, + Clipboard: 1, + Application: 2, + Events: 3, + ContextMenu: 4, + Dialog: 5, + Window: 6, + Screens: 7, + System: 8, + Browser: 9, + CancelCall: 10 +}); +let clientId = nanoid(); +function newRuntimeCaller(object, windowName = "") { + return function(method, args = null) { + return runtimeCallWithID(object, method, windowName, args); + }; +} +async function runtimeCallWithID(objectID, method, windowName, args) { + var _a2, _b; + let url = new URL(runtimeURL); + url.searchParams.append("object", objectID.toString()); + url.searchParams.append("method", method.toString()); + if (args) { + url.searchParams.append("args", JSON.stringify(args)); + } + let headers = { + ["x-wails-client-id"]: clientId + }; + if (windowName) { + headers["x-wails-window-name"] = windowName; + } + let response = await fetch(url, { headers }); + if (!response.ok) { + throw new Error(await response.text()); + } + if (((_b = (_a2 = response.headers.get("Content-Type")) === null || _a2 === void 0 ? void 0 : _a2.indexOf("application/json")) !== null && _b !== void 0 ? _b : -1) !== -1) { + return response.json(); + } else { + return response.text(); + } +} +newRuntimeCaller(objectNames.System); +const _invoke = function() { + var _a2, _b, _c, _d, _e; + try { + if ((_b = (_a2 = window.chrome) === null || _a2 === void 0 ? void 0 : _a2.webview) === null || _b === void 0 ? void 0 : _b.postMessage) { + return window.chrome.webview.postMessage.bind(window.chrome.webview); + } else if ((_e = (_d = (_c = window.webkit) === null || _c === void 0 ? void 0 : _c.messageHandlers) === null || _d === void 0 ? void 0 : _d["external"]) === null || _e === void 0 ? void 0 : _e.postMessage) { + return window.webkit.messageHandlers["external"].postMessage.bind(window.webkit.messageHandlers["external"]); + } + } catch (e) { + } + console.warn("\n%c⚠️ Browser Environment Detected %c\n\n%cOnly UI previews are available in the browser. For full functionality, please run the application in desktop mode.\nMore information at: https://v3.wails.io/learn/build/#using-a-browser-for-development\n", "background: #ffffff; color: #000000; font-weight: bold; padding: 4px 8px; border-radius: 4px; border: 2px solid #000000;", "background: transparent;", "color: #ffffff; font-style: italic; font-weight: bold;"); + return null; +}(); +function invoke(msg) { + _invoke === null || _invoke === void 0 ? void 0 : _invoke(msg); +} +function IsWindows() { + return window._wails.environment.OS === "windows"; +} +function IsDebug() { + return Boolean(window._wails.environment.Debug); +} +function canTrackButtons() { + return new MouseEvent("mousedown").buttons === 0; +} +function eventTarget(event) { + var _a2; + if (event.target instanceof HTMLElement) { + return event.target; + } else if (!(event.target instanceof HTMLElement) && event.target instanceof Node) { + return (_a2 = event.target.parentElement) !== null && _a2 !== void 0 ? _a2 : document.body; + } else { + return document.body; + } +} +document.addEventListener("DOMContentLoaded", () => { +}); +window.addEventListener("contextmenu", contextMenuHandler); +const call$2 = newRuntimeCaller(objectNames.ContextMenu); +const ContextMenuOpen = 0; +function openContextMenu(id, x, y, data) { + void call$2(ContextMenuOpen, { id, x, y, data }); +} +function contextMenuHandler(event) { + const target = eventTarget(event); + const customContextMenu = window.getComputedStyle(target).getPropertyValue("--custom-contextmenu").trim(); + if (customContextMenu) { + event.preventDefault(); + const data = window.getComputedStyle(target).getPropertyValue("--custom-contextmenu-data"); + openContextMenu(customContextMenu, event.clientX, event.clientY, data); + } else { + processDefaultContextMenu(event, target); + } +} +function processDefaultContextMenu(event, target) { + if (IsDebug()) { + return; + } + switch (window.getComputedStyle(target).getPropertyValue("--default-contextmenu").trim()) { + case "show": + return; + case "hide": + event.preventDefault(); + return; + } + if (target.isContentEditable) { + return; + } + const selection = window.getSelection(); + const hasSelection = selection && selection.toString().length > 0; + if (hasSelection) { + for (let i = 0; i < selection.rangeCount; i++) { + const range = selection.getRangeAt(i); + const rects = range.getClientRects(); + for (let j = 0; j < rects.length; j++) { + const rect = rects[j]; + if (document.elementFromPoint(rect.left, rect.top) === target) { + return; + } + } + } + } + if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) { + if (hasSelection || !target.readOnly && !target.disabled) { + return; + } + } + event.preventDefault(); +} +function GetFlag(key) { + try { + return window._wails.flags[key]; + } catch (e) { + throw new Error("Unable to retrieve flag '" + key + "': " + e, { cause: e }); + } +} +let canDrag = false; +let dragging = false; +let resizable = false; +let canResize = false; +let resizing = false; +let resizeEdge = ""; +let defaultCursor = "auto"; +let buttons = 0; +const buttonsTracked = canTrackButtons(); +window._wails = window._wails || {}; +window._wails.setResizable = (value) => { + resizable = value; + if (!resizable) { + canResize = resizing = false; + setResize(); + } +}; +window.addEventListener("mousedown", update, { capture: true }); +window.addEventListener("mousemove", update, { capture: true }); +window.addEventListener("mouseup", update, { capture: true }); +for (const ev of ["click", "contextmenu", "dblclick"]) { + window.addEventListener(ev, suppressEvent, { capture: true }); +} +function suppressEvent(event) { + if (dragging || resizing) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } +} +const MouseDown = 0; +const MouseUp = 1; +const MouseMove = 2; +function update(event) { + let eventType, eventButtons = event.buttons; + switch (event.type) { + case "mousedown": + eventType = MouseDown; + if (!buttonsTracked) { + eventButtons = buttons | 1 << event.button; + } + break; + case "mouseup": + eventType = MouseUp; + if (!buttonsTracked) { + eventButtons = buttons & ~(1 << event.button); + } + break; + default: + eventType = MouseMove; + if (!buttonsTracked) { + eventButtons = buttons; + } + break; + } + let released = buttons & ~eventButtons; + let pressed = eventButtons & ~buttons; + buttons = eventButtons; + if (eventType === MouseDown && !(pressed & event.button)) { + released |= 1 << event.button; + pressed |= 1 << event.button; + } + if (eventType !== MouseMove && resizing || dragging && (eventType === MouseDown || event.button !== 0)) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } + if (released & 1) { + primaryUp(); + } + if (pressed & 1) { + primaryDown(event); + } + if (eventType === MouseMove) { + onMouseMove(event); + } +} +function primaryDown(event) { + canDrag = false; + canResize = false; + if (!IsWindows()) { + if (event.type === "mousedown" && event.button === 0 && event.detail !== 1) { + return; + } + } + if (resizeEdge) { + canResize = true; + return; + } + const target = eventTarget(event); + const style = window.getComputedStyle(target); + canDrag = style.getPropertyValue("--wails-draggable").trim() === "drag" && (event.offsetX - parseFloat(style.paddingLeft) < target.clientWidth && event.offsetY - parseFloat(style.paddingTop) < target.clientHeight); +} +function primaryUp(event) { + canDrag = false; + dragging = false; + canResize = false; + resizing = false; +} +const cursorForEdge = Object.freeze({ + "se-resize": "nwse-resize", + "sw-resize": "nesw-resize", + "nw-resize": "nwse-resize", + "ne-resize": "nesw-resize", + "w-resize": "ew-resize", + "n-resize": "ns-resize", + "s-resize": "ns-resize", + "e-resize": "ew-resize" +}); +function setResize(edge) { + if (edge) { + if (!resizeEdge) { + defaultCursor = document.body.style.cursor; + } + document.body.style.cursor = cursorForEdge[edge]; + } else if (!edge && resizeEdge) { + document.body.style.cursor = defaultCursor; + } + resizeEdge = edge || ""; +} +function onMouseMove(event) { + if (canResize && resizeEdge) { + resizing = true; + invoke("wails:resize:" + resizeEdge); + } else if (canDrag) { + dragging = true; + invoke("wails:drag"); + } + if (dragging || resizing) { + canDrag = canResize = false; + return; + } + if (!resizable || !IsWindows()) { + if (resizeEdge) { + setResize(); + } + return; + } + const resizeHandleHeight = GetFlag("system.resizeHandleHeight") || 5; + const resizeHandleWidth = GetFlag("system.resizeHandleWidth") || 5; + const cornerExtra = GetFlag("resizeCornerExtra") || 10; + const rightBorder = window.outerWidth - event.clientX < resizeHandleWidth; + const leftBorder = event.clientX < resizeHandleWidth; + const topBorder = event.clientY < resizeHandleHeight; + const bottomBorder = window.outerHeight - event.clientY < resizeHandleHeight; + const rightCorner = window.outerWidth - event.clientX < resizeHandleWidth + cornerExtra; + const leftCorner = event.clientX < resizeHandleWidth + cornerExtra; + const topCorner = event.clientY < resizeHandleHeight + cornerExtra; + const bottomCorner = window.outerHeight - event.clientY < resizeHandleHeight + cornerExtra; + if (!leftCorner && !topCorner && !bottomCorner && !rightCorner) { + setResize(); + } else if (rightCorner && bottomCorner) + setResize("se-resize"); + else if (leftCorner && bottomCorner) + setResize("sw-resize"); + else if (leftCorner && topCorner) + setResize("nw-resize"); + else if (topCorner && rightCorner) + setResize("ne-resize"); + else if (leftBorder) + setResize("w-resize"); + else if (topBorder) + setResize("n-resize"); + else if (bottomBorder) + setResize("s-resize"); + else if (rightBorder) + setResize("e-resize"); + else + setResize(); +} +var fnToStr = Function.prototype.toString; +var reflectApply = typeof Reflect === "object" && Reflect !== null && Reflect.apply; +var badArrayLike; +var isCallableMarker; +if (typeof reflectApply === "function" && typeof Object.defineProperty === "function") { + try { + badArrayLike = Object.defineProperty({}, "length", { + get: function() { + throw isCallableMarker; + } + }); + isCallableMarker = {}; + reflectApply(function() { + throw 42; + }, null, badArrayLike); + } catch (_) { + if (_ !== isCallableMarker) { + reflectApply = null; + } + } +} else { + reflectApply = null; +} +var constructorRegex = /^\s*class\b/; +var isES6ClassFn = function isES6ClassFunction(value) { + try { + var fnStr = fnToStr.call(value); + return constructorRegex.test(fnStr); + } catch (e) { + return false; + } +}; +var tryFunctionObject = function tryFunctionToStr(value) { + try { + if (isES6ClassFn(value)) { + return false; + } + fnToStr.call(value); + return true; + } catch (e) { + return false; + } +}; +var toStr = Object.prototype.toString; +var objectClass = "[object Object]"; +var fnClass = "[object Function]"; +var genClass = "[object GeneratorFunction]"; +var ddaClass = "[object HTMLAllCollection]"; +var ddaClass2 = "[object HTML document.all class]"; +var ddaClass3 = "[object HTMLCollection]"; +var hasToStringTag = typeof Symbol === "function" && !!Symbol.toStringTag; +var isIE68 = !(0 in [,]); +var isDDA = function isDocumentDotAll() { + return false; +}; +if (typeof document === "object") { + var all = document.all; + if (toStr.call(all) === toStr.call(document.all)) { + isDDA = function isDocumentDotAll2(value) { + if ((isIE68 || !value) && (typeof value === "undefined" || typeof value === "object")) { + try { + var str = toStr.call(value); + return (str === ddaClass || str === ddaClass2 || str === ddaClass3 || str === objectClass) && value("") == null; + } catch (e) { + } + } + return false; + }; + } +} +function isCallableRefApply(value) { + if (isDDA(value)) { + return true; + } + if (!value) { + return false; + } + if (typeof value !== "function" && typeof value !== "object") { + return false; + } + try { + reflectApply(value, null, badArrayLike); + } catch (e) { + if (e !== isCallableMarker) { + return false; + } + } + return !isES6ClassFn(value) && tryFunctionObject(value); +} +function isCallableNoRefApply(value) { + if (isDDA(value)) { + return true; + } + if (!value) { + return false; + } + if (typeof value !== "function" && typeof value !== "object") { + return false; + } + if (hasToStringTag) { + return tryFunctionObject(value); + } + if (isES6ClassFn(value)) { + return false; + } + var strClass = toStr.call(value); + if (strClass !== fnClass && strClass !== genClass && !/^\[object HTML/.test(strClass)) { + return false; + } + return tryFunctionObject(value); +} +const isCallable = reflectApply ? isCallableRefApply : isCallableNoRefApply; +var _a; +class CancelError extends Error { + /** + * Constructs a new `CancelError` instance. + * @param message - The error message. + * @param options - Options to be forwarded to the Error constructor. + */ + constructor(message, options) { + super(message, options); + this.name = "CancelError"; + } +} +class CancelledRejectionError extends Error { + /** + * Constructs a new `CancelledRejectionError` instance. + * @param promise - The promise that caused the error originally. + * @param reason - The rejection reason. + * @param info - An optional informative message specifying the circumstances in which the error was thrown. + * Defaults to the string `"Unhandled rejection in cancelled promise."`. + */ + constructor(promise, reason, info) { + super((info !== null && info !== void 0 ? info : "Unhandled rejection in cancelled promise.") + " Reason: " + errorMessage(reason), { cause: reason }); + this.promise = promise; + this.name = "CancelledRejectionError"; + } +} +const barrierSym = Symbol("barrier"); +const cancelImplSym = Symbol("cancelImpl"); +const species = (_a = Symbol.species) !== null && _a !== void 0 ? _a : Symbol("speciesPolyfill"); +class CancellablePromise extends Promise { + /** + * Creates a new `CancellablePromise`. + * + * @param executor - A callback used to initialize the promise. This callback is passed two arguments: + * a `resolve` callback used to resolve the promise with a value + * or the result of another promise (possibly cancellable), + * and a `reject` callback used to reject the promise with a provided reason or error. + * If the value provided to the `resolve` callback is a thenable _and_ cancellable object + * (it has a `then` _and_ a `cancel` method), + * cancellation requests will be forwarded to that object and the oncancelled will not be invoked anymore. + * If any one of the two callbacks is called _after_ the promise has been cancelled, + * the provided values will be cancelled and resolved as usual, + * but their results will be discarded. + * However, if the resolution process ultimately ends up in a rejection + * that is not due to cancellation, the rejection reason + * will be wrapped in a {@link CancelledRejectionError} + * and bubbled up as an unhandled rejection. + * @param oncancelled - It is the caller's responsibility to ensure that any operation + * started by the executor is properly halted upon cancellation. + * This optional callback can be used to that purpose. + * It will be called _synchronously_ with a cancellation cause + * when cancellation is requested, _after_ the promise has already rejected + * with a {@link CancelError}, but _before_ + * any {@link then}/{@link catch}/{@link finally} callback runs. + * If the callback returns a thenable, the promise returned from {@link cancel} + * will only fulfill after the former has settled. + * Unhandled exceptions or rejections from the callback will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as unhandled rejections. + * If the `resolve` callback is called before cancellation with a cancellable promise, + * cancellation requests on this promise will be diverted to that promise, + * and the original `oncancelled` callback will be discarded. + */ + constructor(executor, oncancelled) { + let resolve; + let reject; + super((res, rej) => { + resolve = res; + reject = rej; + }); + if (this.constructor[species] !== Promise) { + throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property."); + } + let promise = { + promise: this, + resolve, + reject, + get oncancelled() { + return oncancelled !== null && oncancelled !== void 0 ? oncancelled : null; + }, + set oncancelled(cb) { + oncancelled = cb !== null && cb !== void 0 ? cb : void 0; + } + }; + const state = { + get root() { + return state; + }, + resolving: false, + settled: false + }; + void Object.defineProperties(this, { + [barrierSym]: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + [cancelImplSym]: { + configurable: false, + enumerable: false, + writable: false, + value: cancellerFor(promise, state) + } + }); + const rejector = rejectorFor(promise, state); + try { + executor(resolverFor(promise, state), rejector); + } catch (err) { + if (state.resolving) { + console.log("Unhandled exception in CancellablePromise executor.", err); + } else { + rejector(err); + } + } + } + /** + * Cancels immediately the execution of the operation associated with this promise. + * The promise rejects with a {@link CancelError} instance as reason, + * with the {@link CancelError#cause} property set to the given argument, if any. + * + * Has no effect if called after the promise has already settled; + * repeated calls in particular are safe, but only the first one + * will set the cancellation cause. + * + * The `CancelError` exception _need not_ be handled explicitly _on the promises that are being cancelled:_ + * cancelling a promise with no attached rejection handler does not trigger an unhandled rejection event. + * Therefore, the following idioms are all equally correct: + * ```ts + * new CancellablePromise((resolve, reject) => { ... }).cancel(); + * new CancellablePromise((resolve, reject) => { ... }).then(...).cancel(); + * new CancellablePromise((resolve, reject) => { ... }).then(...).catch(...).cancel(); + * ``` + * Whenever some cancelled promise in a chain rejects with a `CancelError` + * with the same cancellation cause as itself, the error will be discarded silently. + * However, the `CancelError` _will still be delivered_ to all attached rejection handlers + * added by {@link then} and related methods: + * ```ts + * let cancellable = new CancellablePromise((resolve, reject) => { ... }); + * cancellable.then(() => { ... }).catch(console.log); + * cancellable.cancel(); // A CancelError is printed to the console. + * ``` + * If the `CancelError` is not handled downstream by the time it reaches + * a _non-cancelled_ promise, it _will_ trigger an unhandled rejection event, + * just like normal rejections would: + * ```ts + * let cancellable = new CancellablePromise((resolve, reject) => { ... }); + * let chained = cancellable.then(() => { ... }).then(() => { ... }); // No catch... + * cancellable.cancel(); // Unhandled rejection event on chained! + * ``` + * Therefore, it is important to either cancel whole promise chains from their tail, + * as shown in the correct idioms above, or take care of handling errors everywhere. + * + * @returns A cancellable promise that _fulfills_ after the cancel callback (if any) + * and all handlers attached up to the call to cancel have run. + * If the cancel callback returns a thenable, the promise returned by `cancel` + * will also wait for that thenable to settle. + * This enables callers to wait for the cancelled operation to terminate + * without being forced to handle potential errors at the call site. + * ```ts + * cancellable.cancel().then(() => { + * // Cleanup finished, it's safe to do something else. + * }, (err) => { + * // Unreachable: the promise returned from cancel will never reject. + * }); + * ``` + * Note that the returned promise will _not_ handle implicitly any rejection + * that might have occurred already in the cancelled chain. + * It will just track whether registered handlers have been executed or not. + * Therefore, unhandled rejections will never be silently handled by calling cancel. + */ + cancel(cause) { + return new CancellablePromise((resolve) => { + Promise.all([ + this[cancelImplSym](new CancelError("Promise cancelled.", { cause })), + currentBarrier(this) + ]).then(() => resolve(), () => resolve()); + }); + } + /** + * Binds promise cancellation to the abort event of the given {@link AbortSignal}. + * If the signal has already aborted, the promise will be cancelled immediately. + * When either condition is verified, the cancellation cause will be set + * to the signal's abort reason (see {@link AbortSignal#reason}). + * + * Has no effect if called (or if the signal aborts) _after_ the promise has already settled. + * Only the first signal to abort will set the cancellation cause. + * + * For more details about the cancellation process, + * see {@link cancel} and the `CancellablePromise` constructor. + * + * This method enables `await`ing cancellable promises without having + * to store them for future cancellation, e.g.: + * ```ts + * await longRunningOperation().cancelOn(signal); + * ``` + * instead of: + * ```ts + * let promiseToBeCancelled = longRunningOperation(); + * await promiseToBeCancelled; + * ``` + * + * @returns This promise, for method chaining. + */ + cancelOn(signal) { + if (signal.aborted) { + void this.cancel(signal.reason); + } else { + signal.addEventListener("abort", () => void this.cancel(signal.reason), { capture: true }); + } + return this; + } + /** + * Attaches callbacks for the resolution and/or rejection of the `CancellablePromise`. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * When the parent promise rejects or is cancelled, the `onrejected` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A `CancellablePromise` for the completion of whichever callback is executed. + * The returned promise is hooked up to propagate cancellation requests up the chain, but not down: + * + * - if the parent promise is cancelled, the `onrejected` handler will be invoked with a `CancelError` + * and the returned promise _will resolve regularly_ with its result; + * - conversely, if the returned promise is cancelled, _the parent promise is cancelled too;_ + * the `onrejected` handler will still be invoked with the parent's `CancelError`, + * but its result will be discarded + * and the returned promise will reject with a `CancelError` as well. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If either callback returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + */ + then(onfulfilled, onrejected, oncancelled) { + if (!(this instanceof CancellablePromise)) { + throw new TypeError("CancellablePromise.prototype.then called on an invalid object."); + } + if (!isCallable(onfulfilled)) { + onfulfilled = identity; + } + if (!isCallable(onrejected)) { + onrejected = thrower; + } + if (onfulfilled === identity && onrejected == thrower) { + return new CancellablePromise((resolve) => resolve(this)); + } + const barrier = {}; + this[barrierSym] = barrier; + return new CancellablePromise((resolve, reject) => { + void super.then((value) => { + var _a2; + if (this[barrierSym] === barrier) { + this[barrierSym] = null; + } + (_a2 = barrier.resolve) === null || _a2 === void 0 ? void 0 : _a2.call(barrier); + try { + resolve(onfulfilled(value)); + } catch (err) { + reject(err); + } + }, (reason) => { + var _a2; + if (this[barrierSym] === barrier) { + this[barrierSym] = null; + } + (_a2 = barrier.resolve) === null || _a2 === void 0 ? void 0 : _a2.call(barrier); + try { + resolve(onrejected(reason)); + } catch (err) { + reject(err); + } + }); + }, async (cause) => { + try { + return oncancelled === null || oncancelled === void 0 ? void 0 : oncancelled(cause); + } finally { + await this.cancel(cause); + } + }); + } + /** + * Attaches a callback for only the rejection of the Promise. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * When the parent promise rejects or is cancelled, the `onrejected` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * It is equivalent to + * ```ts + * cancellablePromise.then(undefined, onrejected, oncancelled); + * ``` + * and the same caveats apply. + * + * @returns A Promise for the completion of the callback. + * Cancellation requests on the returned promise + * will propagate up the chain to the parent promise, + * but not in the other direction. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If `onrejected` returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + * See {@link then} for more details. + */ + catch(onrejected, oncancelled) { + return this.then(void 0, onrejected, oncancelled); + } + /** + * Attaches a callback that is invoked when the CancellablePromise is settled (fulfilled or rejected). The + * resolved value cannot be accessed or modified from the callback. + * The returned promise will settle in the same state as the original one + * after the provided callback has completed execution, + * unless the callback throws or returns a rejecting promise, + * in which case the returned promise will reject as well. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * Once the parent promise settles, the `onfinally` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * This method is implemented in terms of {@link then} and the same caveats apply. + * It is polyfilled, hence available in every OS/webview version. + * + * @returns A Promise for the completion of the callback. + * Cancellation requests on the returned promise + * will propagate up the chain to the parent promise, + * but not in the other direction. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If `onfinally` returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + * See {@link then} for more details. + */ + finally(onfinally, oncancelled) { + if (!(this instanceof CancellablePromise)) { + throw new TypeError("CancellablePromise.prototype.finally called on an invalid object."); + } + if (!isCallable(onfinally)) { + return this.then(onfinally, onfinally, oncancelled); + } + return this.then((value) => CancellablePromise.resolve(onfinally()).then(() => value), (reason) => CancellablePromise.resolve(onfinally()).then(() => { + throw reason; + }), oncancelled); + } + /** + * We use the `[Symbol.species]` static property, if available, + * to disable the built-in automatic subclassing features from {@link Promise}. + * It is critical for performance reasons that extenders do not override this. + * Once the proposal at https://github.com/tc39/proposal-rm-builtin-subclassing + * is either accepted or retired, this implementation will have to be revised accordingly. + * + * @ignore + * @internal + */ + static get [species]() { + return Promise; + } + static all(values) { + let collected = Array.from(values); + const promise = collected.length === 0 ? CancellablePromise.resolve(collected) : new CancellablePromise((resolve, reject) => { + void Promise.all(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + static allSettled(values) { + let collected = Array.from(values); + const promise = collected.length === 0 ? CancellablePromise.resolve(collected) : new CancellablePromise((resolve, reject) => { + void Promise.allSettled(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + static any(values) { + let collected = Array.from(values); + const promise = collected.length === 0 ? CancellablePromise.resolve(collected) : new CancellablePromise((resolve, reject) => { + void Promise.any(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + static race(values) { + let collected = Array.from(values); + const promise = new CancellablePromise((resolve, reject) => { + void Promise.race(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + /** + * Creates a new cancelled CancellablePromise for the provided cause. + * + * @group Static Methods + */ + static cancel(cause) { + const p = new CancellablePromise(() => { + }); + p.cancel(cause); + return p; + } + /** + * Creates a new CancellablePromise that cancels + * after the specified timeout, with the provided cause. + * + * If the {@link AbortSignal.timeout} factory method is available, + * it is used to base the timeout on _active_ time rather than _elapsed_ time. + * Otherwise, `timeout` falls back to {@link setTimeout}. + * + * @group Static Methods + */ + static timeout(milliseconds, cause) { + const promise = new CancellablePromise(() => { + }); + if (AbortSignal && typeof AbortSignal === "function" && AbortSignal.timeout && typeof AbortSignal.timeout === "function") { + AbortSignal.timeout(milliseconds).addEventListener("abort", () => void promise.cancel(cause)); + } else { + setTimeout(() => void promise.cancel(cause), milliseconds); + } + return promise; + } + static sleep(milliseconds, value) { + return new CancellablePromise((resolve) => { + setTimeout(() => resolve(value), milliseconds); + }); + } + /** + * Creates a new rejected CancellablePromise for the provided reason. + * + * @group Static Methods + */ + static reject(reason) { + return new CancellablePromise((_, reject) => reject(reason)); + } + static resolve(value) { + if (value instanceof CancellablePromise) { + return value; + } + return new CancellablePromise((resolve) => resolve(value)); + } + /** + * Creates a new CancellablePromise and returns it in an object, along with its resolve and reject functions + * and a getter/setter for the cancellation callback. + * + * This method is polyfilled, hence available in every OS/webview version. + * + * @group Static Methods + */ + static withResolvers() { + let result = { oncancelled: null }; + result.promise = new CancellablePromise((resolve, reject) => { + result.resolve = resolve; + result.reject = reject; + }, (cause) => { + var _a2; + (_a2 = result.oncancelled) === null || _a2 === void 0 ? void 0 : _a2.call(result, cause); + }); + return result; + } +} +function cancellerFor(promise, state) { + let cancellationPromise = void 0; + return (reason) => { + if (!state.settled) { + state.settled = true; + state.reason = reason; + promise.reject(reason); + void Promise.prototype.then.call(promise.promise, void 0, (err) => { + if (err !== reason) { + throw err; + } + }); + } + if (!state.reason || !promise.oncancelled) { + return; + } + cancellationPromise = new Promise((resolve) => { + try { + resolve(promise.oncancelled(state.reason.cause)); + } catch (err) { + Promise.reject(new CancelledRejectionError(promise.promise, err, "Unhandled exception in oncancelled callback.")); + } + }).catch((reason2) => { + Promise.reject(new CancelledRejectionError(promise.promise, reason2, "Unhandled rejection in oncancelled callback.")); + }); + promise.oncancelled = null; + return cancellationPromise; + }; +} +function resolverFor(promise, state) { + return (value) => { + if (state.resolving) { + return; + } + state.resolving = true; + if (value === promise.promise) { + if (state.settled) { + return; + } + state.settled = true; + promise.reject(new TypeError("A promise cannot be resolved with itself.")); + return; + } + if (value != null && (typeof value === "object" || typeof value === "function")) { + let then; + try { + then = value.then; + } catch (err) { + state.settled = true; + promise.reject(err); + return; + } + if (isCallable(then)) { + try { + let cancel = value.cancel; + if (isCallable(cancel)) { + const oncancelled = (cause) => { + Reflect.apply(cancel, value, [cause]); + }; + if (state.reason) { + void cancellerFor(Object.assign(Object.assign({}, promise), { oncancelled }), state)(state.reason); + } else { + promise.oncancelled = oncancelled; + } + } + } catch (_a2) { + } + const newState = { + root: state.root, + resolving: false, + get settled() { + return this.root.settled; + }, + set settled(value2) { + this.root.settled = value2; + }, + get reason() { + return this.root.reason; + } + }; + const rejector = rejectorFor(promise, newState); + try { + Reflect.apply(then, value, [resolverFor(promise, newState), rejector]); + } catch (err) { + rejector(err); + } + return; + } + } + if (state.settled) { + return; + } + state.settled = true; + promise.resolve(value); + }; +} +function rejectorFor(promise, state) { + return (reason) => { + if (state.resolving) { + return; + } + state.resolving = true; + if (state.settled) { + try { + if (reason instanceof CancelError && state.reason instanceof CancelError && Object.is(reason.cause, state.reason.cause)) { + return; + } + } catch (_a2) { + } + void Promise.reject(new CancelledRejectionError(promise.promise, reason)); + } else { + state.settled = true; + promise.reject(reason); + } + }; +} +function cancelAll(parent, values, cause) { + const results = []; + for (const value of values) { + let cancel; + try { + if (!isCallable(value.then)) { + continue; + } + cancel = value.cancel; + if (!isCallable(cancel)) { + continue; + } + } catch (_a2) { + continue; + } + let result; + try { + result = Reflect.apply(cancel, value, [cause]); + } catch (err) { + Promise.reject(new CancelledRejectionError(parent, err, "Unhandled exception in cancel method.")); + continue; + } + if (!result) { + continue; + } + results.push((result instanceof Promise ? result : Promise.resolve(result)).catch((reason) => { + Promise.reject(new CancelledRejectionError(parent, reason, "Unhandled rejection in cancel method.")); + })); + } + return Promise.all(results); +} +function identity(x) { + return x; +} +function thrower(reason) { + throw reason; +} +function errorMessage(err) { + try { + if (err instanceof Error || typeof err !== "object" || err.toString !== Object.prototype.toString) { + return "" + err; + } + } catch (_a2) { + } + try { + return JSON.stringify(err); + } catch (_b) { + } + try { + return Object.prototype.toString.call(err); + } catch (_c) { + } + return ""; +} +function currentBarrier(promise) { + var _a2; + let pwr = (_a2 = promise[barrierSym]) !== null && _a2 !== void 0 ? _a2 : {}; + if (!("promise" in pwr)) { + Object.assign(pwr, promiseWithResolvers()); + } + if (promise[barrierSym] == null) { + pwr.resolve(); + promise[barrierSym] = pwr; + } + return pwr.promise; +} +let promiseWithResolvers = Promise.withResolvers; +if (promiseWithResolvers && typeof promiseWithResolvers === "function") { + promiseWithResolvers = promiseWithResolvers.bind(Promise); +} else { + promiseWithResolvers = function() { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; + }; +} +window._wails = window._wails || {}; +window._wails.callResultHandler = resultHandler; +window._wails.callErrorHandler = errorHandler; +const call$1 = newRuntimeCaller(objectNames.Call); +const cancelCall = newRuntimeCaller(objectNames.CancelCall); +const callResponses = /* @__PURE__ */ new Map(); +const CallBinding = 0; +const CancelMethod = 0; +class RuntimeError extends Error { + /** + * Constructs a new RuntimeError instance. + * @param message - The error message. + * @param options - Options to be forwarded to the Error constructor. + */ + constructor(message, options) { + super(message, options); + this.name = "RuntimeError"; + } +} +function resultHandler(id, data, isJSON) { + const resolvers = getAndDeleteResponse(id); + if (!resolvers) { + return; + } + if (!data) { + resolvers.resolve(void 0); + } else if (!isJSON) { + resolvers.resolve(data); + } else { + try { + resolvers.resolve(JSON.parse(data)); + } catch (err) { + resolvers.reject(new TypeError("could not parse result: " + err.message, { cause: err })); + } + } +} +function errorHandler(id, data, isJSON) { + const resolvers = getAndDeleteResponse(id); + if (!resolvers) { + return; + } + if (!isJSON) { + resolvers.reject(new Error(data)); + } else { + let error; + try { + error = JSON.parse(data); + } catch (err) { + resolvers.reject(new TypeError("could not parse error: " + err.message, { cause: err })); + return; + } + let options = {}; + if (error.cause) { + options.cause = error.cause; + } + let exception; + switch (error.kind) { + case "ReferenceError": + exception = new ReferenceError(error.message, options); + break; + case "TypeError": + exception = new TypeError(error.message, options); + break; + case "RuntimeError": + exception = new RuntimeError(error.message, options); + break; + default: + exception = new Error(error.message, options); + break; + } + resolvers.reject(exception); + } +} +function getAndDeleteResponse(id) { + const response = callResponses.get(id); + callResponses.delete(id); + return response; +} +function generateID() { + let result; + do { + result = nanoid(); + } while (callResponses.has(result)); + return result; +} +function Call(options) { + const id = generateID(); + const result = CancellablePromise.withResolvers(); + callResponses.set(id, { resolve: result.resolve, reject: result.reject }); + const request = call$1(CallBinding, Object.assign({ "call-id": id }, options)); + let running = false; + request.then(() => { + running = true; + }, (err) => { + callResponses.delete(id); + result.reject(err); + }); + const cancel = () => { + callResponses.delete(id); + return cancelCall(CancelMethod, { "call-id": id }).catch((err) => { + console.error("Error while requesting binding call cancellation:", err); + }); + }; + result.oncancelled = () => { + if (running) { + return cancel(); + } else { + return request.then(cancel); + } + }; + return result.promise; +} +function ByID(methodID, ...args) { + return Call({ methodID, args }); +} +const eventListeners = /* @__PURE__ */ new Map(); +class Listener { + constructor(eventName, callback, maxCallbacks) { + this.eventName = eventName; + this.callback = callback; + this.maxCallbacks = maxCallbacks || -1; + } + dispatch(data) { + try { + this.callback(data); + } catch (err) { + console.error(err); + } + if (this.maxCallbacks === -1) + return false; + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + } +} +function listenerOff(listener) { + let listeners = eventListeners.get(listener.eventName); + if (!listeners) { + return; + } + listeners = listeners.filter((l) => l !== listener); + if (listeners.length === 0) { + eventListeners.delete(listener.eventName); + } else { + eventListeners.set(listener.eventName, listeners); + } +} +window._wails = window._wails || {}; +window._wails.dispatchWailsEvent = dispatchWailsEvent; +const call = newRuntimeCaller(objectNames.Events); +const EmitMethod = 0; +class WailsEvent { + constructor(name, data = null) { + this.name = name; + this.data = data; + } +} +function dispatchWailsEvent(event) { + let listeners = eventListeners.get(event.name); + if (!listeners) { + return; + } + let wailsEvent = new WailsEvent(event.name, event.data); + if ("sender" in event) { + wailsEvent.sender = event.sender; + } + listeners = listeners.filter((listener) => !listener.dispatch(wailsEvent)); + if (listeners.length === 0) { + eventListeners.delete(event.name); + } else { + eventListeners.set(event.name, listeners); + } +} +function OnMultiple(eventName, callback, maxCallbacks) { + let listeners = eventListeners.get(eventName) || []; + const thisListener = new Listener(eventName, callback, maxCallbacks); + listeners.push(thisListener); + eventListeners.set(eventName, listeners); + return () => listenerOff(thisListener); +} +function On(eventName, callback) { + return OnMultiple(eventName, callback, -1); +} +function Emit(event) { + return call(EmitMethod, event); +} +window._wails = window._wails || {}; +window._wails.invoke = invoke; +invoke("wails:runtime:ready"); +function RemoveBadge() { + return ByID(2752757297); +} +function SetBadge(label) { + return ByID(1717705661, label); +} +const setButton = document.getElementById("set"); +const removeButton = document.getElementById("remove"); +const setButtonUsingGo = document.getElementById("set-go"); +const removeButtonUsingGo = document.getElementById("remove-go"); +const labelElement = document.getElementById("label"); +const timeElement = document.getElementById("time"); +setButton.addEventListener("click", () => { + let label = labelElement.value; + SetBadge(label); +}); +removeButton.addEventListener("click", () => { + RemoveBadge(); +}); +setButtonUsingGo.addEventListener("click", () => { + let label = labelElement.value; + void Emit({ + name: "set:badge", + data: label + }); +}); +removeButtonUsingGo.addEventListener("click", () => { + void Emit({ name: "remove:badge", data: null }); +}); +On("time", (time) => { + timeElement.innerText = time.data; +}); diff --git a/v3/examples/badge/frontend/dist/index.html b/v3/examples/badge/frontend/dist/index.html new file mode 100644 index 000000000..b8ca49f06 --- /dev/null +++ b/v3/examples/badge/frontend/dist/index.html @@ -0,0 +1,38 @@ + + + + + + + + Wails App + + + +
+ +

Wails + Typescript

+
Set a badge label below 👇
+
+
+ + + + + +
+
+ +
+ + diff --git a/v3/examples/badge/frontend/dist/style.css b/v3/examples/badge/frontend/dist/style.css new file mode 100644 index 000000000..6ce81cad2 --- /dev/null +++ b/v3/examples/badge/frontend/dist/style.css @@ -0,0 +1,155 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/examples/badge/frontend/dist/typescript.svg b/v3/examples/badge/frontend/dist/typescript.svg new file mode 100644 index 000000000..d91c910cc --- /dev/null +++ b/v3/examples/badge/frontend/dist/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/badge/frontend/dist/wails.png b/v3/examples/badge/frontend/dist/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/badge/frontend/dist/wails.png differ diff --git a/v3/examples/badge/frontend/index.html b/v3/examples/badge/frontend/index.html new file mode 100644 index 000000000..616cb4c0f --- /dev/null +++ b/v3/examples/badge/frontend/index.html @@ -0,0 +1,38 @@ + + + + + + + + Wails App + + +
+ +

Wails + Typescript

+
Set a badge label below 👇
+
+
+ + + + + +
+
+ +
+ + + diff --git a/v3/examples/badge/frontend/package.json b/v3/examples/badge/frontend/package.json new file mode 100644 index 000000000..b39da7ece --- /dev/null +++ b/v3/examples/badge/frontend/package.json @@ -0,0 +1,19 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "tsc && vite build --minify false --mode development", + "build": "tsc && vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "typescript": "^4.9.3", + "vite": "^5.0.0" + } +} diff --git a/v3/examples/badge/frontend/public/Inter-Medium.ttf b/v3/examples/badge/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/badge/frontend/public/Inter-Medium.ttf differ diff --git a/v3/examples/badge/frontend/public/style.css b/v3/examples/badge/frontend/public/style.css new file mode 100644 index 000000000..6ce81cad2 --- /dev/null +++ b/v3/examples/badge/frontend/public/style.css @@ -0,0 +1,155 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/examples/badge/frontend/public/typescript.svg b/v3/examples/badge/frontend/public/typescript.svg new file mode 100644 index 000000000..d91c910cc --- /dev/null +++ b/v3/examples/badge/frontend/public/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/badge/frontend/public/wails.png b/v3/examples/badge/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/badge/frontend/public/wails.png differ diff --git a/v3/examples/badge/frontend/src/main.ts b/v3/examples/badge/frontend/src/main.ts new file mode 100644 index 000000000..222564848 --- /dev/null +++ b/v3/examples/badge/frontend/src/main.ts @@ -0,0 +1,32 @@ +import {Events} from "@wailsio/runtime"; +import {SetBadge, RemoveBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/dock/dockservice"; + +const setButton = document.getElementById('set')! as HTMLButtonElement; +const removeButton = document.getElementById('remove')! as HTMLButtonElement; +const setButtonUsingGo = document.getElementById('set-go')! as HTMLButtonElement; +const removeButtonUsingGo = document.getElementById('remove-go')! as HTMLButtonElement; +const labelElement : HTMLInputElement = document.getElementById('label')! as HTMLInputElement; +const timeElement = document.getElementById('time')! as HTMLDivElement; + +setButton.addEventListener('click', () => { + let label = (labelElement as HTMLInputElement).value + SetBadge(label); +}); + +removeButton.addEventListener('click', () => { + RemoveBadge(); +}); + +setButtonUsingGo.addEventListener('click', () => { + let label = (labelElement as HTMLInputElement).value + void Events.Emit("set:badge", label) +}) + +removeButtonUsingGo.addEventListener('click', () => { + void Events.Emit("remove:badge") +}) + +Events.On('time', (time: {data: any}) => { + timeElement.innerText = time.data; +}); + diff --git a/v3/examples/badge/frontend/src/vite-env.d.ts b/v3/examples/badge/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/examples/badge/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/examples/badge/frontend/tsconfig.json b/v3/examples/badge/frontend/tsconfig.json new file mode 100644 index 000000000..c267ecf24 --- /dev/null +++ b/v3/examples/badge/frontend/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "noImplicitReturns": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/v3/examples/badge/main.go b/v3/examples/badge/main.go new file mode 100644 index 000000000..f97f3924e --- /dev/null +++ b/v3/examples/badge/main.go @@ -0,0 +1,109 @@ +package main + +import ( + "embed" + _ "embed" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/services/dock" +) + +// Wails uses Go's `embed` package to embed the frontend files into the binary. +// Any files in the frontend/dist folder will be embedded into the binary and +// made available to the frontend. +// See https://pkg.go.dev/embed for more information. + +//go:embed all:frontend/dist +var assets embed.FS + +// main function serves as the application's entry point. It initializes the application, creates a window, +// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and +// logs any error that might occur. +func main() { + // Create a new Wails application by providing the necessary options. + // Variables 'Name' and 'Description' are for application metadata. + // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files. + // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances. + // 'Mac' options tailor the application when running an macOS. + + dockService := dock.New() + + app := application.New(application.Options{ + Name: "badge", + Description: "A demo of using raw HTML & CSS", + Services: []application.Service{ + application.NewService(dockService), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a new window with the necessary options. + // 'Title' is the title of the window. + // 'Mac' options tailor the window when running on macOS. + // 'BackgroundColour' is the background colour of the window. + // 'URL' is the URL that will be loaded into the webview. + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + BackgroundColour: application.NewRGB(27, 38, 54), + URL: "/", + }) + + // Store cleanup functions for proper resource management + removeBadgeHandler := app.Event.On("remove:badge", func(event *application.CustomEvent) { + err := dockService.RemoveBadge() + if err != nil { + log.Fatal(err) + } + }) + + setBadgeHandler := app.Event.On("set:badge", func(event *application.CustomEvent) { + text := event.Data.(string) + err := dockService.SetBadge(text) + if err != nil { + log.Fatal(err) + } + }) + + // Note: In a production application, you would call these cleanup functions + // when the handlers are no longer needed, e.g., during shutdown: + // defer removeBadgeHandler() + // defer setBadgeHandler() + _ = removeBadgeHandler // Acknowledge we're storing the cleanup functions + _ = setBadgeHandler + + // Create a goroutine that emits an event containing the current time every second. + // The frontend can listen to this event and update the UI accordingly. + go func() { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + now := time.Now().Format(time.RFC1123) + app.Event.Emit("time", now) + case <-app.Context().Done(): + return + } + } + }() + + // Run the application. This blocks until the application has been exited. + err := app.Run() + + // If an error occurred while running the application, log it and exit. + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/binding/GreetService.go b/v3/examples/binding/GreetService.go new file mode 100644 index 000000000..262fea722 --- /dev/null +++ b/v3/examples/binding/GreetService.go @@ -0,0 +1,39 @@ +package main + +import ( + "strconv" + + "github.com/wailsapp/wails/v3/examples/binding/data" +) + +// GreetService is a service that greets people +type GreetService struct { +} + +// Greet greets a person +func (*GreetService) Greet(name string, counts ...int) string { + times := " " + + for index, count := range counts { + if index > 0 { + times += ", " + } + times += strconv.Itoa(count) + } + + if len(counts) > 0 { + times += " times " + } + + return "Hello" + times + name +} + +// GreetPerson greets a person +func (srv *GreetService) GreetPerson(person data.Person) string { + return srv.Greet(person.Name, person.Counts...) +} + +// GetPerson returns a person with the given name. +func (srv *GreetService) GetPerson(name string) data.Person { + return data.Person{Name: name} +} diff --git a/v3/examples/binding/README.md b/v3/examples/binding/README.md new file mode 100644 index 000000000..37d0f2cc8 --- /dev/null +++ b/v3/examples/binding/README.md @@ -0,0 +1,11 @@ +# Bindings Example + +This example demonstrates how to generate bindings for your application. + +To generate bindings, run `wails3 generate bindings -clean -b -d assets/bindings` in this directory. + +See more options by running `wails3 generate bindings --help`. + +## Notes + - The bindings generator is still a work in progress and is subject to change. + - The generated code uses `wails.CallByID` by default. This is the most robust way to call a function. diff --git a/v3/examples/binding/assets/bindings/github.com/wailsapp/wails/v3/examples/binding/data/index.js b/v3/examples/binding/assets/bindings/github.com/wailsapp/wails/v3/examples/binding/data/index.js new file mode 100644 index 000000000..4c7e0b9c6 --- /dev/null +++ b/v3/examples/binding/assets/bindings/github.com/wailsapp/wails/v3/examples/binding/data/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Person +} from "./models.js"; diff --git a/v3/examples/binding/assets/bindings/github.com/wailsapp/wails/v3/examples/binding/data/models.js b/v3/examples/binding/assets/bindings/github.com/wailsapp/wails/v3/examples/binding/data/models.js new file mode 100644 index 000000000..19a39472c --- /dev/null +++ b/v3/examples/binding/assets/bindings/github.com/wailsapp/wails/v3/examples/binding/data/models.js @@ -0,0 +1,55 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Create as $Create} from "/wails/runtime.js"; + +/** + * Person holds someone's most important attributes + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("name" in $$source)) { + /** + * Name is the person's name + * @member + * @type {string} + */ + this["name"] = ""; + } + if (!("counts" in $$source)) { + /** + * Counts tracks the number of time the person + * has been greeted in various ways + * @member + * @type {number[]} + */ + this["counts"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("counts" in $$parsedSource) { + $$parsedSource["counts"] = $$createField1_0($$parsedSource["counts"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); diff --git a/v3/examples/binding/assets/bindings/github.com/wailsapp/wails/v3/examples/binding/greetservice.js b/v3/examples/binding/assets/bindings/github.com/wailsapp/wails/v3/examples/binding/greetservice.js new file mode 100644 index 000000000..0e5e40d84 --- /dev/null +++ b/v3/examples/binding/assets/bindings/github.com/wailsapp/wails/v3/examples/binding/greetservice.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is a service that greets people + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create} from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as data$0 from "./data/models.js"; + +/** + * GetPerson returns a person with the given name. + * @param {string} name + * @returns {$CancellablePromise} + */ +export function GetPerson(name) { + return $Call.ByID(2952413357, name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +/** + * Greet greets a person + * @param {string} name + * @param {number[]} counts + * @returns {$CancellablePromise} + */ +export function Greet(name, ...counts) { + return $Call.ByID(1411160069, name, counts); +} + +/** + * GreetPerson greets a person + * @param {data$0.Person} person + * @returns {$CancellablePromise} + */ +export function GreetPerson(person) { + return $Call.ByID(4021313248, person); +} + +// Private type creation functions +const $$createType0 = data$0.Person.createFrom; diff --git a/v3/examples/binding/assets/bindings/github.com/wailsapp/wails/v3/examples/binding/index.js b/v3/examples/binding/assets/bindings/github.com/wailsapp/wails/v3/examples/binding/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/examples/binding/assets/bindings/github.com/wailsapp/wails/v3/examples/binding/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/examples/binding/assets/index.html b/v3/examples/binding/assets/index.html new file mode 100644 index 000000000..c86c7b7af --- /dev/null +++ b/v3/examples/binding/assets/index.html @@ -0,0 +1,130 @@ + + + + + Wails Alpha + + + + +
Alpha
+
+

Documentation

+

Feedback

+
+
Please enter your name below 👇
+
+ + + +
+ + + diff --git a/v3/examples/binding/data/person.go b/v3/examples/binding/data/person.go new file mode 100644 index 000000000..114dfdf4c --- /dev/null +++ b/v3/examples/binding/data/person.go @@ -0,0 +1,11 @@ +package data + +// Person holds someone's most important attributes +type Person struct { + // Name is the person's name + Name string `json:"name"` + + // Counts tracks the number of time the person + // has been greeted in various ways + Counts []int `json:"counts"` +} diff --git a/v3/examples/binding/main.go b/v3/examples/binding/main.go new file mode 100644 index 000000000..6e956bac1 --- /dev/null +++ b/v3/examples/binding/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + URL: "/", + DevToolsEnabled: true, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/build/README.md b/v3/examples/build/README.md new file mode 100644 index 000000000..3e2c246a0 --- /dev/null +++ b/v3/examples/build/README.md @@ -0,0 +1,23 @@ +# 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 +wails3 task build +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | \ No newline at end of file diff --git a/v3/examples/build/Taskfile.yml b/v3/examples/build/Taskfile.yml new file mode 100644 index 000000000..f4e5e1f15 --- /dev/null +++ b/v3/examples/build/Taskfile.yml @@ -0,0 +1,110 @@ +version: '3' + +vars: + APP_NAME: "buildtest{{exeExt}}" +tasks: + + build: + summary: Builds the application + cmds: + - go build -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + + package: + summary: Packages a production build of the application into a `.app` or `.exe` bundle + deps: + - task: build + cmds: + - task: package:darwin + - task: package:windows + +# ------------------------------------------------------------------------------ + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails3 generate icons -input appicon.png + + build:production:darwin: + summary: Creates a production build of the application + cmds: + - GOOS=darwin GOARCH={{.GOARCH}} go build -tags production -ldflags="-w -s" -o build/bin/{{.APP_NAME}} + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + GOARCH: '{{.GOARCH}}' + + build:production:linux: + summary: Creates a production build of the application + cmds: + - GOOS=linux GOARCH={{.GOARCH}} go build -tags production -ldflags="-w -s" -o build/bin/{{.APP_NAME}} + env: + GOARCH: '{{.GOARCH}}' + + generate:app_bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{.APP_NAME}}.app/Contents/Resources + - cp build/bin/{{.APP_NAME}} {{.APP_NAME}}.app/Contents/MacOS + - cp build/Info.plist {{.APP_NAME}}.app/Contents + + package:darwin:arm64: + summary: Packages a production build of the application into a `.app` bundle + platforms: + - darwin + deps: + - task: build:production:darwin + vars: + ARCH: arm64 + - generate:icons + cmds: + - task: generate:app_bundle + + package:darwin: + summary: Packages a production build of the application into a `.app` bundle + platforms: + - darwin + deps: + - task: build:production:darwin + - generate:icons + cmds: + - task: generate:app_bundle + + generate:syso: + dir: build + platforms: + - windows + cmds: + - wails3 generate syso -arch {{.GOARCH}} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails_windows.syso + vars: + GOARCH: '{{.GOARCH}}' + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platforms: + - windows/amd64 + deps: + - generate:icons + cmds: + - task: generate:syso + vars: + GOARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{.APP_NAME}} + - powershell Remove-item *.syso + + + package:windows:arm64: + summary: Packages a production build of the application into a `.exe` bundle (ARM64) + platforms: + - windows + cmds: + - task: generate:syso + vars: + GOARCH: arm64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/buildtest.arm64.exe + - powershell Remove-item *.syso + env: + GOARCH: arm64 diff --git a/v3/examples/build/build/Info.dev.plist b/v3/examples/build/build/Info.dev.plist new file mode 100644 index 000000000..d6d28b179 --- /dev/null +++ b/v3/examples/build/build/Info.dev.plist @@ -0,0 +1,35 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My App + CFBundleExecutable + app + CFBundleIdentifier + com.wails.app + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + The ultimate thing + CFBundleShortVersionString + v1 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) Me + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + + + + \ No newline at end of file diff --git a/v3/examples/build/build/Info.plist b/v3/examples/build/build/Info.plist new file mode 100644 index 000000000..7f03f54e9 --- /dev/null +++ b/v3/examples/build/build/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My App + CFBundleExecutable + app + CFBundleIdentifier + com.wails.app + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + The ultimate thing + CFBundleShortVersionString + v1 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) Me + + \ No newline at end of file diff --git a/v3/examples/build/build/appicon.png b/v3/examples/build/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/build/build/appicon.png differ diff --git a/v3/examples/build/build/icon.ico b/v3/examples/build/build/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/build/build/icon.ico differ diff --git a/v3/examples/build/build/icons.icns b/v3/examples/build/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/build/build/icons.icns differ diff --git a/v3/examples/build/build/info.json b/v3/examples/build/build/info.json new file mode 100644 index 000000000..1005eb5cb --- /dev/null +++ b/v3/examples/build/build/info.json @@ -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" + } + } +} \ No newline at end of file diff --git a/v3/examples/build/build/wails.exe.manifest b/v3/examples/build/build/wails.exe.manifest new file mode 100644 index 000000000..fb1ce5bde --- /dev/null +++ b/v3/examples/build/build/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/v3/examples/build/main.go b/v3/examples/build/main.go new file mode 100755 index 000000000..752ad3395 --- /dev/null +++ b/v3/examples/build/main.go @@ -0,0 +1,273 @@ +package main + +import ( + _ "embed" + "fmt" + "log" + "math/rand" + "runtime" + "strconv" + "time" + + "github.com/wailsapp/wails/v3/pkg/events" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + + app := application.New(application.Options{ + Name: "WebviewWindow Demo (debug)", + Description: "A demo of the WebviewWindow API", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + app.Event.OnApplicationEvent(events.Mac.ApplicationDidFinishLaunching, func(*application.ApplicationEvent) { + log.Println("ApplicationDidFinishLaunching") + }) + + currentWindow := func(fn func(window application.Window)) { + current := app.Window.Current() + if current != nil { + fn(current) + } else { + println("Current WebviewWindow is nil") + } + } + + // Create a custom menu + menu := app.NewMenu() + if runtime.GOOS == "darwin" { + 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.Window.New(). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(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.Window.NewWithOptions(application.WebviewWindowOptions{ + X: rand.Intn(1000), + Y: rand.Intn(800), + Frameless: true, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + }, + }).Show() + windowCounter++ + }) + if runtime.GOOS == "darwin" { + myMenu.Add("New WebviewWindow (MacTitleBarHiddenInset)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHiddenInset, + InvisibleTitleBarHeight: 25, + }, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetHTML("

A MacTitleBarHiddenInset WebviewWindow example

"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (MacTitleBarHiddenInsetUnified)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetHTML("

A MacTitleBarHiddenInsetUnified WebviewWindow example

"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (MacTitleBarHidden)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHidden, + InvisibleTitleBarHeight: 25, + }, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetHTML("

A MacTitleBarHidden WebviewWindow example

"). + Show() + windowCounter++ + }) + } + + sizeMenu := menu.AddSubmenu("Size") + sizeMenu.Add("Set Size (800,600)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetSize(800, 600) + }) + }) + + sizeMenu.Add("Set Size (Random)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + 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.Window) { + w.SetMinSize(200, 200) + }) + }) + sizeMenu.Add("Set Max Size (600,600)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetMaximiseButtonState(application.ButtonDisabled) + w.SetMaxSize(600, 600) + }) + }) + sizeMenu.Add("Get Current WebviewWindow Size").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + width, height := w.Size() + app.Dialog.Info().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.Window) { + w.SetMinSize(0, 0) + }) + }) + + sizeMenu.Add("Reset Max Size").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetMaxSize(0, 0) + w.SetMaximiseButtonState(application.ButtonEnabled) + }) + }) + positionMenu := menu.AddSubmenu("Position") + positionMenu.Add("Set Relative Position (0,0)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetRelativePosition(0, 0) + }) + }) + positionMenu.Add("Set Relative Position (Random)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetRelativePosition(rand.Intn(1000), rand.Intn(800)) + }) + }) + + positionMenu.Add("Get Relative Position").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + x, y := w.RelativePosition() + app.Dialog.Info().SetTitle("Current WebviewWindow Relative Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() + }) + }) + + positionMenu.Add("Center").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.Center() + }) + }) + stateMenu := menu.AddSubmenu("State") + stateMenu.Add("Minimise (for 2 secs)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.Minimise() + time.Sleep(2 * time.Second) + w.Restore() + }) + }) + stateMenu.Add("Maximise").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.Maximise() + }) + }) + stateMenu.Add("Fullscreen").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.Fullscreen() + }) + }) + stateMenu.Add("UnFullscreen").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.UnFullscreen() + }) + }) + stateMenu.Add("Restore").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.Restore() + }) + }) + stateMenu.Add("Hide (for 2 seconds)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.Hide() + time.Sleep(2 * time.Second) + w.Show() + }) + }) + stateMenu.Add("Always on Top").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetAlwaysOnTop(true) + }) + }) + stateMenu.Add("Not always on Top").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetAlwaysOnTop(false) + }) + }) + stateMenu.Add("Google.com").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetURL("https://google.com") + }) + }) + stateMenu.Add("wails.io").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetURL("https://wails.io") + }) + }) + stateMenu.Add("Get Primary Screen").OnClick(func(ctx *application.Context) { + screen := app.Screen.GetPrimary() + msg := fmt.Sprintf("Screen: %+v", screen) + app.Dialog.Info().SetTitle("Primary Screen").SetMessage(msg).Show() + }) + stateMenu.Add("Get Screens").OnClick(func(ctx *application.Context) { + screens := app.Screen.GetAll() + for _, screen := range screens { + msg := fmt.Sprintf("Screen: %+v", screen) + app.Dialog.Info().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.Window) { + screen, err := w.GetScreen() + if err != nil { + app.Dialog.Error().SetTitle("Error").SetMessage(err.Error()).Show() + return + } + msg := fmt.Sprintf("Screen: %+v", screen) + app.Dialog.Info().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + }) + }) + app.Window.New() + + app.Menu.Set(menu) + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/cancel-async/README.md b/v3/examples/cancel-async/README.md new file mode 100644 index 000000000..feec0e5d6 --- /dev/null +++ b/v3/examples/cancel-async/README.md @@ -0,0 +1,5 @@ +# Binding Call Cancelling Example + +This example demonstrates how to cancel binding calls in async/await style. + +To regenerate bindings, run `wails3 generate bindings -clean -b -d assets/bindings` in this directory. diff --git a/v3/examples/cancel-async/assets/bindings/github.com/wailsapp/wails/v3/examples/cancel-async/index.js b/v3/examples/cancel-async/assets/bindings/github.com/wailsapp/wails/v3/examples/cancel-async/index.js new file mode 100644 index 000000000..d2bf43c94 --- /dev/null +++ b/v3/examples/cancel-async/assets/bindings/github.com/wailsapp/wails/v3/examples/cancel-async/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; diff --git a/v3/examples/cancel-async/assets/bindings/github.com/wailsapp/wails/v3/examples/cancel-async/service.js b/v3/examples/cancel-async/assets/bindings/github.com/wailsapp/wails/v3/examples/cancel-async/service.js new file mode 100644 index 000000000..6aae91e1d --- /dev/null +++ b/v3/examples/cancel-async/assets/bindings/github.com/wailsapp/wails/v3/examples/cancel-async/service.js @@ -0,0 +1,16 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create} from "/wails/runtime.js"; + +/** + * A long running operation of specified duration. + * @param {number} milliseconds + * @returns {$CancellablePromise} + */ +export function LongRunning(milliseconds) { + return $Call.ByID(298191698, milliseconds); +} diff --git a/v3/examples/cancel-async/assets/index.html b/v3/examples/cancel-async/assets/index.html new file mode 100644 index 000000000..ff0609928 --- /dev/null +++ b/v3/examples/cancel-async/assets/index.html @@ -0,0 +1,134 @@ + + + + + Wails Alpha + + + + +
Alpha
+
+

Documentation

+

Feedback

+
+
Please enter a duration in milliseconds below 👇
+
+ + + + +
+ + + diff --git a/v3/examples/cancel-async/main.go b/v3/examples/cancel-async/main.go new file mode 100644 index 000000000..9fb52481b --- /dev/null +++ b/v3/examples/cancel-async/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&Service{}), + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + URL: "/", + DevToolsEnabled: true, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/cancel-async/service.go b/v3/examples/cancel-async/service.go new file mode 100644 index 000000000..b2cc72e9b --- /dev/null +++ b/v3/examples/cancel-async/service.go @@ -0,0 +1,19 @@ +package main + +import ( + "context" + "time" +) + +type Service struct { +} + +// A long running operation of specified duration. +func (*Service) LongRunning(ctx context.Context, milliseconds int) error { + select { + case <-time.After(time.Duration(milliseconds) * time.Millisecond): + return nil + case <-ctx.Done(): + return ctx.Err() + } +} diff --git a/v3/examples/cancel-chaining/README.md b/v3/examples/cancel-chaining/README.md new file mode 100644 index 000000000..10c5855ea --- /dev/null +++ b/v3/examples/cancel-chaining/README.md @@ -0,0 +1,5 @@ +# Binding Call Cancelling Example + +This example demonstrates how to cancel binding calls in promise chaining style. + +To regenerate bindings, run `wails3 generate bindings -clean -b -d assets/bindings` in this directory. diff --git a/v3/examples/cancel-chaining/assets/bindings/github.com/wailsapp/wails/v3/examples/cancel-chaining/index.js b/v3/examples/cancel-chaining/assets/bindings/github.com/wailsapp/wails/v3/examples/cancel-chaining/index.js new file mode 100644 index 000000000..d2bf43c94 --- /dev/null +++ b/v3/examples/cancel-chaining/assets/bindings/github.com/wailsapp/wails/v3/examples/cancel-chaining/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; diff --git a/v3/examples/cancel-chaining/assets/bindings/github.com/wailsapp/wails/v3/examples/cancel-chaining/service.js b/v3/examples/cancel-chaining/assets/bindings/github.com/wailsapp/wails/v3/examples/cancel-chaining/service.js new file mode 100644 index 000000000..6aae91e1d --- /dev/null +++ b/v3/examples/cancel-chaining/assets/bindings/github.com/wailsapp/wails/v3/examples/cancel-chaining/service.js @@ -0,0 +1,16 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create} from "/wails/runtime.js"; + +/** + * A long running operation of specified duration. + * @param {number} milliseconds + * @returns {$CancellablePromise} + */ +export function LongRunning(milliseconds) { + return $Call.ByID(298191698, milliseconds); +} diff --git a/v3/examples/cancel-chaining/assets/index.html b/v3/examples/cancel-chaining/assets/index.html new file mode 100644 index 000000000..71221c28d --- /dev/null +++ b/v3/examples/cancel-chaining/assets/index.html @@ -0,0 +1,136 @@ + + + + + Wails Alpha + + + + +
Alpha
+
+

Documentation

+

Feedback

+
+
Please enter a duration in milliseconds below 👇
+
+ + + + +
+ + + diff --git a/v3/examples/cancel-chaining/main.go b/v3/examples/cancel-chaining/main.go new file mode 100644 index 000000000..9fb52481b --- /dev/null +++ b/v3/examples/cancel-chaining/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&Service{}), + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + URL: "/", + DevToolsEnabled: true, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/cancel-chaining/service.go b/v3/examples/cancel-chaining/service.go new file mode 100644 index 000000000..b2cc72e9b --- /dev/null +++ b/v3/examples/cancel-chaining/service.go @@ -0,0 +1,19 @@ +package main + +import ( + "context" + "time" +) + +type Service struct { +} + +// A long running operation of specified duration. +func (*Service) LongRunning(ctx context.Context, milliseconds int) error { + select { + case <-time.After(time.Duration(milliseconds) * time.Millisecond): + return nil + case <-ctx.Done(): + return ctx.Err() + } +} diff --git a/v3/examples/clipboard/README.md b/v3/examples/clipboard/README.md new file mode 100644 index 000000000..b3128b531 --- /dev/null +++ b/v3/examples/clipboard/README.md @@ -0,0 +1,11 @@ +# clipboard + +This example demonstrates how to use the clipboard API. + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | \ No newline at end of file diff --git a/v3/examples/clipboard/main.go b/v3/examples/clipboard/main.go new file mode 100644 index 000000000..0c97693aa --- /dev/null +++ b/v3/examples/clipboard/main.go @@ -0,0 +1,77 @@ +package main + +import ( + _ "embed" + "log" + "runtime" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + + app := application.New(application.Options{ + Name: "Clipboard Demo", + Description: "A demo of the clipboard API", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a custom menu + menu := app.NewMenu() + if runtime.GOOS == "darwin" { + 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.Dialog.Info().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.Dialog.Info().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.Dialog.Info().SetMessage("Failed to set clipboard text").Show() + } + }) + getClipboardMenu := menu.AddSubmenu("Get Clipboard") + getClipboardMenu.Add("Get Text").OnClick(func(ctx *application.Context) { + result, ok := app.Clipboard.Text() + if !ok { + app.Dialog.Info().SetMessage("Failed to get clipboard text").Show() + } else { + app.Dialog.Info().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.Dialog.Info().SetMessage("Clipboard text cleared").Show() + } else { + app.Dialog.Info().SetMessage("Clipboard text not cleared").Show() + } + }) + + app.Menu.Set(menu) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/contextmenus/README.md b/v3/examples/contextmenus/README.md new file mode 100644 index 000000000..ba6d498e7 --- /dev/null +++ b/v3/examples/contextmenus/README.md @@ -0,0 +1,30 @@ +# contextmenus + +This example shows how to create a context menu for your application. +It demonstrates window level and global context menus. + +A simple menu is registered with the window and the application with the id "test". +In our frontend html, we then use the `--custom-contextmenu` style to attach the menu to an element. +We also use the `--custom-contextmenu-data` style to pass data to the menu callback which can be read in Go. +This is really useful when using components to distinguish between different elements. + +```go + +```html + +
+

1

+
+
+

2

+
+``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | + diff --git a/v3/examples/contextmenus/assets/index.html b/v3/examples/contextmenus/assets/index.html new file mode 100644 index 000000000..23224084d --- /dev/null +++ b/v3/examples/contextmenus/assets/index.html @@ -0,0 +1,82 @@ + + + + + Title + + + + + + +

Context Menu Demo

+

+ You need to run this example in production for it to work : +

go run -tags production .
+ +
+
+

1

+
+
+

2

+
+
+

Default Context Menu shown here

+ +
+
+

Default auto (smart) Context Menu here

+ +
+
+

Context menu shown here only if you select text

+

Selecting text here and right-clicking the box below or its border shouldn't show the context menu

+
+
+
+
+
+
+ + + + +

content editable

+
+
+
+

Default Context Menu hidden here

+

Context Menu hidden here even if text is selected

+ +
+
+

Nested section reverted to auto (smart) default Context Menu

+

Context menu shown here only if you select text

+
+
+
+ + diff --git a/v3/examples/contextmenus/main.go b/v3/examples/contextmenus/main.go new file mode 100644 index 000000000..70cdf5c7e --- /dev/null +++ b/v3/examples/contextmenus/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Context Menu Demo", + Description: "A demo of the Context Menu API", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Context Menu Demo", + Width: 1024, + Height: 1024, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + contextMenu := app.ContextMenu.New() + clickMe := contextMenu.Add("Click to set Menuitem label to Context Data") + contextDataMenuItem := contextMenu.Add("Current context data: No Context Data") + clickMe.OnClick(func(data *application.Context) { + app.Logger.Info("Context menu", "context data", data.ContextMenuData()) + contextDataMenuItem.SetLabel("Current context data: " + data.ContextMenuData()) + contextMenu.Update() + }) + + // Register the context menu + app.ContextMenu.Add("test", contextMenu) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/custom-protocol-example/.gitignore b/v3/examples/custom-protocol-example/.gitignore new file mode 100644 index 000000000..ba8194ab6 --- /dev/null +++ b/v3/examples/custom-protocol-example/.gitignore @@ -0,0 +1,6 @@ +.task +bin +frontend/dist +frontend/node_modules +build/linux/appimage/build +build/windows/nsis/MicrosoftEdgeWebview2Setup.exe \ No newline at end of file diff --git a/v3/examples/custom-protocol-example/README.md b/v3/examples/custom-protocol-example/README.md new file mode 100644 index 000000000..ad12c3f40 --- /dev/null +++ b/v3/examples/custom-protocol-example/README.md @@ -0,0 +1,59 @@ +# Welcome to Your New Wails3 Project! + +Congratulations on generating your Wails3 application! This README will guide you through the next steps to get your project up and running. + +## Getting Started + +1. Navigate to your project directory in the terminal. + +2. To run your application in development mode, use the following command: + + ``` + wails3 dev + ``` + + This will start your application and enable hot-reloading for both frontend and backend changes. + +3. To build your application for production, use: + + ``` + wails3 build + ``` + + This will create a production-ready executable in the `build` directory. + +## Exploring Wails3 Features + +Now that you have your project set up, it's time to explore the features that Wails3 offers: + +1. **Check out the examples**: The best way to learn is by example. Visit the `examples` directory in the `v3/examples` directory to see various sample applications. + +2. **Run an example**: To run any of the examples, navigate to the example's directory and use: + + ``` + go run . + ``` + + Note: Some examples may be under development during the alpha phase. + +3. **Explore the documentation**: Visit the [Wails3 documentation](https://v3.wails.io/) for in-depth guides and API references. + +4. **Join the community**: Have questions or want to share your progress? Join the [Wails Discord](https://discord.gg/JDdSxwjhGf) or visit the [Wails discussions on GitHub](https://github.com/wailsapp/wails/discussions). + +## Project Structure + +Take a moment to familiarize yourself with your project structure: + +- `frontend/`: Contains your frontend code (HTML, CSS, JavaScript/TypeScript) +- `main.go`: The entry point of your Go backend +- `app.go`: Define your application structure and methods here +- `wails.json`: Configuration file for your Wails project + +## Next Steps + +1. Modify the frontend in the `frontend/` directory to create your desired UI. +2. Add backend functionality in `main.go`. +3. Use `wails3 dev` to see your changes in real-time. +4. When ready, build your application with `wails3 build`. + +Happy coding with Wails3! If you encounter any issues or have questions, don't hesitate to consult the documentation or reach out to the Wails community. diff --git a/v3/examples/custom-protocol-example/Taskfile.yml b/v3/examples/custom-protocol-example/Taskfile.yml new file mode 100644 index 000000000..572db59ac --- /dev/null +++ b/v3/examples/custom-protocol-example/Taskfile.yml @@ -0,0 +1,33 @@ +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + +vars: + APP_NAME: "custom-protocol-example" + BIN_DIR: "bin" + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{OS}}:package" + + run: + summary: Runs the application + cmds: + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml + diff --git a/v3/examples/custom-protocol-example/build/Taskfile.yml b/v3/examples/custom-protocol-example/build/Taskfile.yml new file mode 100644 index 000000000..ba497b5b6 --- /dev/null +++ b/v3/examples/custom-protocol-example/build/Taskfile.yml @@ -0,0 +1,85 @@ +version: '3' + +tasks: + go:mod:tidy: + summary: Runs `go mod tidy` + internal: true + cmds: + - go mod tidy + + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + cmds: + - echo "Skipping frontend dependencies installation" + + build:frontend: + label: build:frontend (PRODUCTION={{.PRODUCTION}}) + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/**/* + deps: + - task: install:frontend:deps + - task: generate:bindings + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + cmds: + - npm run {{.BUILD_COMMAND}} -q + env: + PRODUCTION: '{{.PRODUCTION | default "false"}}' + vars: + BUILD_COMMAND: '{{if eq .PRODUCTION "true"}}build{{else}}build{{end}}' + + + generate:bindings: + label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}}) + summary: Generates bindings for the frontend + deps: + - task: go:mod:tidy + sources: + - "**/*.[jt]s" + - exclude: frontend/**/* + - frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output + - "**/*.go" + - go.mod + - go.sum + generates: + - frontend/bindings/**/* + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + sources: + - "appicon.png" + generates: + - "darwin/icons.icns" + - "windows/icon.ico" + cmds: + - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico + + dev:frontend: + summary: Runs the frontend in development mode + dir: frontend + deps: + - task: install:frontend:deps + cmds: + - echo "Skipping frontend development mode" + + update:build-assets: + summary: Updates the build assets + dir: build + cmds: + - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir . diff --git a/v3/examples/custom-protocol-example/build/appicon.png b/v3/examples/custom-protocol-example/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/custom-protocol-example/build/appicon.png differ diff --git a/v3/examples/custom-protocol-example/build/config.yml b/v3/examples/custom-protocol-example/build/config.yml new file mode 100644 index 000000000..8f2cb0348 --- /dev/null +++ b/v3/examples/custom-protocol-example/build/config.yml @@ -0,0 +1,67 @@ +# This file contains the configuration for this project. +# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets. +# Note that this will overwrite any changes you have made to the assets. +version: '3' + +# This information is used to generate the build assets. +info: + companyName: "My Company" # The name of the company + productName: "My Product" # The name of the application + productIdentifier: "com.mycompany.myproduct" # The unique product identifier + description: "A program that does X" # The application description + copyright: "(c) 2025, My Company" # Copyright text + comments: "Some Product Comments" # Comments + version: "0.0.1" # The application version + +# Dev mode configuration +dev_mode: + root_path: . + log_level: warn + debounce: 1000 + ignore: + dir: + - .git + - node_modules + - frontend + - bin + file: + - .DS_Store + - .gitignore + - .gitkeep + watched_extension: + - "*.go" + git_ignore: true + executes: + - cmd: wails3 task common:install:frontend:deps + type: once + - cmd: wails3 task common:dev:frontend + type: background + - cmd: go mod tidy + type: blocking + - cmd: wails3 task build + type: blocking + - cmd: wails3 task run + type: primary + +# File Associations +# More information at: https://v3.wails.io/noit/done/yet +fileAssociations: +# - ext: wails +# name: Wails +# description: Wails Application File +# iconName: wailsFileIcon +# role: Editor +# - ext: jpg +# name: JPEG +# description: Image File +# iconName: jpegFileIcon +# role: Editor +# mimeType: image/jpeg # (optional) + +protocols: + - scheme: wailsexample + description: Wails Example Application Custom Protocol + +# Other data +other: + - name: My Other Data \ No newline at end of file diff --git a/v3/examples/custom-protocol-example/build/darwin/Info.dev.plist b/v3/examples/custom-protocol-example/build/darwin/Info.dev.plist new file mode 100644 index 000000000..a46aa82d9 --- /dev/null +++ b/v3/examples/custom-protocol-example/build/darwin/Info.dev.plist @@ -0,0 +1,43 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + custom-protocol-example + CFBundleIdentifier + com.mycompany.myproduct + CFBundleVersion + 0.0.1 + CFBundleGetInfoString + Some Product Comments + CFBundleShortVersionString + 0.0.1 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2025, My Company + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + CFBundleURLTypes + + + CFBundleURLName + wails.com.wailsexample + CFBundleURLSchemes + + wailsexample + + + + + \ No newline at end of file diff --git a/v3/examples/custom-protocol-example/build/darwin/Info.plist b/v3/examples/custom-protocol-example/build/darwin/Info.plist new file mode 100644 index 000000000..d88fd1030 --- /dev/null +++ b/v3/examples/custom-protocol-example/build/darwin/Info.plist @@ -0,0 +1,38 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + custom-protocol-example + CFBundleIdentifier + com.mycompany.myproduct + CFBundleVersion + 0.0.1 + CFBundleGetInfoString + Some Product Comments + CFBundleShortVersionString + 0.0.1 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2025, My Company + CFBundleURLTypes + + + CFBundleURLName + wails.com.wailsexample + CFBundleURLSchemes + + wailsexample + + + + + \ No newline at end of file diff --git a/v3/examples/custom-protocol-example/build/darwin/Taskfile.yml b/v3/examples/custom-protocol-example/build/darwin/Taskfile.yml new file mode 100644 index 000000000..e456dbad5 --- /dev/null +++ b/v3/examples/custom-protocol-example/build/darwin/Taskfile.yml @@ -0,0 +1,76 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Creates a production build of the application + deps: + - task: common:go:mod:tidy + - task: common:generate:icons + - task: common:build:frontend + cmds: + - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + env: + GOOS: darwin + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + CGO_CFLAGS: "-mmacosx-version-min=10.15" + CGO_LDFLAGS: "-mmacosx-version-min=10.15" + MACOSX_DEPLOYMENT_TARGET: "10.15" + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + build:universal: + summary: Builds darwin universal binary (arm64 + amd64) + deps: + - task: build + vars: + ARCH: amd64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" + - task: build + vars: + ARCH: arm64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + cmds: + - lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + - rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + + package: + summary: Packages a production build of the application into a `.app` bundle + deps: + - task: build + vars: + PRODUCTION: "false" + cmds: + - task: create:app:bundle + + package:universal: + summary: Packages darwin universal binary (arm64 + amd64) + deps: + - task: build:universal + cmds: + - task: create:app:bundle + + + create:app:bundle: + summary: Creates an `.app` bundle + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS + - cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app + + run: + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS + - cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app + - '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}' diff --git a/v3/examples/custom-protocol-example/build/darwin/icons.icns b/v3/examples/custom-protocol-example/build/darwin/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/custom-protocol-example/build/darwin/icons.icns differ diff --git a/v3/examples/custom-protocol-example/build/linux/Taskfile.yml b/v3/examples/custom-protocol-example/build/linux/Taskfile.yml new file mode 100644 index 000000000..a6115574a --- /dev/null +++ b/v3/examples/custom-protocol-example/build/linux/Taskfile.yml @@ -0,0 +1,113 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Linux + deps: + - task: common:go:mod:tidy + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: linux + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application for Linux + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:appimage + - task: create:deb + - task: create:rpm + - task: create:aur + + create:appimage: + summary: Creates an AppImage + dir: build/linux/appimage + deps: + - task: build + vars: + PRODUCTION: "true" + - task: generate:dotdesktop + cmds: + - cp {{.APP_BINARY}} {{.APP_NAME}} + - cp ../../appicon.png appicon.png + - wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build + vars: + APP_NAME: '{{.APP_NAME}}' + APP_BINARY: '../../../bin/{{.APP_NAME}}' + ICON: '../../appicon.png' + DESKTOP_FILE: '../{{.APP_NAME}}.desktop' + OUTPUT_DIR: '../../../bin' + + create:deb: + summary: Creates a deb package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:deb + + create:rpm: + summary: Creates a rpm package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:rpm + + create:aur: + summary: Creates a arch linux packager package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:aur + + generate:deb: + summary: Creates a deb package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:rpm: + summary: Creates a rpm package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:aur: + summary: Creates a arch linux packager package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:dotdesktop: + summary: Generates a `.desktop` file + dir: build + cmds: + - mkdir -p {{.ROOT_DIR}}/build/linux/appimage + - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}" + vars: + APP_NAME: '{{.APP_NAME}}' + EXEC: '{{.APP_NAME}}' + ICON: 'appicon' + CATEGORIES: 'Development;' + OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}' diff --git a/v3/examples/custom-protocol-example/build/linux/appimage/build.sh b/v3/examples/custom-protocol-example/build/linux/appimage/build.sh new file mode 100644 index 000000000..85901c34e --- /dev/null +++ b/v3/examples/custom-protocol-example/build/linux/appimage/build.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Copyright (c) 2018-Present Lea Anthony +# SPDX-License-Identifier: MIT + +# Fail script on any error +set -euxo pipefail + +# Define variables +APP_DIR="${APP_NAME}.AppDir" + +# Create AppDir structure +mkdir -p "${APP_DIR}/usr/bin" +cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/" +cp "${ICON_PATH}" "${APP_DIR}/" +cp "${DESKTOP_FILE}" "${APP_DIR}/" + +if [[ $(uname -m) == *x86_64* ]]; then + # Download linuxdeploy and make it executable + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + + # Run linuxdeploy to bundle the application + ./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage +else + # Download linuxdeploy and make it executable (arm64) + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage + chmod +x linuxdeploy-aarch64.AppImage + + # Run linuxdeploy to bundle the application (arm64) + ./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage +fi + +# Rename the generated AppImage +mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage" + diff --git a/v3/examples/custom-protocol-example/build/linux/nfpm/nfpm.yaml b/v3/examples/custom-protocol-example/build/linux/nfpm/nfpm.yaml new file mode 100644 index 000000000..7356f2992 --- /dev/null +++ b/v3/examples/custom-protocol-example/build/linux/nfpm/nfpm.yaml @@ -0,0 +1,50 @@ +# Feel free to remove those if you don't want/need to use them. +# Make sure to check the documentation at https://nfpm.goreleaser.com +# +# The lines below are called `modelines`. See `:help modeline` + +name: "custom-protocol-example" +arch: ${GOARCH} +platform: "linux" +version: "0.0.1" +section: "default" +priority: "extra" +maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> +description: "A program that does X" +vendor: "My Company" +homepage: "https://wails.io" +license: "MIT" +release: "1" + +contents: + - src: "./bin/custom-protocol-example" + dst: "/usr/local/bin/custom-protocol-example" + - src: "./build/appicon.png" + dst: "/usr/share/icons/hicolor/128x128/apps/custom-protocol-example.png" + - src: "./build/linux/custom-protocol-example.desktop" + dst: "/usr/share/applications/custom-protocol-example.desktop" + +depends: + - gtk3 + - libwebkit2gtk + +# replaces: +# - foobar +# provides: +# - bar +# depends: +# - gtk3 +# - libwebkit2gtk +# recommends: +# - whatever +# suggests: +# - something-else +# conflicts: +# - not-foo +# - not-bar +# changelog: "changelog.yaml" +# scripts: +# preinstall: ./build/linux/nfpm/scripts/preinstall.sh +# postinstall: ./build/linux/nfpm/scripts/postinstall.sh +# preremove: ./build/linux/nfpm/scripts/preremove.sh +# postremove: ./build/linux/nfpm/scripts/postremove.sh diff --git a/v3/examples/custom-protocol-example/build/linux/nfpm/scripts/postinstall.sh b/v3/examples/custom-protocol-example/build/linux/nfpm/scripts/postinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/custom-protocol-example/build/linux/nfpm/scripts/postinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/custom-protocol-example/build/linux/nfpm/scripts/postremove.sh b/v3/examples/custom-protocol-example/build/linux/nfpm/scripts/postremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/custom-protocol-example/build/linux/nfpm/scripts/postremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/custom-protocol-example/build/linux/nfpm/scripts/preinstall.sh b/v3/examples/custom-protocol-example/build/linux/nfpm/scripts/preinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/custom-protocol-example/build/linux/nfpm/scripts/preinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/custom-protocol-example/build/linux/nfpm/scripts/preremove.sh b/v3/examples/custom-protocol-example/build/linux/nfpm/scripts/preremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/custom-protocol-example/build/linux/nfpm/scripts/preremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/custom-protocol-example/build/windows/Taskfile.yml b/v3/examples/custom-protocol-example/build/windows/Taskfile.yml new file mode 100644 index 000000000..805c1aa7f --- /dev/null +++ b/v3/examples/custom-protocol-example/build/windows/Taskfile.yml @@ -0,0 +1,57 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Windows + deps: + - task: common:go:mod:tidy + - task: common:generate:icons + cmds: + - task: generate:syso + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe + - cmd: powershell Remove-item *.syso + platforms: [windows] + - cmd: rm -f *.syso + platforms: [linux, darwin] + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: windows + CGO_ENABLED: 0 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application into a `.exe` bundle + cmds: + - task: create:nsis:installer + + generate:syso: + summary: Generates Windows `.syso` file + dir: build + cmds: + - wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso + vars: + ARCH: '{{.ARCH | default ARCH}}' + + create:nsis:installer: + summary: Creates an NSIS installer + dir: build/windows/nsis + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + # Create the Microsoft WebView2 bootstrapper if it doesn't exist + - wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis" + - makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi + vars: + ARCH: '{{.ARCH | default ARCH}}' + ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}.exe' diff --git a/v3/examples/custom-protocol-example/build/windows/icon.ico b/v3/examples/custom-protocol-example/build/windows/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/custom-protocol-example/build/windows/icon.ico differ diff --git a/v3/examples/custom-protocol-example/build/windows/info.json b/v3/examples/custom-protocol-example/build/windows/info.json new file mode 100644 index 000000000..71dfd6d99 --- /dev/null +++ b/v3/examples/custom-protocol-example/build/windows/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "0.0.1" + }, + "info": { + "0000": { + "ProductVersion": "0.0.1", + "CompanyName": "My Company", + "FileDescription": "A program that does X", + "LegalCopyright": "(c) 2025, My Company", + "ProductName": "My Product", + "Comments": "Some Product Comments" + } + } +} \ No newline at end of file diff --git a/v3/examples/custom-protocol-example/build/windows/nsis/project.nsi b/v3/examples/custom-protocol-example/build/windows/nsis/project.nsi new file mode 100644 index 000000000..5ff2eb085 --- /dev/null +++ b/v3/examples/custom-protocol-example/build/windows/nsis/project.nsi @@ -0,0 +1,112 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the wails_tools.nsh file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "my-project" # Default "custom-protocol-example" +## !define INFO_COMPANYNAME "My Company" # Default "My Company" +## !define INFO_PRODUCTNAME "My Product Name" # Default "My Product" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.1.0" +## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "© now, My Company" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/v3/examples/custom-protocol-example/build/windows/nsis/wails_tools.nsh b/v3/examples/custom-protocol-example/build/windows/nsis/wails_tools.nsh new file mode 100644 index 000000000..607fc4e85 --- /dev/null +++ b/v3/examples/custom-protocol-example/build/windows/nsis/wails_tools.nsh @@ -0,0 +1,212 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "custom-protocol-example" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "My Company" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "My Product" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "0.0.1" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "(c) 2025, My Company" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + +!macroend \ No newline at end of file diff --git a/v3/examples/custom-protocol-example/build/windows/wails.exe.manifest b/v3/examples/custom-protocol-example/build/windows/wails.exe.manifest new file mode 100644 index 000000000..21af92674 --- /dev/null +++ b/v3/examples/custom-protocol-example/build/windows/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/v3/examples/custom-protocol-example/frontend/.gitignore b/v3/examples/custom-protocol-example/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/examples/custom-protocol-example/frontend/bindings/github.com/wailsapp/wails/v3/examples/custom-protocol-example/greetservice.js b/v3/examples/custom-protocol-example/frontend/bindings/github.com/wailsapp/wails/v3/examples/custom-protocol-example/greetservice.js new file mode 100644 index 000000000..0b93e6d75 --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/bindings/github.com/wailsapp/wails/v3/examples/custom-protocol-example/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/examples/custom-protocol-example/frontend/bindings/github.com/wailsapp/wails/v3/examples/custom-protocol-example/index.js b/v3/examples/custom-protocol-example/frontend/bindings/github.com/wailsapp/wails/v3/examples/custom-protocol-example/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/bindings/github.com/wailsapp/wails/v3/examples/custom-protocol-example/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/examples/custom-protocol-example/frontend/dist/assets/index-BS9x21Y4.js b/v3/examples/custom-protocol-example/frontend/dist/assets/index-BS9x21Y4.js new file mode 100644 index 000000000..4f1023c59 --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/dist/assets/index-BS9x21Y4.js @@ -0,0 +1,24 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))o(i);new MutationObserver(i=>{for(const r of i)if(r.type==="childList")for(const s of r.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&o(s)}).observe(document,{childList:!0,subtree:!0});function n(i){const r={};return i.integrity&&(r.integrity=i.integrity),i.referrerPolicy&&(r.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?r.credentials="include":i.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function o(i){if(i.ep)return;i.ep=!0;const r=n(i);fetch(i.href,r)}})();const S="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";function R(e=21){let t="",n=e|0;for(;n--;)t+=S[Math.random()*64|0];return t}const U=window.location.origin+"/wails/runtime",_=Object.freeze({Call:0,Clipboard:1,Application:2,Events:3,ContextMenu:4,Dialog:5,Window:6,Screens:7,System:8,Browser:9,CancelCall:10});let T=R();function k(e,t=""){return function(n,o=null){return I(e,n,t,o)}}async function I(e,t,n,o){var i,r;let s=new URL(U);s.searchParams.append("object",e.toString()),s.searchParams.append("method",t.toString()),o&&s.searchParams.append("args",JSON.stringify(o));let c={"x-wails-client-id":T};n&&(c["x-wails-window-name"]=n);let a=await fetch(s,{headers:c});if(!a.ok)throw new Error(await a.text());return((r=(i=a.headers.get("Content-Type"))===null||i===void 0?void 0:i.indexOf("application/json"))!==null&&r!==void 0?r:-1)!==-1?a.json():a.text()}k(_.System);const z=function(){var e,t,n,o,i;try{if(!((t=(e=window.chrome)===null||e===void 0?void 0:e.webview)===null||t===void 0)&&t.postMessage)return window.chrome.webview.postMessage.bind(window.chrome.webview);if(!((i=(o=(n=window.webkit)===null||n===void 0?void 0:n.messageHandlers)===null||o===void 0?void 0:o.external)===null||i===void 0)&&i.postMessage)return window.webkit.messageHandlers.external.postMessage.bind(window.webkit.messageHandlers.external)}catch{}return console.warn(` +%c⚠️ Browser Environment Detected %c + +%cOnly UI previews are available in the browser. For full functionality, please run the application in desktop mode. +More information at: https://v3.wails.io/learn/build/#using-a-browser-for-development +`,"background: #ffffff; color: #000000; font-weight: bold; padding: 4px 8px; border-radius: 4px; border: 2px solid #000000;","background: transparent;","color: #ffffff; font-style: italic; font-weight: bold;"),null}();function h(e){z==null||z(e)}function H(){return window._wails.environment.OS==="windows"}function W(){return!!window._wails.environment.Debug}function B(){return new MouseEvent("mousedown").buttons===0}function P(e){var t;return e.target instanceof HTMLElement?e.target:!(e.target instanceof HTMLElement)&&e.target instanceof Node&&(t=e.target.parentElement)!==null&&t!==void 0?t:document.body}document.addEventListener("DOMContentLoaded",()=>{});window.addEventListener("contextmenu",X);const N=k(_.ContextMenu),j=0;function F(e,t,n,o){N(j,{id:e,x:t,y:n,data:o})}function X(e){const t=P(e),n=window.getComputedStyle(t).getPropertyValue("--custom-contextmenu").trim();if(n){e.preventDefault();const o=window.getComputedStyle(t).getPropertyValue("--custom-contextmenu-data");F(n,e.clientX,e.clientY,o)}else Y(e,t)}function Y(e,t){if(W())return;switch(window.getComputedStyle(t).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":e.preventDefault();return}if(t.isContentEditable)return;const n=window.getSelection(),o=n&&n.toString().length>0;if(o)for(let i=0;i{M=e,M||(w=p=!1,l())};window.addEventListener("mousedown",D,{capture:!0});window.addEventListener("mousemove",D,{capture:!0});window.addEventListener("mouseup",D,{capture:!0});for(const e of["click","contextmenu","dblclick"])window.addEventListener(e,A,{capture:!0});function A(e){(g||p)&&(e.stopImmediatePropagation(),e.stopPropagation(),e.preventDefault())}const C=0,V=1,L=2;function D(e){let t,n=e.buttons;switch(e.type){case"mousedown":t=C,E||(n=f|1<n!==e),t.length===0?d.delete(e.eventName):d.set(e.eventName,t))}window._wails=window._wails||{};window._wails.dispatchWailsEvent=ee;k(_.Events);class ${constructor(t,n=null){this.name=t,this.data=n}}function ee(e){let t=d.get(e.name);if(!t)return;let n=new $(e.name,e.data);"sender"in e&&(n.sender=e.sender),t=t.filter(o=>!o.dispatch(n)),t.length===0?d.delete(e.name):d.set(e.name,t)}function te(e,t,n){let o=d.get(e)||[];const i=new Q(e,t,n);return o.push(i),d.set(e,o),()=>Z(i)}function ne(e,t){return te(e,t,-1)}window._wails=window._wails||{};window._wails.invoke=h;h("wails:runtime:ready");document.addEventListener("DOMContentLoaded",()=>{const e=document.getElementById("app");e?e.innerHTML=` +
+

Custom Protocol / Deep Link Test

+

+ This page demonstrates handling custom URL schemes (deep links). +

+

+ Example Link: + Try opening this URL (e.g., by pasting it into your browser's address bar or using open your-app-scheme://... in terminal): +
+ wailsexample://test/path?value=123&message=hello +

+ +
+ Received URL: +
Waiting for application to be opened via a custom URL...
+
+
+ `:console.error('Element with ID "app" not found.')});ne("frontend:ShowURL",e=>{console.log("frontend:ShowURL event received, data:",e),displayUrl(e.data)});window.displayUrl=function(e){const t=document.getElementById("received-url");t?t.textContent=e||"No URL received or an error occurred.":console.error("Element with ID 'received-url' not found in displayUrl.")}; diff --git a/v3/examples/custom-protocol-example/frontend/dist/assets/index-uDrhEpT0.css b/v3/examples/custom-protocol-example/frontend/dist/assets/index-uDrhEpT0.css new file mode 100644 index 000000000..37a5c205c --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/dist/assets/index-uDrhEpT0.css @@ -0,0 +1 @@ +:root{font-family:system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}a{font-weight:500;color:#646cff;text-decoration:inherit}a:hover{color:#535bf2}body{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;margin:0;background-color:#f7f7f7;color:#333;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:20px;box-sizing:border-box}h1{color:#0056b3;font-size:1.8em;margin-bottom:.8em}#app{width:100%;max-width:600px;margin:auto}.container{background-color:#fff;padding:25px 30px;border-radius:8px;box-shadow:0 2px 10px #0000001a;text-align:left}p{line-height:1.6;font-size:1em;margin-bottom:1em}a{color:#007bff;text-decoration:none}a:hover{text-decoration:underline}.url-display{margin-top:25px;padding:15px;background-color:#e9ecef;border:1px solid #ced4da;border-radius:4px;font-family:Courier New,Courier,monospace;font-size:.95em;word-break:break-all}.label{font-weight:700;display:block;margin-bottom:8px;color:#495057}.logo{height:6em;padding:1.5em;will-change:filter;transition:filter .3s}.logo:hover{filter:drop-shadow(0 0 2em #646cffaa)}.logo.vanilla:hover{filter:drop-shadow(0 0 2em #f7df1eaa)}.card{padding:2em}.read-the-docs{color:#888}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;background-color:#1a1a1a;cursor:pointer;transition:border-color .25s}button:hover{border-color:#646cff}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}@media (prefers-color-scheme: light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}} diff --git a/v3/examples/custom-protocol-example/frontend/dist/index.html b/v3/examples/custom-protocol-example/frontend/dist/index.html new file mode 100644 index 000000000..126962c69 --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/dist/index.html @@ -0,0 +1,14 @@ + + + + + + + Vite App + + + + +
+ + diff --git a/v3/examples/custom-protocol-example/frontend/dist/vite.svg b/v3/examples/custom-protocol-example/frontend/dist/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/dist/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/custom-protocol-example/frontend/index.html b/v3/examples/custom-protocol-example/frontend/index.html new file mode 100644 index 000000000..72ba3a8b3 --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/v3/examples/custom-protocol-example/frontend/package-lock.json b/v3/examples/custom-protocol-example/frontend/package-lock.json new file mode 100644 index 000000000..2b2b28cc5 --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/package-lock.json @@ -0,0 +1,1017 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@wailsio/runtime": "^3.0.0-alpha.66" + }, + "devDependencies": { + "vite": "^6.3.5" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", + "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz", + "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz", + "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz", + "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz", + "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz", + "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz", + "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz", + "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz", + "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz", + "integrity": "sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz", + "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz", + "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz", + "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz", + "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz", + "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz", + "integrity": "sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz", + "integrity": "sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz", + "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz", + "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz", + "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@wailsio/runtime": { + "version": "3.0.0-alpha.66", + "resolved": "https://registry.npmjs.org/@wailsio/runtime/-/runtime-3.0.0-alpha.66.tgz", + "integrity": "sha512-ENLu8rn1griL1gFHJqkq1i+BVxrrA0JPJHYneUJYuf/s54kjuQViW0RKDEe/WTDo56ABpfykrd/T8OYpPUyXUw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" + } + }, + "node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz", + "integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.41.0", + "@rollup/rollup-android-arm64": "4.41.0", + "@rollup/rollup-darwin-arm64": "4.41.0", + "@rollup/rollup-darwin-x64": "4.41.0", + "@rollup/rollup-freebsd-arm64": "4.41.0", + "@rollup/rollup-freebsd-x64": "4.41.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.0", + "@rollup/rollup-linux-arm-musleabihf": "4.41.0", + "@rollup/rollup-linux-arm64-gnu": "4.41.0", + "@rollup/rollup-linux-arm64-musl": "4.41.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.0", + "@rollup/rollup-linux-riscv64-gnu": "4.41.0", + "@rollup/rollup-linux-riscv64-musl": "4.41.0", + "@rollup/rollup-linux-s390x-gnu": "4.41.0", + "@rollup/rollup-linux-x64-gnu": "4.41.0", + "@rollup/rollup-linux-x64-musl": "4.41.0", + "@rollup/rollup-win32-arm64-msvc": "4.41.0", + "@rollup/rollup-win32-ia32-msvc": "4.41.0", + "@rollup/rollup-win32-x64-msvc": "4.41.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/v3/examples/custom-protocol-example/frontend/package.json b/v3/examples/custom-protocol-example/frontend/package.json new file mode 100644 index 000000000..09086988e --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/package.json @@ -0,0 +1,17 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^6.3.5" + }, + "dependencies": { + "@wailsio/runtime": "latest" + } +} diff --git a/v3/examples/custom-protocol-example/frontend/public/vite.svg b/v3/examples/custom-protocol-example/frontend/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/custom-protocol-example/frontend/src/counter.js b/v3/examples/custom-protocol-example/frontend/src/counter.js new file mode 100644 index 000000000..881e2d7ad --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/src/counter.js @@ -0,0 +1,9 @@ +export function setupCounter(element) { + let counter = 0 + const setCounter = (count) => { + counter = count + element.innerHTML = `count is ${counter}` + } + element.addEventListener('click', () => setCounter(counter + 1)) + setCounter(0) +} diff --git a/v3/examples/custom-protocol-example/frontend/src/javascript.svg b/v3/examples/custom-protocol-example/frontend/src/javascript.svg new file mode 100644 index 000000000..f9abb2b72 --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/src/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/custom-protocol-example/frontend/src/main.js b/v3/examples/custom-protocol-example/frontend/src/main.js new file mode 100644 index 000000000..fd11d82fa --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/src/main.js @@ -0,0 +1,45 @@ +import './style.css' +import { Events } from '@wailsio/runtime' + +document.addEventListener('DOMContentLoaded', () => { + const appDiv = document.getElementById('app'); + if (appDiv) { + appDiv.innerHTML = ` +
+

Custom Protocol / Deep Link Test

+

+ This page demonstrates handling custom URL schemes (deep links). +

+

+ Example Link: + Try opening this URL (e.g., by pasting it into your browser's address bar or using open your-app-scheme://... in terminal): +
+ wailsexample://test/path?value=123&message=hello +

+ +
+ Received URL: +
Waiting for application to be opened via a custom URL...
+
+
+ `; + } else { + console.error('Element with ID "app" not found.'); + } +}); + +// Listen for the event from Go +Events.On('frontend:ShowURL', (e) => { + console.log('frontend:ShowURL event received, data:', e); + displayUrl(e.data); +}); + +// Make displayUrl available globally just in case, though direct call from event is better +window.displayUrl = function(url) { + const urlElement = document.getElementById('received-url'); + if (urlElement) { + urlElement.textContent = url || "No URL received or an error occurred."; + } else { + console.error("Element with ID 'received-url' not found in displayUrl."); + } +} diff --git a/v3/examples/custom-protocol-example/frontend/src/style.css b/v3/examples/custom-protocol-example/frontend/src/style.css new file mode 100644 index 000000000..0fb047c33 --- /dev/null +++ b/v3/examples/custom-protocol-example/frontend/src/style.css @@ -0,0 +1,142 @@ +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + margin: 0; /* Reset default margin */ + background-color: #f7f7f7; + color: #333; + display: flex; /* Use flexbox to center content */ + justify-content: center; /* Center horizontally */ + align-items: center; /* Center vertically */ + min-height: 100vh; /* Full viewport height */ + padding: 20px; /* Add some padding around the content */ + box-sizing: border-box; /* Ensure padding doesn't expand body beyond viewport */ +} + +h1 { + color: #0056b3; + font-size: 1.8em; + margin-bottom: 0.8em; +} + +#app { + width: 100%; + max-width: 600px; + margin: auto; /* This also helps in centering if body flex isn't enough or overridden */ +} + +.container { + background-color: #fff; + padding: 25px 30px; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + text-align: left; +} + +p { + line-height: 1.6; + font-size: 1em; + margin-bottom: 1em; +} + +a { + color: #007bff; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +.url-display { + margin-top: 25px; + padding: 15px; + background-color: #e9ecef; + border: 1px solid #ced4da; + border-radius: 4px; + font-family: 'Courier New', Courier, monospace; + font-size: 0.95em; + word-break: break-all; +} + +.label { + font-weight: bold; + display: block; + margin-bottom: 8px; + color: #495057; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/examples/custom-protocol-example/greetservice.go b/v3/examples/custom-protocol-example/greetservice.go new file mode 100644 index 000000000..8972c39cd --- /dev/null +++ b/v3/examples/custom-protocol-example/greetservice.go @@ -0,0 +1,7 @@ +package main + +type GreetService struct{} + +func (g *GreetService) Greet(name string) string { + return "Hello " + name + "!" +} diff --git a/v3/examples/custom-protocol-example/main.go b/v3/examples/custom-protocol-example/main.go new file mode 100644 index 000000000..f5c5db38b --- /dev/null +++ b/v3/examples/custom-protocol-example/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "embed" + _ "embed" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +// Wails uses Go's `embed` package to embed the frontend files into the binary. +// Any files in the frontend/dist folder will be embedded into the binary and +// made available to the frontend. +// See https://pkg.go.dev/embed for more information. + +//go:embed all:frontend/dist +var assets embed.FS + +// main function serves as the application's entry point. It initializes the application, creates a window, +// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and +// logs any error that might occur. +func main() { + // Create a new Wails application by providing the necessary options. + // Variables 'Name' and 'Description' are for application metadata. + // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files. + // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances. + // 'Mac' options tailor the application when running an macOS. + app := application.New(application.Options{ + Name: "custom-protocol-example", + Description: "A demo of using raw HTML & CSS", + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Listen for the system event indicating the app was launched with a URL + app.Event.OnApplicationEvent(events.Common.ApplicationLaunchedWithUrl, func(e *application.ApplicationEvent) { + app.Event.Emit("frontend:ShowURL", e.Context().URL()) + }) + + // Create a new window with the necessary options. + // 'Title' is the title of the window. + // 'Mac' options tailor the window when running on macOS. + // 'BackgroundColour' is the background colour of the window. + // 'URL' is the URL that will be loaded into the webview. + _ = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + BackgroundColour: application.NewRGB(27, 38, 54), + URL: "/", + }) + + // Create a goroutine that emits an event containing the current time every second. + // The frontend can listen to this event and update the UI accordingly. + go func() { + for { + now := time.Now().Format(time.RFC1123) + app.Event.Emit("time", now) + time.Sleep(time.Second) + } + }() + + // Run the application. This blocks until the application has been exited. + err := app.Run() + + // If an error occurred while running the application, log it and exit. + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/dev/.gitignore b/v3/examples/dev/.gitignore new file mode 100644 index 000000000..c2a88322f --- /dev/null +++ b/v3/examples/dev/.gitignore @@ -0,0 +1 @@ +.task \ No newline at end of file diff --git a/v3/examples/dev/README.md b/v3/examples/dev/README.md new file mode 100644 index 000000000..5dcc421f7 --- /dev/null +++ b/v3/examples/dev/README.md @@ -0,0 +1,4 @@ +# Dev Example + +**NOTE**: This example is currently a work in progress. It is not yet ready for use. + diff --git a/v3/examples/dev/Taskfile.yml b/v3/examples/dev/Taskfile.yml new file mode 100644 index 000000000..8d2d46716 --- /dev/null +++ b/v3/examples/dev/Taskfile.yml @@ -0,0 +1,183 @@ +version: '3' + +vars: + APP_NAME: "dev{{exeExt}}" + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + install-frontend-deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build-frontend: + summary: Build the frontend project + dir: frontend + sources: + - src/* + generates: + - dist/* + deps: + - install-frontend-deps + cmds: + - npm run build + + build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:backend:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - go build -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + build:windows: + summary: Builds the application for Windows + platforms: + - windows + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + - task: post-build + + + build:backend:windows: + summary: Builds the backend application for Windows + platforms: + - windows + cmds: + - task: pre-build + - go build -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + - task: post-build + + build: + summary: Builds the application + watch: true + sources: + - main.go + cmds: + - task: build:darwin + - task: build:windows + - task: run + + build:backend: + summary: Builds the backend application + cmds: + - task: build:backend:darwin + - task: build:backend:windows + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate icons -input appicon.png + + build-app-prod-darwin: + summary: Creates a production build of the application + cmds: + - task: pre-build + - task: build-frontend + - GOOS=darwin GOARCH={{.ARCH}} go build -tags production -ldflags="-w -s" -o build/bin/{{.APP_NAME}} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + vars: + ARCH: $GOARCH + + frontend:dev: + summary: Runs the frontend in development mode + deps: + - task: install-frontend-deps + dir: frontend + cmds: + - npm run dev + + run: + summary: Runs the application + cmds: + - ./bin/{{.APP_NAME}} + + dev: + summary: Runs the application in development mode + watch: true + preconditions: + - sh: 'wails3 tool checkport -p 5173' + msg: "Vite does not appear to be running. Please run `wails3 task frontend:dev` in another terminal." + cmds: + - task: build:backend + + create-app-bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{.APP_NAME}}.app/Contents/Resources + - cp build/bin/{{.APP_NAME}} {{.APP_NAME}}.app/Contents/MacOS + - cp build/Info.plist {{.APP_NAME}}.app/Contents + + package-darwin-arm64: + summary: Packages a production build of the application into a `.app` bundle + platform: darwin + deps: + - task: build-app-prod-darwin + vars: + ARCH: arm64 + - generate-icons + cmds: + - task: create-app-bundle + + generate:syso: + dir: build + platform: windows + cmds: + - wails generate syso -arch {{.ARCH}} -icon icon.ico -manifest wails.exe.manifest -info info.json + vars: + ARCH: $GOARCH + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platform: windows + deps: + - generate-icons + cmds: + - task: generate:syso + vars: + ARCH: amd64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/{{.APP_NAME}}.exe + - powershell Remove-item *.syso diff --git a/v3/examples/dev/build/Info.dev.plist b/v3/examples/dev/build/Info.dev.plist new file mode 100644 index 000000000..0c0ed6032 --- /dev/null +++ b/v3/examples/dev/build/Info.dev.plist @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + dev + CFBundleIdentifier + com.wails.dev + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/examples/dev/build/Info.plist b/v3/examples/dev/build/Info.plist new file mode 100644 index 000000000..f9ea24900 --- /dev/null +++ b/v3/examples/dev/build/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + dev + CFBundleIdentifier + com.wails.dev + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + v1.0.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) 2023 My Company Name + + \ No newline at end of file diff --git a/v3/examples/dev/build/appicon.png b/v3/examples/dev/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/dev/build/appicon.png differ diff --git a/v3/examples/dev/build/icons.icns b/v3/examples/dev/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/dev/build/icons.icns differ diff --git a/v3/examples/dev/frontend/dist/assets/index-3635012e.css b/v3/examples/dev/frontend/dist/assets/index-3635012e.css new file mode 100644 index 000000000..14cfb027f --- /dev/null +++ b/v3/examples/dev/frontend/dist/assets/index-3635012e.css @@ -0,0 +1 @@ +:root{font-family:Inter,Avenir,Helvetica,Arial,sans-serif;font-size:16px;line-height:24px;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-text-size-adjust:100%}a{font-weight:500;color:#646cff;text-decoration:inherit}a:hover{color:#535bf2}body{margin:0;display:flex;place-items:center;min-width:320px;min-height:100vh}h1{font-size:3.2em;line-height:1.1}.card{padding:2em}#app{max-width:1280px;margin:0 auto;padding:2rem;text-align:center}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;background-color:#1a1a1a;cursor:pointer;transition:border-color .25s}button:hover{border-color:#646cff}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}@media (prefers-color-scheme: light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}}.logo.svelte-c9fbf7{height:6em;padding:1.5em;will-change:filter}.logo.svelte-c9fbf7:hover{filter:drop-shadow(0 0 2em #646cffaa)}.logo.svelte.svelte-c9fbf7:hover{filter:drop-shadow(0 0 2em #ff3e00aa)}.read-the-docs.svelte-c9fbf7{color:#888} diff --git a/v3/examples/dev/frontend/dist/assets/index-9076c63b.js b/v3/examples/dev/frontend/dist/assets/index-9076c63b.js new file mode 100644 index 000000000..e2b665b28 --- /dev/null +++ b/v3/examples/dev/frontend/dist/assets/index-9076c63b.js @@ -0,0 +1 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const s of o)if(s.type==="childList")for(const i of s.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&r(i)}).observe(document,{childList:!0,subtree:!0});function n(o){const s={};return o.integrity&&(s.integrity=o.integrity),o.referrerPolicy&&(s.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?s.credentials="include":o.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(o){if(o.ep)return;o.ep=!0;const s=n(o);fetch(o.href,s)}})();function v(){}function I(e){return e()}function W(){return Object.create(null)}function A(e){e.forEach(I)}function K(e){return typeof e=="function"}function F(e,t){return e!=e?t==t:e!==t||e&&typeof e=="object"||typeof e=="function"}let S;function X(e,t){return S||(S=document.createElement("a")),S.href=t,e===S.href}function Y(e){return Object.keys(e).length===0}function c(e,t){e.appendChild(t)}function V(e,t,n){e.insertBefore(t,n||null)}function M(e){e.parentNode&&e.parentNode.removeChild(e)}function a(e){return document.createElement(e)}function k(e){return document.createTextNode(e)}function w(){return k(" ")}function Z(e,t,n,r){return e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)}function u(e,t,n){n==null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n)}function ee(e){return Array.from(e.childNodes)}function te(e,t){t=""+t,e.data!==t&&(e.data=t)}let q;function E(e){q=e}const $=[],B=[];let y=[];const H=[],ne=Promise.resolve();let j=!1;function re(){j||(j=!0,ne.then(z))}function P(e){y.push(e)}const N=new Set;let g=0;function z(){if(g!==0)return;const e=q;do{try{for(;g<$.length;){const t=$[g];g++,E(t),oe(t.$$)}}catch(t){throw $.length=0,g=0,t}for(E(null),$.length=0,g=0;B.length;)B.pop()();for(let t=0;te.indexOf(r)===-1?t.push(r):n.push(r)),n.forEach(r=>r()),y=t}const C=new Set;let ie;function D(e,t){e&&e.i&&(C.delete(e),e.i(t))}function le(e,t,n,r){if(e&&e.o){if(C.has(e))return;C.add(e),ie.c.push(()=>{C.delete(e),r&&(n&&e.d(1),r())}),e.o(t)}else r&&r()}function ce(e){e&&e.c()}function G(e,t,n,r){const{fragment:o,after_update:s}=e.$$;o&&o.m(t,n),r||P(()=>{const i=e.$$.on_mount.map(I).filter(K);e.$$.on_destroy?e.$$.on_destroy.push(...i):A(i),e.$$.on_mount=[]}),s.forEach(P)}function J(e,t){const n=e.$$;n.fragment!==null&&(se(n.after_update),A(n.on_destroy),n.fragment&&n.fragment.d(t),n.on_destroy=n.fragment=null,n.ctx=[])}function fe(e,t){e.$$.dirty[0]===-1&&($.push(e),re(),e.$$.dirty.fill(0)),e.$$.dirty[t/31|0]|=1<{const _=x.length?x[0]:d;return l.ctx&&o(l.ctx[f],l.ctx[f]=_)&&(!l.skip_bound&&l.bound[f]&&l.bound[f](_),b&&fe(e,f)),d}):[],l.update(),b=!0,A(l.before_update),l.fragment=r?r(l.ctx):!1,t.target){if(t.hydrate){const f=ee(t.target);l.fragment&&l.fragment.l(f),f.forEach(M)}else l.fragment&&l.fragment.c();t.intro&&D(e.$$.fragment),G(e,t.target,t.anchor,t.customElement),z()}E(p)}class R{$destroy(){J(this,1),this.$destroy=v}$on(t,n){if(!K(n))return v;const r=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return r.push(n),()=>{const o=r.indexOf(n);o!==-1&&r.splice(o,1)}}$set(t){this.$$set&&!Y(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}const ue="/assets/svelte-a39f39b7.svg";function ae(e){let t,n,r,o,s;return{c(){t=a("button"),n=k("count is "),r=k(e[0])},m(i,h){V(i,t,h),c(t,n),c(t,r),o||(s=Z(t,"click",e[1]),o=!0)},p(i,[h]){h&1&&te(r,i[0])},i:v,o:v,d(i){i&&M(t),o=!1,s()}}}function de(e,t,n){let r=0;return[r,()=>{n(0,r+=1)}]}class he extends R{constructor(t){super(),Q(this,t,de,ae,F,{})}}function pe(e){let t,n,r,o,s,i,h,p,l,b,f,d,x,_,T,L,O;return d=new he({}),{c(){t=a("main"),n=a("div"),r=a("a"),r.innerHTML='',o=w(),s=a("a"),i=a("img"),p=w(),l=a("h1"),l.textContent="Wails + Svelte",b=w(),f=a("div"),ce(d.$$.fragment),x=w(),_=a("p"),_.innerHTML='Check out SvelteKit, the official Svelte app framework powered by Vite!',T=w(),L=a("p"),L.textContent="Click on the Wails and Svelte logos to learn more",u(r,"href","https://wails.io"),u(r,"target","_blank"),u(r,"rel","noreferrer"),X(i.src,h=ue)||u(i,"src",h),u(i,"class","logo svelte svelte-c9fbf7"),u(i,"alt","Svelte Logo"),u(s,"href","https://svelte.dev"),u(s,"target","_blank"),u(s,"rel","noreferrer"),u(f,"class","card"),u(L,"class","read-the-docs svelte-c9fbf7")},m(m,U){V(m,t,U),c(t,n),c(n,r),c(n,o),c(n,s),c(s,i),c(t,p),c(t,l),c(t,b),c(t,f),G(d,f,null),c(t,x),c(t,_),c(t,T),c(t,L),O=!0},p:v,i(m){O||(D(d.$$.fragment,m),O=!0)},o(m){le(d.$$.fragment,m),O=!1},d(m){m&&M(t),J(d)}}}class me extends R{constructor(t){super(),Q(this,t,null,pe,F,{})}}new me({target:document.getElementById("app")}); diff --git a/v3/examples/dev/frontend/dist/assets/svelte-a39f39b7.svg b/v3/examples/dev/frontend/dist/assets/svelte-a39f39b7.svg new file mode 100644 index 000000000..c5e08481f --- /dev/null +++ b/v3/examples/dev/frontend/dist/assets/svelte-a39f39b7.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/dev/frontend/dist/index.html b/v3/examples/dev/frontend/dist/index.html new file mode 100644 index 000000000..c4380af7b --- /dev/null +++ b/v3/examples/dev/frontend/dist/index.html @@ -0,0 +1,15 @@ + + + + + + + Wails + Svelte + + + + +
+ + + diff --git a/v3/examples/dev/frontend/dist/wails.png b/v3/examples/dev/frontend/dist/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/dev/frontend/dist/wails.png differ diff --git a/v3/examples/dev/frontend/index.html b/v3/examples/dev/frontend/index.html new file mode 100644 index 000000000..1ea50f904 --- /dev/null +++ b/v3/examples/dev/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Svelte + + +
+ + + diff --git a/v3/examples/dev/frontend/package-lock.json b/v3/examples/dev/frontend/package-lock.json new file mode 100644 index 000000000..309115bf9 --- /dev/null +++ b/v3/examples/dev/frontend/package-lock.json @@ -0,0 +1,685 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.0.0", + "svelte": "^3.54.0", + "vite": "^4.5.2" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.5.tgz", + "integrity": "sha512-UJKsFNwhzCVuiZd06jM/psscyNJNDwjQC+qIeb7GBJK9iWeQCcIyfcPWDvbCudfcJggY9jtxJeeaZH7uny93FQ==", + "dev": true, + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^1.0.3", + "debug": "^4.3.4", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.2", + "svelte-hmr": "^0.15.3", + "vitefu": "^0.2.4" + }, + "engines": { + "node": "^14.18.0 || >= 16" + }, + "peerDependencies": { + "svelte": "^3.54.0 || ^4.0.0", + "vite": "^4.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.3.tgz", + "integrity": "sha512-Khdl5jmmPN6SUsVuqSXatKpQTMIifoQPDanaxC84m9JxIibWvSABJyHpyys0Z+1yYrxY5TTEQm+6elh0XCMaOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": "^14.18.0 || >= 16" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.2.0", + "svelte": "^3.54.0 || ^4.0.0", + "vite": "^4.0.0" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/magic-string": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", + "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", + "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz", + "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "3.59.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.2.tgz", + "integrity": "sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/svelte-hmr": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz", + "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==", + "dev": true, + "engines": { + "node": "^12.20 || ^14.13.1 || >= 16" + }, + "peerDependencies": { + "svelte": "^3.19.0 || ^4.0.0" + } + }, + "node_modules/vite": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz", + "integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz", + "integrity": "sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==", + "dev": true, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + } + } +} diff --git a/v3/examples/dev/frontend/package.json b/v3/examples/dev/frontend/package.json new file mode 100644 index 000000000..ed218cf99 --- /dev/null +++ b/v3/examples/dev/frontend/package.json @@ -0,0 +1,16 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.0.0", + "svelte": "^3.54.0", + "vite": "^4.5.2" + } +} \ No newline at end of file diff --git a/v3/examples/dev/frontend/public/wails.png b/v3/examples/dev/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/dev/frontend/public/wails.png differ diff --git a/v3/examples/dev/frontend/src/App.svelte b/v3/examples/dev/frontend/src/App.svelte new file mode 100644 index 000000000..539c395dd --- /dev/null +++ b/v3/examples/dev/frontend/src/App.svelte @@ -0,0 +1,45 @@ + + +
+ +

Wails + Svelte

+ +
+ +
+ +

+ Check out SvelteKit, the official Svelte app framework powered by Vite! +

+ +

+ Click on the Wails and Svelte logos to learn more +

+
+ + diff --git a/v3/examples/dev/frontend/src/app.css b/v3/examples/dev/frontend/src/app.css new file mode 100644 index 000000000..bcc7233dd --- /dev/null +++ b/v3/examples/dev/frontend/src/app.css @@ -0,0 +1,81 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/examples/dev/frontend/src/assets/svelte.svg b/v3/examples/dev/frontend/src/assets/svelte.svg new file mode 100644 index 000000000..c5e08481f --- /dev/null +++ b/v3/examples/dev/frontend/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/dev/frontend/src/lib/Counter.svelte b/v3/examples/dev/frontend/src/lib/Counter.svelte new file mode 100644 index 000000000..e45f90310 --- /dev/null +++ b/v3/examples/dev/frontend/src/lib/Counter.svelte @@ -0,0 +1,10 @@ + + + diff --git a/v3/examples/dev/frontend/src/main.js b/v3/examples/dev/frontend/src/main.js new file mode 100644 index 000000000..8a909a15a --- /dev/null +++ b/v3/examples/dev/frontend/src/main.js @@ -0,0 +1,8 @@ +import './app.css' +import App from './App.svelte' + +const app = new App({ + target: document.getElementById('app'), +}) + +export default app diff --git a/v3/examples/dev/frontend/src/vite-env.d.ts b/v3/examples/dev/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..4078e7476 --- /dev/null +++ b/v3/examples/dev/frontend/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/v3/examples/dev/frontend/tsconfig.json b/v3/examples/dev/frontend/tsconfig.json new file mode 100644 index 000000000..b3e8a23aa --- /dev/null +++ b/v3/examples/dev/frontend/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "allowJs": true, + "moduleResolution": "Node", + "target": "ESNext", + "module": "ESNext", + /** + * svelte-preprocess cannot figure out whether you have + * a value or a type, so tell TypeScript to enforce using + * `import type` instead of `import` for Types. + */ + "importsNotUsedAsValues": "error", + "isolatedModules": true, + "resolveJsonModule": true, + /** + * To have warnings / errors of the Svelte compiler at the + * correct position, enable source maps by default. + */ + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable this if you'd like to use dynamic types. + */ + "checkJs": true + }, + /** + * Use global.d.ts instead of compilerOptions.types + * to avoid limiting type declarations. + */ + "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte", "bindings"] +} diff --git a/v3/examples/dev/frontend/vite.config.js b/v3/examples/dev/frontend/vite.config.js new file mode 100644 index 000000000..d70196943 --- /dev/null +++ b/v3/examples/dev/frontend/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/v3/examples/dev/go.mod b/v3/examples/dev/go.mod new file mode 100644 index 000000000..296394de3 --- /dev/null +++ b/v3/examples/dev/go.mod @@ -0,0 +1,49 @@ +module changeme + +go 1.25 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.62 + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-git/go-git/v5 v5.16.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/lmittmann/tint v1.1.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.52.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/wailsapp/go-webview2 v1.0.23 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/dev/go.sum b/v3/examples/dev/go.sum new file mode 100644 index 000000000..56f1153ea --- /dev/null +++ b/v3/examples/dev/go.sum @@ -0,0 +1,147 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= +github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/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-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/examples/dev/main.go b/v3/examples/dev/main.go new file mode 100644 index 000000000..111273721 --- /dev/null +++ b/v3/examples/dev/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "dev", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/dialogs-basic/.hidden_file b/v3/examples/dialogs-basic/.hidden_file new file mode 100644 index 000000000..e69de29bb diff --git a/v3/examples/dialogs-basic/README.md b/v3/examples/dialogs-basic/README.md new file mode 100644 index 000000000..c03911376 --- /dev/null +++ b/v3/examples/dialogs-basic/README.md @@ -0,0 +1,36 @@ +# Dialog Test Application + +This application is designed to test macOS file dialog functionality across different versions of macOS. It provides a comprehensive suite of tests for various dialog features and configurations. + +## Features Tested + +1. Basic file open dialog +2. Single extension filter +3. Multiple extension filter +4. Multiple file selection +5. Directory selection +6. Save dialog with extension +7. Complex filters +8. Hidden files +9. Default directory +10. Full featured dialog with all options + +## Running the Tests + +```bash +go run main.go +``` + +## Test Results + +When running tests: +- Each test will show the selected file(s) and their types +- For multiple selections, all selected files will be listed +- Errors will be displayed in an error dialog +- The application logs debug information to help track issues + +## Notes + +- This test application is primarily for development and testing purposes +- It can be used to verify dialog behavior across different macOS versions +- The tests are designed to not interfere with CI pipelines diff --git a/v3/examples/dialogs-basic/main.go b/v3/examples/dialogs-basic/main.go new file mode 100644 index 000000000..0b47b34b3 --- /dev/null +++ b/v3/examples/dialogs-basic/main.go @@ -0,0 +1,257 @@ +package main + +import ( + "fmt" + "log" + "log/slog" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Dialog Test", + Description: "Test application for macOS dialogs", + Logger: application.DefaultLogger(slog.LevelDebug), + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create main window + mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Dialog Tests", + Width: 800, + Height: 600, + MinWidth: 800, + MinHeight: 600, + }) + mainWindow.SetAlwaysOnTop(true) + + // Create main menu + menu := app.NewMenu() + app.Menu.Set(menu) + menu.AddRole(application.AppMenu) + menu.AddRole(application.EditMenu) + menu.AddRole(application.WindowMenu) + + // Add test menu + testMenu := menu.AddSubmenu("Tests") + + // Test 1: Basic file open with no filters (no window) + testMenu.Add("1. Basic Open (No Window)").OnClick(func(ctx *application.Context) { + result, err := app.Dialog.OpenFile(). + CanChooseFiles(true). + PromptForSingleSelection() + showResult("Basic Open", result, err, nil) + }) + + // Test 1b: Basic file open with window + testMenu.Add("1b. Basic Open (With Window)").OnClick(func(ctx *application.Context) { + result, err := app.Dialog.OpenFile(). + CanChooseFiles(true). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Basic Open", result, err, mainWindow) + }) + + // Test 2: Open with single extension filter + testMenu.Add("2. Single Filter").OnClick(func(ctx *application.Context) { + result, err := app.Dialog.OpenFile(). + CanChooseFiles(true). + AddFilter("Text Files", "*.txt"). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Single Filter", result, err, mainWindow) + }) + + // Test 3: Open with multiple extension filter + testMenu.Add("3. Multiple Filter").OnClick(func(ctx *application.Context) { + result, err := app.Dialog.OpenFile(). + CanChooseFiles(true). + AddFilter("Documents", "*.txt;*.md;*.doc;*.docx"). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Multiple Filter", result, err, mainWindow) + }) + + // Test 4: Multiple file selection + testMenu.Add("4. Multiple Selection").OnClick(func(ctx *application.Context) { + results, err := app.Dialog.OpenFile(). + CanChooseFiles(true). + AddFilter("Images", "*.png;*.jpg;*.jpeg"). + AttachToWindow(mainWindow). + PromptForMultipleSelection() + if err != nil { + showError("Multiple Selection", err, mainWindow) + return + } + showResults("Multiple Selection", results, mainWindow) + }) + + // Test 5: Directory selection + testMenu.Add("5. Directory Selection").OnClick(func(ctx *application.Context) { + result, err := app.Dialog.OpenFile(). + CanChooseDirectories(true). + CanChooseFiles(false). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Directory Selection", result, err, mainWindow) + }) + + // Test 6: Save dialog with extension + testMenu.Add("6. Save Dialog").OnClick(func(ctx *application.Context) { + result, err := app.Dialog.SaveFile(). + SetFilename("test.txt"). + AddFilter("Text Files", "*.txt"). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Save Dialog", result, err, mainWindow) + }) + + // Test 7: Complex filters + testMenu.Add("7. Complex Filters").OnClick(func(ctx *application.Context) { + result, err := app.Dialog.OpenFile(). + CanChooseFiles(true). + AddFilter("All Documents", "*.txt;*.md;*.doc;*.docx;*.pdf"). + AddFilter("Text Files", "*.txt"). + AddFilter("Markdown", "*.md"). + AddFilter("Word Documents", "*.doc;*.docx"). + AddFilter("PDF Files", "*.pdf"). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Complex Filters", result, err, mainWindow) + }) + + // Test 8: Hidden files + testMenu.Add("8. Show Hidden").OnClick(func(ctx *application.Context) { + result, err := app.Dialog.OpenFile(). + CanChooseFiles(true). + ShowHiddenFiles(true). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Show Hidden", result, err, mainWindow) + }) + + // Test 9: Default directory + testMenu.Add("9. Default Directory").OnClick(func(ctx *application.Context) { + home, _ := os.UserHomeDir() + result, err := app.Dialog.OpenFile(). + CanChooseFiles(true). + SetDirectory(home). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Default Directory", result, err, mainWindow) + }) + + // Test 10: Full featured dialog + testMenu.Add("10. Full Featured").OnClick(func(ctx *application.Context) { + home, _ := os.UserHomeDir() + dialog := app.Dialog.OpenFile(). + SetTitle("Full Featured Dialog"). + SetDirectory(home). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + ResolvesAliases(true). + AllowsOtherFileTypes(true). + AttachToWindow(mainWindow) + + if runtime.GOOS == "darwin" { + dialog.SetMessage("Please select files") + } + + dialog.AddFilter("All Supported", "*.txt;*.md;*.pdf;*.png;*.jpg") + dialog.AddFilter("Documents", "*.txt;*.md;*.pdf") + dialog.AddFilter("Images", "*.png;*.jpg;*.jpeg") + + results, err := dialog.PromptForMultipleSelection() + if err != nil { + showError("Full Featured", err, mainWindow) + return + } + showResults("Full Featured", results, mainWindow) + }) + + // Show the window + mainWindow.Show() + + // Run the app + if err := app.Run(); err != nil { + log.Fatal(err) + } +} + +func showResult(test string, result string, err error, window *application.WebviewWindow) { + if err != nil { + showError(test, err, window) + return + } + if result == "" { + dialog := application.Get().Dialog.Info(). + SetTitle(test). + SetMessage("No file selected") + if window != nil { + dialog.AttachToWindow(window) + } + dialog.Show() + return + } + dialog := application.Get().Dialog.Info(). + SetTitle(test). + SetMessage(fmt.Sprintf("Selected: %s\nType: %s", result, getFileType(result))) + if window != nil { + dialog.AttachToWindow(window) + } + dialog.Show() +} + +func showResults(test string, results []string, window *application.WebviewWindow) { + if len(results) == 0 { + dialog := application.Get().Dialog.Info(). + SetTitle(test). + SetMessage("No files selected") + if window != nil { + dialog.AttachToWindow(window) + } + dialog.Show() + return + } + var message strings.Builder + message.WriteString(fmt.Sprintf("Selected %d files:\n\n", len(results))) + for _, result := range results { + message.WriteString(fmt.Sprintf("%s (%s)\n", result, getFileType(result))) + } + dialog := application.Get().Dialog.Info(). + SetTitle(test). + SetMessage(message.String()) + if window != nil { + dialog.AttachToWindow(window) + } + dialog.Show() +} + +func showError(test string, err error, window *application.WebviewWindow) { + dialog := application.Get().Dialog.Error(). + SetTitle(test). + SetMessage(fmt.Sprintf("Error: %v", err)) + if window != nil { + dialog.AttachToWindow(window) + } + dialog.Show() +} + +func getFileType(path string) string { + if path == "" { + return "unknown" + } + ext := filepath.Ext(path) + if ext == "" { + return "no extension" + } + return ext +} diff --git a/v3/examples/dialogs-basic/test.txt b/v3/examples/dialogs-basic/test.txt new file mode 100644 index 000000000..64b89ccaf --- /dev/null +++ b/v3/examples/dialogs-basic/test.txt @@ -0,0 +1 @@ +This is a sample text file to test filtering. \ No newline at end of file diff --git a/v3/examples/dialogs-basic/wails-logo-small.jpg b/v3/examples/dialogs-basic/wails-logo-small.jpg new file mode 100644 index 000000000..29cb1129e Binary files /dev/null and b/v3/examples/dialogs-basic/wails-logo-small.jpg differ diff --git a/v3/examples/dialogs-basic/wails-logo-small.png b/v3/examples/dialogs-basic/wails-logo-small.png new file mode 100644 index 000000000..cbd40bf90 Binary files /dev/null and b/v3/examples/dialogs-basic/wails-logo-small.png differ diff --git a/v3/examples/dialogs/README.md b/v3/examples/dialogs/README.md new file mode 100644 index 000000000..d80afc91a --- /dev/null +++ b/v3/examples/dialogs/README.md @@ -0,0 +1,33 @@ +# Dialogs Example + +This example is a comprehensive example of using dialogs in Wails. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run main.go +``` + +## Building the example + +To build the example in debug mode, simply run the following command: + +```bash +wails3 task build +``` + +To build the example to use application icons, simply run the following command: + +```bash +wails3 task package +``` + +# Status + +| Platform | Status | +|----------|----------------| +| Mac | Mostly Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/dialogs/Taskfile.yml b/v3/examples/dialogs/Taskfile.yml new file mode 100644 index 000000000..90ac01145 --- /dev/null +++ b/v3/examples/dialogs/Taskfile.yml @@ -0,0 +1,107 @@ +version: '3' + +vars: + APP_NAME: "buildtest{{exeExt}}" + + +tasks: + + build: + summary: Builds the application + cmds: + - go build -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + package: + summary: Packages a production build of the application into a `.app` or `.exe` bundle + cmds: + - task: package:darwin + - task: package:windows + +# ------------------------------------------------------------------------------ + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails3 generate icons -input appicon.png + + build:production:darwin: + summary: Creates a production build of the application + cmds: + - GOOS=darwin GOARCH={{.GOARCH}} go build -tags production -ldflags="-w -s" -o build/bin/{{.APP_NAME}} + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + GOARCH: '{{.GOARCH}}' + + generate:app_bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{.APP_NAME}}.app/Contents/Resources + - cp build/bin/{{.APP_NAME}} {{.APP_NAME}}.app/Contents/MacOS + - cp build/Info.plist {{.APP_NAME}}.app/Contents + + package:darwin:arm64: + summary: Packages a production build of the application into a `.app` bundle + platforms: + - darwin + deps: + - task: build:production:darwin + vars: + ARCH: arm64 + - generate:icons + cmds: + - task: generate:app_bundle + + package:darwin: + summary: Packages a production build of the application into a `.app` bundle + platforms: + - darwin + deps: + - task: build:production:darwin + - generate:icons + cmds: + - task: generate:app_bundle + + generate:syso: + dir: build + platforms: + - windows + cmds: + - wails3 generate syso -arch {{.GOARCH}} -icon icon.ico -manifest wails.exe.manifest -info info.json + vars: + GOARCH: '{{.GOARCH}}' + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platforms: + - windows/amd64 + deps: + - generate:icons + cmds: + - task: generate:syso + vars: + GOARCH: amd64 + - go build -tags production -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + - powershell Remove-item *.syso + + + package:windows:arm64: + summary: Packages a production build of the application into a `.exe` bundle (ARM64) + platforms: + - windows + cmds: + - task: generate:syso + vars: + GOARCH: arm64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/buildtest.arm64.exe + - powershell Remove-item *.syso + env: + GOARCH: arm64 diff --git a/v3/examples/dialogs/build/Info.dev.plist b/v3/examples/dialogs/build/Info.dev.plist new file mode 100644 index 000000000..d6d28b179 --- /dev/null +++ b/v3/examples/dialogs/build/Info.dev.plist @@ -0,0 +1,35 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My App + CFBundleExecutable + app + CFBundleIdentifier + com.wails.app + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + The ultimate thing + CFBundleShortVersionString + v1 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) Me + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + + + + \ No newline at end of file diff --git a/v3/examples/dialogs/build/Info.plist b/v3/examples/dialogs/build/Info.plist new file mode 100644 index 000000000..7f03f54e9 --- /dev/null +++ b/v3/examples/dialogs/build/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My App + CFBundleExecutable + app + CFBundleIdentifier + com.wails.app + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + The ultimate thing + CFBundleShortVersionString + v1 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) Me + + \ No newline at end of file diff --git a/v3/examples/dialogs/build/appicon.png b/v3/examples/dialogs/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/dialogs/build/appicon.png differ diff --git a/v3/examples/dialogs/build/icon.ico b/v3/examples/dialogs/build/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/dialogs/build/icon.ico differ diff --git a/v3/examples/dialogs/build/icons.icns b/v3/examples/dialogs/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/dialogs/build/icons.icns differ diff --git a/v3/examples/dialogs/build/info.json b/v3/examples/dialogs/build/info.json new file mode 100644 index 000000000..1005eb5cb --- /dev/null +++ b/v3/examples/dialogs/build/info.json @@ -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" + } + } +} \ No newline at end of file diff --git a/v3/examples/dialogs/build/wails.exe.manifest b/v3/examples/dialogs/build/wails.exe.manifest new file mode 100644 index 000000000..fb1ce5bde --- /dev/null +++ b/v3/examples/dialogs/build/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/v3/examples/dialogs/main.go b/v3/examples/dialogs/main.go new file mode 100644 index 000000000..e99878941 --- /dev/null +++ b/v3/examples/dialogs/main.go @@ -0,0 +1,379 @@ +package main + +import ( + _ "embed" + "log" + "log/slog" + "os" + "runtime" + "strings" + + "github.com/wailsapp/wails/v3/pkg/icons" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + + app := application.New(application.Options{ + Name: "Dialogs Demo", + Description: "A demo of the dialogs API", + Assets: application.AlphaAssets, + Logger: application.DefaultLogger(slog.LevelDebug), + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a custom menu + menu := app.NewMenu() + + // macOS: Add application menu + if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) + } + + // All platforms: Add standard menus + menu.AddRole(application.FileMenu) + menu.AddRole(application.EditMenu) + menu.AddRole(application.WindowMenu) + menu.AddRole(application.ServicesMenu) + menu.AddRole(application.HelpMenu) + + // Let's make a "Demo" menu + infoMenu := menu.AddSubmenu("Info") + infoMenu.Add("Info").OnClick(func(ctx *application.Context) { + dialog := app.Dialog.Info() + 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.Dialog.Info() + dialog.SetTitle("Custom Title") + dialog.Show() + }) + infoMenu.Add("Info (Message only)").OnClick(func(ctx *application.Context) { + dialog := app.Dialog.Info() + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + infoMenu.Add("Info (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := app.Dialog.Info() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(icons.ApplicationDarkMode256) + dialog.Show() + }) + infoMenu.Add("About").OnClick(func(ctx *application.Context) { + app.Menu.ShowAbout() + }) + + questionMenu := menu.AddSubmenu("Question") + questionMenu.Add("Question (No default)").OnClick(func(ctx *application.Context) { + dialog := app.Dialog.Question() + dialog.SetMessage("No default button") + dialog.AddButton("Yes") + dialog.AddButton("No") + dialog.Show() + }) + questionMenu.Add("Question (Attached to Window)").OnClick(func(ctx *application.Context) { + dialog := app.Dialog.Question() + dialog.AttachToWindow(app.Window.Current()) + 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.Dialog.Question() + 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.Dialog.Question(). + SetTitle("Update"). + SetMessage("The cancel button is selected when pressing escape") + download := dialog.AddButton("📥 Download") + download.OnClick(func() { + app.Dialog.Info().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.Dialog.Question() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(icons.WailsLogoWhiteTransparent) + likeIt := dialog.AddButton("I like it!").OnClick(func() { + app.Dialog.Info().SetMessage("Thanks!").Show() + }) + dialog.AddButton("Not so keen...").OnClick(func() { + app.Dialog.Info().SetMessage("Too bad!").Show() + }) + dialog.SetDefaultButton(likeIt) + dialog.Show() + }) + + warningMenu := menu.AddSubmenu("Warning") + warningMenu.Add("Warning").OnClick(func(ctx *application.Context) { + app.Dialog.Warning(). + SetTitle("Custom Title"). + SetMessage("This is a custom message"). + Show() + }) + warningMenu.Add("Warning (Title only)").OnClick(func(ctx *application.Context) { + dialog := app.Dialog.Warning() + dialog.SetTitle("Custom Title") + dialog.Show() + }) + warningMenu.Add("Warning (Message only)").OnClick(func(ctx *application.Context) { + dialog := app.Dialog.Warning() + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + warningMenu.Add("Warning (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := app.Dialog.Warning() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(icons.ApplicationLightMode256) + dialog.Show() + }) + + errorMenu := menu.AddSubmenu("Error") + errorMenu.Add("Error").OnClick(func(ctx *application.Context) { + dialog := app.Dialog.Error() + 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.Dialog.Error() + dialog.SetTitle("Custom Title") + dialog.Show() + }) + errorMenu.Add("Error (Custom Message)").OnClick(func(ctx *application.Context) { + app.Dialog.Error(). + SetMessage("This is a custom message"). + Show() + }) + errorMenu.Add("Error (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := app.Dialog.Error() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(icons.WailsLogoWhite) + dialog.Show() + }) + + openMenu := menu.AddSubmenu("Open") + openMenu.Add("Open File").OnClick(func(ctx *application.Context) { + result, _ := app.Dialog.OpenFile(). + CanChooseFiles(true). + PromptForSingleSelection() + if result != "" { + app.Dialog.Info().SetMessage(result).Show() + } else { + app.Dialog.Info().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open File (Show Hidden Files)").OnClick(func(ctx *application.Context) { + result, _ := app.Dialog.OpenFile(). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + PromptForSingleSelection() + if result != "" { + app.Dialog.Info().SetMessage(result).Show() + } else { + app.Dialog.Info().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open File (Attach to window)").OnClick(func(ctx *application.Context) { + result, _ := app.Dialog.OpenFile(). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + AttachToWindow(app.Window.Current()). + PromptForSingleSelection() + if result != "" { + app.Dialog.Info().SetMessage(result).Show() + } else { + app.Dialog.Info().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open Multiple Files (Show Hidden Files)").OnClick(func(ctx *application.Context) { + result, _ := app.Dialog.OpenFile(). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + PromptForMultipleSelection() + if len(result) > 0 { + app.Dialog.Info().SetMessage(strings.Join(result, ",")).Show() + } else { + app.Dialog.Info().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open Directory").OnClick(func(ctx *application.Context) { + result, _ := app.Dialog.OpenFile(). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() + if result != "" { + app.Dialog.Info().SetMessage(result).Show() + } else { + app.Dialog.Info().SetMessage("No directory selected").Show() + } + }) + openMenu.Add("Open Directory (Create Directories)").OnClick(func(ctx *application.Context) { + result, _ := app.Dialog.OpenFile(). + CanChooseDirectories(true). + CanCreateDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() + if result != "" { + app.Dialog.Info().SetMessage(result).Show() + } else { + app.Dialog.Info().SetMessage("No directory selected").Show() + } + }) + openMenu.Add("Open Directory (Resolves Aliases)").OnClick(func(ctx *application.Context) { + result, _ := app.Dialog.OpenFile(). + CanChooseDirectories(true). + CanCreateDirectories(true). + CanChooseFiles(false). + ResolvesAliases(true). + PromptForSingleSelection() + if result != "" { + app.Dialog.Info().SetMessage(result).Show() + } else { + app.Dialog.Info().SetMessage("No directory selected").Show() + } + }) + openMenu.Add("Open File/Directory (Set Title)").OnClick(func(ctx *application.Context) { + dialog := app.Dialog.OpenFile(). + 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.Dialog.Info().SetMessage(result).Show() + } else { + app.Dialog.Info().SetMessage("No file/directory selected").Show() + } + }) + openMenu.Add("Open (Full Example)").OnClick(func(ctx *application.Context) { + cwd, _ := os.Getwd() + dialog := app.Dialog.OpenFile(). + 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.Dialog.Info().SetMessage(result).Show() + } else { + app.Dialog.Info().SetMessage("No file selected").Show() + } + }) + + saveMenu := menu.AddSubmenu("Save") + saveMenu.Add("Select File (Defaults)").OnClick(func(ctx *application.Context) { + result, _ := app.Dialog.SaveFile(). + PromptForSingleSelection() + if result != "" { + app.Dialog.Info().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Attach To WebviewWindow)").OnClick(func(ctx *application.Context) { + result, _ := app.Dialog.SaveFile(). + AttachToWindow(app.Window.Current()). + PromptForSingleSelection() + if result != "" { + app.Dialog.Info().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Show Hidden Files)").OnClick(func(ctx *application.Context) { + result, _ := app.Dialog.SaveFile(). + ShowHiddenFiles(true). + PromptForSingleSelection() + if result != "" { + app.Dialog.Info().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Cannot Create Directories)").OnClick(func(ctx *application.Context) { + result, _ := app.Dialog.SaveFile(). + CanCreateDirectories(false). + PromptForSingleSelection() + if result != "" { + app.Dialog.Info().SetMessage(result).Show() + } + }) + + userHomeDir, err := os.UserHomeDir() + if err != nil || userHomeDir == "" { + userHomeDir = os.TempDir() + } + + saveMenu.Add("Select File (Full Example)").OnClick(func(ctx *application.Context) { + result, _ := app.Dialog.SaveFile(). + CanCreateDirectories(false). + ShowHiddenFiles(true). + SetMessage("Select a file"). + SetDirectory(userHomeDir). + SetButtonText("Let's do this!"). + SetFilename("README.md"). + HideExtension(true). + AllowsOtherFileTypes(true). + TreatsFilePackagesAsDirectories(true). + ShowHiddenFiles(true). + PromptForSingleSelection() + if result != "" { + app.Dialog.Info().SetMessage(result).Show() + } + }) + + // Set the application menu + app.Menu.Set(menu) + + // Create window with UseApplicationMenu to inherit the app menu on Windows/Linux + app.Window.NewWithOptions(application.WebviewWindowOptions{ + UseApplicationMenu: true, + }) + + err = app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/dock/README.md b/v3/examples/dock/README.md new file mode 100644 index 000000000..ad12c3f40 --- /dev/null +++ b/v3/examples/dock/README.md @@ -0,0 +1,59 @@ +# Welcome to Your New Wails3 Project! + +Congratulations on generating your Wails3 application! This README will guide you through the next steps to get your project up and running. + +## Getting Started + +1. Navigate to your project directory in the terminal. + +2. To run your application in development mode, use the following command: + + ``` + wails3 dev + ``` + + This will start your application and enable hot-reloading for both frontend and backend changes. + +3. To build your application for production, use: + + ``` + wails3 build + ``` + + This will create a production-ready executable in the `build` directory. + +## Exploring Wails3 Features + +Now that you have your project set up, it's time to explore the features that Wails3 offers: + +1. **Check out the examples**: The best way to learn is by example. Visit the `examples` directory in the `v3/examples` directory to see various sample applications. + +2. **Run an example**: To run any of the examples, navigate to the example's directory and use: + + ``` + go run . + ``` + + Note: Some examples may be under development during the alpha phase. + +3. **Explore the documentation**: Visit the [Wails3 documentation](https://v3.wails.io/) for in-depth guides and API references. + +4. **Join the community**: Have questions or want to share your progress? Join the [Wails Discord](https://discord.gg/JDdSxwjhGf) or visit the [Wails discussions on GitHub](https://github.com/wailsapp/wails/discussions). + +## Project Structure + +Take a moment to familiarize yourself with your project structure: + +- `frontend/`: Contains your frontend code (HTML, CSS, JavaScript/TypeScript) +- `main.go`: The entry point of your Go backend +- `app.go`: Define your application structure and methods here +- `wails.json`: Configuration file for your Wails project + +## Next Steps + +1. Modify the frontend in the `frontend/` directory to create your desired UI. +2. Add backend functionality in `main.go`. +3. Use `wails3 dev` to see your changes in real-time. +4. When ready, build your application with `wails3 build`. + +Happy coding with Wails3! If you encounter any issues or have questions, don't hesitate to consult the documentation or reach out to the Wails community. diff --git a/v3/examples/dock/Taskfile.yml b/v3/examples/dock/Taskfile.yml new file mode 100644 index 000000000..b80b6614a --- /dev/null +++ b/v3/examples/dock/Taskfile.yml @@ -0,0 +1,34 @@ +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + +vars: + APP_NAME: "dock" + BIN_DIR: "bin" + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{OS}}:package" + + run: + summary: Runs the application + cmds: + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} + diff --git a/v3/examples/dock/build/Taskfile.yml b/v3/examples/dock/build/Taskfile.yml new file mode 100644 index 000000000..5f3517efc --- /dev/null +++ b/v3/examples/dock/build/Taskfile.yml @@ -0,0 +1,86 @@ +version: '3' + +tasks: + go:mod:tidy: + summary: Runs `go mod tidy` + internal: true + cmds: + - go mod tidy + + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + label: build:frontend (PRODUCTION={{.PRODUCTION}}) + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/**/* + deps: + - task: install:frontend:deps + - task: generate:bindings + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + cmds: + - npm run {{.BUILD_COMMAND}} -q + env: + PRODUCTION: '{{.PRODUCTION | default "false"}}' + vars: + BUILD_COMMAND: '{{if eq .PRODUCTION "true"}}build{{else}}build:dev{{end}}' + + + generate:bindings: + label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}}) + summary: Generates bindings for the frontend + deps: + - task: go:mod:tidy + sources: + - "**/*.[jt]s" + - exclude: frontend/**/* + - frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output + - "**/*.go" + - go.mod + - go.sum + generates: + - frontend/bindings/**/* + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true -ts + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + sources: + - "appicon.png" + generates: + - "darwin/icons.icns" + - "windows/icon.ico" + cmds: + - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico + + dev:frontend: + summary: Runs the frontend in development mode + dir: frontend + deps: + - task: install:frontend:deps + cmds: + - npm run dev -- --port {{.VITE_PORT}} --strictPort + + update:build-assets: + summary: Updates the build assets + dir: build + cmds: + - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir . diff --git a/v3/examples/dock/build/appicon.png b/v3/examples/dock/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/dock/build/appicon.png differ diff --git a/v3/examples/dock/build/config.yml b/v3/examples/dock/build/config.yml new file mode 100644 index 000000000..aaa3240fb --- /dev/null +++ b/v3/examples/dock/build/config.yml @@ -0,0 +1,63 @@ +# This file contains the configuration for this project. +# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets. +# Note that this will overwrite any changes you have made to the assets. +version: '3' + +# This information is used to generate the build assets. +info: + companyName: "My Company" # The name of the company + productName: "My Product" # The name of the application + productIdentifier: "com.mycompany.myproduct" # The unique product identifier + description: "A program that does X" # The application description + copyright: "(c) 2025, My Company" # Copyright text + comments: "Some Product Comments" # Comments + version: "0.0.1" # The application version + +# Dev mode configuration +dev_mode: + root_path: . + log_level: warn + debounce: 1000 + ignore: + dir: + - .git + - node_modules + - frontend + - bin + file: + - .DS_Store + - .gitignore + - .gitkeep + watched_extension: + - "*.go" + git_ignore: true + executes: + - cmd: wails3 task common:install:frontend:deps + type: once + - cmd: wails3 task common:dev:frontend + type: background + - cmd: go mod tidy + type: blocking + - cmd: wails3 task build + type: blocking + - cmd: wails3 task run + type: primary + +# File Associations +# More information at: https://v3.wails.io/noit/done/yet +fileAssociations: +# - ext: wails +# name: Wails +# description: Wails Application File +# iconName: wailsFileIcon +# role: Editor +# - ext: jpg +# name: JPEG +# description: Image File +# iconName: jpegFileIcon +# role: Editor +# mimeType: image/jpeg # (optional) + +# Other data +other: + - name: My Other Data \ No newline at end of file diff --git a/v3/examples/dock/build/darwin/Info.dev.plist b/v3/examples/dock/build/darwin/Info.dev.plist new file mode 100644 index 000000000..58d7f65e7 --- /dev/null +++ b/v3/examples/dock/build/darwin/Info.dev.plist @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + dock + CFBundleIdentifier + com.wails.dock + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © now, My Company + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/examples/dock/build/darwin/Info.plist b/v3/examples/dock/build/darwin/Info.plist new file mode 100644 index 000000000..aff7a2812 --- /dev/null +++ b/v3/examples/dock/build/darwin/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + dock + CFBundleIdentifier + com.wails.dock + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © now, My Company + + \ No newline at end of file diff --git a/v3/examples/dock/build/darwin/Taskfile.yml b/v3/examples/dock/build/darwin/Taskfile.yml new file mode 100644 index 000000000..f0791fea9 --- /dev/null +++ b/v3/examples/dock/build/darwin/Taskfile.yml @@ -0,0 +1,81 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Creates a production build of the application + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + env: + GOOS: darwin + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + CGO_CFLAGS: "-mmacosx-version-min=10.15" + CGO_LDFLAGS: "-mmacosx-version-min=10.15" + MACOSX_DEPLOYMENT_TARGET: "10.15" + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + build:universal: + summary: Builds darwin universal binary (arm64 + amd64) + deps: + - task: build + vars: + ARCH: amd64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" + - task: build + vars: + ARCH: arm64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + cmds: + - lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + - rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + + package: + summary: Packages a production build of the application into a `.app` bundle + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:app:bundle + + package:universal: + summary: Packages darwin universal binary (arm64 + amd64) + deps: + - task: build:universal + cmds: + - task: create:app:bundle + + + create:app:bundle: + summary: Creates an `.app` bundle + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS + - cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app + + run: + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS + - cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app + - '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}' diff --git a/v3/examples/dock/build/darwin/icons.icns b/v3/examples/dock/build/darwin/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/dock/build/darwin/icons.icns differ diff --git a/v3/examples/dock/build/linux/Taskfile.yml b/v3/examples/dock/build/linux/Taskfile.yml new file mode 100644 index 000000000..560cc9c92 --- /dev/null +++ b/v3/examples/dock/build/linux/Taskfile.yml @@ -0,0 +1,119 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Linux + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: linux + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application for Linux + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:appimage + - task: create:deb + - task: create:rpm + - task: create:aur + + create:appimage: + summary: Creates an AppImage + dir: build/linux/appimage + deps: + - task: build + vars: + PRODUCTION: "true" + - task: generate:dotdesktop + cmds: + - cp {{.APP_BINARY}} {{.APP_NAME}} + - cp ../../appicon.png appicon.png + - wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build + vars: + APP_NAME: '{{.APP_NAME}}' + APP_BINARY: '../../../bin/{{.APP_NAME}}' + ICON: '../../appicon.png' + DESKTOP_FILE: '../{{.APP_NAME}}.desktop' + OUTPUT_DIR: '../../../bin' + + create:deb: + summary: Creates a deb package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:deb + + create:rpm: + summary: Creates a rpm package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:rpm + + create:aur: + summary: Creates a arch linux packager package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:aur + + generate:deb: + summary: Creates a deb package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:rpm: + summary: Creates a rpm package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:aur: + summary: Creates a arch linux packager package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:dotdesktop: + summary: Generates a `.desktop` file + dir: build + cmds: + - mkdir -p {{.ROOT_DIR}}/build/linux/appimage + - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}" + vars: + APP_NAME: '{{.APP_NAME}}' + EXEC: '{{.APP_NAME}}' + ICON: 'appicon' + CATEGORIES: 'Development;' + OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}' diff --git a/v3/examples/dock/build/linux/appimage/build.sh b/v3/examples/dock/build/linux/appimage/build.sh new file mode 100644 index 000000000..85901c34e --- /dev/null +++ b/v3/examples/dock/build/linux/appimage/build.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Copyright (c) 2018-Present Lea Anthony +# SPDX-License-Identifier: MIT + +# Fail script on any error +set -euxo pipefail + +# Define variables +APP_DIR="${APP_NAME}.AppDir" + +# Create AppDir structure +mkdir -p "${APP_DIR}/usr/bin" +cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/" +cp "${ICON_PATH}" "${APP_DIR}/" +cp "${DESKTOP_FILE}" "${APP_DIR}/" + +if [[ $(uname -m) == *x86_64* ]]; then + # Download linuxdeploy and make it executable + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + + # Run linuxdeploy to bundle the application + ./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage +else + # Download linuxdeploy and make it executable (arm64) + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage + chmod +x linuxdeploy-aarch64.AppImage + + # Run linuxdeploy to bundle the application (arm64) + ./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage +fi + +# Rename the generated AppImage +mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage" + diff --git a/v3/examples/dock/build/linux/desktop b/v3/examples/dock/build/linux/desktop new file mode 100644 index 000000000..ddb025903 --- /dev/null +++ b/v3/examples/dock/build/linux/desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Version=1.0 +Name=My Product +Comment=My Product Description +# The Exec line includes %u to pass the URL to the application +Exec=/usr/local/bin/dock %u +Terminal=false +Type=Application +Icon=dock +Categories=Utility; +StartupWMClass=dock + + diff --git a/v3/examples/dock/build/linux/nfpm/nfpm.yaml b/v3/examples/dock/build/linux/nfpm/nfpm.yaml new file mode 100644 index 000000000..6e9be6550 --- /dev/null +++ b/v3/examples/dock/build/linux/nfpm/nfpm.yaml @@ -0,0 +1,73 @@ +# Feel free to remove those if you don't want/need to use them. +# Make sure to check the documentation at https://nfpm.goreleaser.com +# +# The lines below are called `modelines`. See `:help modeline` + +name: "dock" +arch: ${GOARCH} +platform: "linux" +version: "0.1.0" +section: "default" +priority: "extra" +maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> +description: "My Product Description" +vendor: "My Company" +homepage: "https://wails.io" +license: "MIT" +release: "1" + +contents: + - src: "./bin/dock" + dst: "/usr/local/bin/dock" + - src: "./build/appicon.png" + dst: "/usr/share/icons/hicolor/128x128/apps/dock.png" + - src: "./build/linux/dock.desktop" + dst: "/usr/share/applications/dock.desktop" + +# Default dependencies for Debian 12/Ubuntu 22.04+ with WebKit 4.1 +depends: + - libgtk-3-dev + - libwebkit2gtk-4.1-dev + - build-essential + - pkg-config + +# Distribution-specific overrides for different package formats and WebKit versions +overrides: + # RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0) + rpm: + depends: + - gtk3-devel + - webkit2gtk3-devel + - gcc-c++ + - pkg-config + + # Arch Linux packages (WebKit 4.1) + archlinux: + depends: + - gtk3 + - webkit2gtk-4.1 + - base-devel + - pkgconf + +# scripts section to ensure desktop database is updated after install +scripts: + postinstall: "./build/linux/nfpm/scripts/postinstall.sh" + # You can also add preremove, postremove if needed + # preremove: "./build/linux/nfpm/scripts/preremove.sh" + # postremove: "./build/linux/nfpm/scripts/postremove.sh" + +# replaces: +# - foobar +# provides: +# - bar +# depends: +# - gtk3 +# - libwebkit2gtk +# recommends: +# - whatever +# suggests: +# - something-else +# conflicts: +# - not-foo +# - not-bar +# changelog: "changelog.yaml" \ No newline at end of file diff --git a/v3/examples/dock/build/linux/nfpm/scripts/postinstall.sh b/v3/examples/dock/build/linux/nfpm/scripts/postinstall.sh new file mode 100644 index 000000000..4bbb815a3 --- /dev/null +++ b/v3/examples/dock/build/linux/nfpm/scripts/postinstall.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# Update desktop database for .desktop file changes +# This makes the application appear in application menus and registers its capabilities. +if command -v update-desktop-database >/dev/null 2>&1; then + echo "Updating desktop database..." + update-desktop-database -q /usr/share/applications +else + echo "Warning: update-desktop-database command not found. Desktop file may not be immediately recognized." >&2 +fi + +# Update MIME database for custom URL schemes (x-scheme-handler) +# This ensures the system knows how to handle your custom protocols. +if command -v update-mime-database >/dev/null 2>&1; then + echo "Updating MIME database..." + update-mime-database -n /usr/share/mime +else + echo "Warning: update-mime-database command not found. Custom URL schemes may not be immediately recognized." >&2 +fi + +exit 0 diff --git a/v3/examples/dock/build/linux/nfpm/scripts/postremove.sh b/v3/examples/dock/build/linux/nfpm/scripts/postremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/dock/build/linux/nfpm/scripts/postremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/dock/build/linux/nfpm/scripts/preinstall.sh b/v3/examples/dock/build/linux/nfpm/scripts/preinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/dock/build/linux/nfpm/scripts/preinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/dock/build/linux/nfpm/scripts/preremove.sh b/v3/examples/dock/build/linux/nfpm/scripts/preremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/dock/build/linux/nfpm/scripts/preremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/dock/build/windows/Taskfile.yml b/v3/examples/dock/build/windows/Taskfile.yml new file mode 100644 index 000000000..19f137616 --- /dev/null +++ b/v3/examples/dock/build/windows/Taskfile.yml @@ -0,0 +1,98 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Windows + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - task: generate:syso + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe + - cmd: powershell Remove-item *.syso + platforms: [windows] + - cmd: rm -f *.syso + platforms: [linux, darwin] + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: windows + CGO_ENABLED: 0 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application + cmds: + - |- + if [ "{{.FORMAT | default "nsis"}}" = "msix" ]; then + task: create:msix:package + else + task: create:nsis:installer + fi + vars: + FORMAT: '{{.FORMAT | default "nsis"}}' + + generate:syso: + summary: Generates Windows `.syso` file + dir: build + cmds: + - wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso + vars: + ARCH: '{{.ARCH | default ARCH}}' + + create:nsis:installer: + summary: Creates an NSIS installer + dir: build/windows/nsis + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + # Create the Microsoft WebView2 bootstrapper if it doesn't exist + - wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis" + - makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi + vars: + ARCH: '{{.ARCH | default ARCH}}' + ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}' + + create:msix:package: + summary: Creates an MSIX package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - |- + wails3 tool msix \ + --config "{{.ROOT_DIR}}/wails.json" \ + --name "{{.APP_NAME}}" \ + --executable "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" \ + --arch "{{.ARCH}}" \ + --out "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}-{{.ARCH}}.msix" \ + {{if .CERT_PATH}}--cert "{{.CERT_PATH}}"{{end}} \ + {{if .PUBLISHER}}--publisher "{{.PUBLISHER}}"{{end}} \ + {{if .USE_MSIX_TOOL}}--use-msix-tool{{else}}--use-makeappx{{end}} + vars: + ARCH: '{{.ARCH | default ARCH}}' + CERT_PATH: '{{.CERT_PATH | default ""}}' + PUBLISHER: '{{.PUBLISHER | default ""}}' + USE_MSIX_TOOL: '{{.USE_MSIX_TOOL | default "false"}}' + + install:msix:tools: + summary: Installs tools required for MSIX packaging + cmds: + - wails3 tool msix-install-tools + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}.exe' diff --git a/v3/examples/dock/build/windows/icon.ico b/v3/examples/dock/build/windows/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/dock/build/windows/icon.ico differ diff --git a/v3/examples/dock/build/windows/info.json b/v3/examples/dock/build/windows/info.json new file mode 100644 index 000000000..850b2b5b0 --- /dev/null +++ b/v3/examples/dock/build/windows/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "0.1.0" + }, + "info": { + "0000": { + "ProductVersion": "0.1.0", + "CompanyName": "My Company", + "FileDescription": "My Product Description", + "LegalCopyright": "© now, My Company", + "ProductName": "My Product", + "Comments": "This is a comment" + } + } +} \ No newline at end of file diff --git a/v3/examples/dock/build/windows/msix/app_manifest.xml b/v3/examples/dock/build/windows/msix/app_manifest.xml new file mode 100644 index 000000000..8eabe1a03 --- /dev/null +++ b/v3/examples/dock/build/windows/msix/app_manifest.xml @@ -0,0 +1,52 @@ + + + + + + + My Product + My Company + My Product Description + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v3/examples/dock/build/windows/msix/template.xml b/v3/examples/dock/build/windows/msix/template.xml new file mode 100644 index 000000000..2824a2932 --- /dev/null +++ b/v3/examples/dock/build/windows/msix/template.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + false + My Product + My Company + My Product Description + Assets\AppIcon.png + + + + + + + diff --git a/v3/examples/dock/build/windows/nsis/project.nsi b/v3/examples/dock/build/windows/nsis/project.nsi new file mode 100644 index 000000000..b992809bc --- /dev/null +++ b/v3/examples/dock/build/windows/nsis/project.nsi @@ -0,0 +1,112 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the wails_tools.nsh file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "my-project" # Default "dock" +## !define INFO_COMPANYNAME "My Company" # Default "My Company" +## !define INFO_PRODUCTNAME "My Product Name" # Default "My Product" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.1.0" +## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "© now, My Company" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/v3/examples/dock/build/windows/nsis/wails_tools.nsh b/v3/examples/dock/build/windows/nsis/wails_tools.nsh new file mode 100644 index 000000000..b5ff5b23c --- /dev/null +++ b/v3/examples/dock/build/windows/nsis/wails_tools.nsh @@ -0,0 +1,212 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "dock" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "My Company" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "My Product" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "0.1.0" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "© now, My Company" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + +!macroend \ No newline at end of file diff --git a/v3/examples/dock/build/windows/wails.exe.manifest b/v3/examples/dock/build/windows/wails.exe.manifest new file mode 100644 index 000000000..48052e7e6 --- /dev/null +++ b/v3/examples/dock/build/windows/wails.exe.manifest @@ -0,0 +1,22 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + + + + + + + + \ No newline at end of file diff --git a/v3/examples/dock/frontend/Inter Font License.txt b/v3/examples/dock/frontend/Inter Font License.txt new file mode 100644 index 000000000..b525cbf3a --- /dev/null +++ b/v3/examples/dock/frontend/Inter Font License.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/v3/examples/dock/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/dockservice.ts b/v3/examples/dock/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/dockservice.ts new file mode 100644 index 000000000..abf1f22e4 --- /dev/null +++ b/v3/examples/dock/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/dockservice.ts @@ -0,0 +1,53 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Service represents the dock service + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * HideAppIcon hides the app icon in the dock/taskbar. + */ +export function HideAppIcon(): $CancellablePromise { + return $Call.ByID(3413658144); +} + +/** + * RemoveBadge removes the badge label from the application icon. + * This method ensures the badge call is made on the main thread to avoid crashes. + */ +export function RemoveBadge(): $CancellablePromise { + return $Call.ByID(2752757297); +} + +/** + * SetBadge sets the badge label on the application icon. + * This method ensures the badge call is made on the main thread to avoid crashes. + */ +export function SetBadge(label: string): $CancellablePromise { + return $Call.ByID(1717705661, label); +} + +/** + * SetCustomBadge sets the badge label on the application icon with custom options. + * This method ensures the badge call is made on the main thread to avoid crashes. + */ +export function SetCustomBadge(label: string, options: $models.BadgeOptions): $CancellablePromise { + return $Call.ByID(2730169760, label, options); +} + +/** + * ShowAppIcon shows the app icon in the dock/taskbar. + */ +export function ShowAppIcon(): $CancellablePromise { + return $Call.ByID(3409697379); +} diff --git a/v3/examples/dock/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/index.ts b/v3/examples/dock/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/index.ts new file mode 100644 index 000000000..fbdaf19f3 --- /dev/null +++ b/v3/examples/dock/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as DockService from "./dockservice.js"; +export { + DockService +}; + +export { + BadgeOptions +} from "./models.js"; diff --git a/v3/examples/dock/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/models.ts b/v3/examples/dock/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/models.ts new file mode 100644 index 000000000..f97c8a4c6 --- /dev/null +++ b/v3/examples/dock/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/dock/models.ts @@ -0,0 +1,61 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as color$0 from "../../../../../../../image/color/models.js"; + +/** + * BadgeOptions represents options for customizing badge appearance + */ +export class BadgeOptions { + "TextColour": color$0.RGBA; + "BackgroundColour": color$0.RGBA; + "FontName": string; + "FontSize": number; + "SmallFontSize": number; + + /** Creates a new BadgeOptions instance. */ + constructor($$source: Partial = {}) { + if (!("TextColour" in $$source)) { + this["TextColour"] = (new color$0.RGBA()); + } + if (!("BackgroundColour" in $$source)) { + this["BackgroundColour"] = (new color$0.RGBA()); + } + if (!("FontName" in $$source)) { + this["FontName"] = ""; + } + if (!("FontSize" in $$source)) { + this["FontSize"] = 0; + } + if (!("SmallFontSize" in $$source)) { + this["SmallFontSize"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new BadgeOptions instance from a string or object. + */ + static createFrom($$source: any = {}): BadgeOptions { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("TextColour" in $$parsedSource) { + $$parsedSource["TextColour"] = $$createField0_0($$parsedSource["TextColour"]); + } + if ("BackgroundColour" in $$parsedSource) { + $$parsedSource["BackgroundColour"] = $$createField1_0($$parsedSource["BackgroundColour"]); + } + return new BadgeOptions($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = color$0.RGBA.createFrom; diff --git a/v3/examples/dock/frontend/bindings/image/color/index.ts b/v3/examples/dock/frontend/bindings/image/color/index.ts new file mode 100644 index 000000000..97b507b08 --- /dev/null +++ b/v3/examples/dock/frontend/bindings/image/color/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + RGBA +} from "./models.js"; diff --git a/v3/examples/dock/frontend/bindings/image/color/models.ts b/v3/examples/dock/frontend/bindings/image/color/models.ts new file mode 100644 index 000000000..0d4eab56d --- /dev/null +++ b/v3/examples/dock/frontend/bindings/image/color/models.ts @@ -0,0 +1,46 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +/** + * RGBA represents a traditional 32-bit alpha-premultiplied color, having 8 + * bits for each of red, green, blue and alpha. + * + * An alpha-premultiplied color component C has been scaled by alpha (A), so + * has valid values 0 <= C <= A. + */ +export class RGBA { + "R": number; + "G": number; + "B": number; + "A": number; + + /** Creates a new RGBA instance. */ + constructor($$source: Partial = {}) { + if (!("R" in $$source)) { + this["R"] = 0; + } + if (!("G" in $$source)) { + this["G"] = 0; + } + if (!("B" in $$source)) { + this["B"] = 0; + } + if (!("A" in $$source)) { + this["A"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new RGBA instance from a string or object. + */ + static createFrom($$source: any = {}): RGBA { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new RGBA($$parsedSource as Partial); + } +} diff --git a/v3/examples/dock/frontend/dist/Inter-Medium.ttf b/v3/examples/dock/frontend/dist/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/dock/frontend/dist/Inter-Medium.ttf differ diff --git a/v3/examples/dock/frontend/dist/assets/index-BJIh-HIc.js b/v3/examples/dock/frontend/dist/assets/index-BJIh-HIc.js new file mode 100644 index 000000000..5217ab9a7 --- /dev/null +++ b/v3/examples/dock/frontend/dist/assets/index-BJIh-HIc.js @@ -0,0 +1,6 @@ +(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))n(o);new MutationObserver(o=>{for(const i of o)if(i.type==="childList")for(const s of i.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&n(s)}).observe(document,{childList:!0,subtree:!0});function r(o){const i={};return o.integrity&&(i.integrity=o.integrity),o.referrerPolicy&&(i.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?i.credentials="include":o.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function n(o){if(o.ep)return;o.ep=!0;const i=r(o);fetch(o.href,i)}})();const ie="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";function K(t=21){let e="",r=t|0;for(;r--;)e+=ie[Math.random()*64|0];return e}const se=window.location.origin+"/wails/runtime",O=Object.freeze({Call:0,Clipboard:1,Application:2,Events:3,ContextMenu:4,Dialog:5,Window:6,Screens:7,System:8,Browser:9,CancelCall:10});let ce=K();function R(t,e=""){return function(r,n=null){return le(t,r,e,n)}}async function le(t,e,r,n){var o,i;let s=new URL(se);s.searchParams.append("object",t.toString()),s.searchParams.append("method",e.toString()),n&&s.searchParams.append("args",JSON.stringify(n));let l={"x-wails-client-id":ce};r&&(l["x-wails-window-name"]=r);let c=await fetch(s,{headers:l});if(!c.ok)throw new Error(await c.text());return((i=(o=c.headers.get("Content-Type"))===null||o===void 0?void 0:o.indexOf("application/json"))!==null&&i!==void 0?i:-1)!==-1?c.json():c.text()}R(O.System);const P=function(){var t,e,r,n,o;try{if(!((e=(t=window.chrome)===null||t===void 0?void 0:t.webview)===null||e===void 0)&&e.postMessage)return window.chrome.webview.postMessage.bind(window.chrome.webview);if(!((o=(n=(r=window.webkit)===null||r===void 0?void 0:r.messageHandlers)===null||n===void 0?void 0:n.external)===null||o===void 0)&&o.postMessage)return window.webkit.messageHandlers.external.postMessage.bind(window.webkit.messageHandlers.external)}catch{}return console.warn(` +%c⚠️ Browser Environment Detected %c + +%cOnly UI previews are available in the browser. For full functionality, please run the application in desktop mode. +More information at: https://v3.wails.io/learn/build/#using-a-browser-for-development +`,"background: #ffffff; color: #000000; font-weight: bold; padding: 4px 8px; border-radius: 4px; border: 2px solid #000000;","background: transparent;","color: #ffffff; font-style: italic; font-weight: bold;"),null}();function M(t){P==null||P(t)}function Q(){return window._wails.environment.OS==="windows"}function ae(){return!!window._wails.environment.Debug}function ue(){return new MouseEvent("mousedown").buttons===0}function Z(t){var e;return t.target instanceof HTMLElement?t.target:!(t.target instanceof HTMLElement)&&t.target instanceof Node&&(e=t.target.parentElement)!==null&&e!==void 0?e:document.body}document.addEventListener("DOMContentLoaded",()=>{});window.addEventListener("contextmenu",pe);const de=R(O.ContextMenu),fe=0;function we(t,e,r,n){de(fe,{id:t,x:e,y:r,data:n})}function pe(t){const e=Z(t),r=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu").trim();if(r){t.preventDefault();const n=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu-data");we(r,t.clientX,t.clientY,n)}else he(t,e)}function he(t,e){if(ae())return;switch(window.getComputedStyle(e).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":t.preventDefault();return}if(e.isContentEditable)return;const r=window.getSelection(),n=r&&r.toString().length>0;if(n)for(let o=0;o{F=t,F||(b=v=!1,u())};window.addEventListener("mousedown",N,{capture:!0});window.addEventListener("mousemove",N,{capture:!0});window.addEventListener("mouseup",N,{capture:!0});for(const t of["click","contextmenu","dblclick"])window.addEventListener(t,me,{capture:!0});function me(t){(E||v)&&(t.stopImmediatePropagation(),t.stopPropagation(),t.preventDefault())}const D=0,ye=1,k=2;function N(t){let e,r=t.buttons;switch(t.type){case"mousedown":e=D,H||(r=h|1<"u"||typeof e=="object"))try{var r=C.call(e);return(r===Ce||r===Me||r===Oe||r===Se)&&e("")==null}catch{}return!1})}function Pe(t){if(X(t))return!0;if(!t||typeof t!="function"&&typeof t!="object")return!1;try{y(t,null,B)}catch(e){if(e!==x)return!1}return!W(t)&&_(t)}function Ae(t){if(X(t))return!0;if(!t||typeof t!="function"&&typeof t!="object")return!1;if(Re)return _(t);if(W(t))return!1;var e=C.call(t);return e!==ze&&e!==xe&&!/^\[object HTML/.test(e)?!1:_(t)}const p=y?Pe:Ae;var I;class U extends Error{constructor(e,r){super(e,r),this.name="CancelError"}}class S extends Error{constructor(e,r,n){super((n??"Unhandled rejection in cancelled promise.")+" Reason: "+He(r),{cause:r}),this.promise=e,this.name="CancelledRejectionError"}}const f=Symbol("barrier"),J=Symbol("cancelImpl"),V=(I=Symbol.species)!==null&&I!==void 0?I:Symbol("speciesPolyfill");class a extends Promise{constructor(e,r){let n,o;if(super((c,d)=>{n=c,o=d}),this.constructor[V]!==Promise)throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property.");let i={promise:this,resolve:n,reject:o,get oncancelled(){return r??null},set oncancelled(c){r=c??void 0}};const s={get root(){return s},resolving:!1,settled:!1};Object.defineProperties(this,{[f]:{configurable:!1,enumerable:!1,writable:!0,value:null},[J]:{configurable:!1,enumerable:!1,writable:!1,value:ee(i,s)}});const l=re(i,s);try{e(te(i,s),l)}catch(c){s.resolving?console.log("Unhandled exception in CancellablePromise executor.",c):l(c)}}cancel(e){return new a(r=>{Promise.all([this[J](new U("Promise cancelled.",{cause:e})),De(this)]).then(()=>r(),()=>r())})}cancelOn(e){return e.aborted?this.cancel(e.reason):e.addEventListener("abort",()=>void this.cancel(e.reason),{capture:!0}),this}then(e,r,n){if(!(this instanceof a))throw new TypeError("CancellablePromise.prototype.then called on an invalid object.");if(p(e)||(e=q),p(r)||(r=G),e===q&&r==G)return new a(i=>i(this));const o={};return this[f]=o,new a((i,s)=>{super.then(l=>{var c;this[f]===o&&(this[f]=null),(c=o.resolve)===null||c===void 0||c.call(o);try{i(e(l))}catch(d){s(d)}},l=>{var c;this[f]===o&&(this[f]=null),(c=o.resolve)===null||c===void 0||c.call(o);try{i(r(l))}catch(d){s(d)}})},async i=>{try{return n==null?void 0:n(i)}finally{await this.cancel(i)}})}catch(e,r){return this.then(void 0,e,r)}finally(e,r){if(!(this instanceof a))throw new TypeError("CancellablePromise.prototype.finally called on an invalid object.");return p(e)?this.then(n=>a.resolve(e()).then(()=>n),n=>a.resolve(e()).then(()=>{throw n}),r):this.then(e,e,r)}static get[V](){return Promise}static all(e){let r=Array.from(e);const n=r.length===0?a.resolve(r):new a((o,i)=>{Promise.all(r).then(o,i)},o=>z(n,r,o));return n}static allSettled(e){let r=Array.from(e);const n=r.length===0?a.resolve(r):new a((o,i)=>{Promise.allSettled(r).then(o,i)},o=>z(n,r,o));return n}static any(e){let r=Array.from(e);const n=r.length===0?a.resolve(r):new a((o,i)=>{Promise.any(r).then(o,i)},o=>z(n,r,o));return n}static race(e){let r=Array.from(e);const n=new a((o,i)=>{Promise.race(r).then(o,i)},o=>z(n,r,o));return n}static cancel(e){const r=new a(()=>{});return r.cancel(e),r}static timeout(e,r){const n=new a(()=>{});return AbortSignal&&typeof AbortSignal=="function"&&AbortSignal.timeout&&typeof AbortSignal.timeout=="function"?AbortSignal.timeout(e).addEventListener("abort",()=>void n.cancel(r)):setTimeout(()=>void n.cancel(r),e),n}static sleep(e,r){return new a(n=>{setTimeout(()=>n(r),e)})}static reject(e){return new a((r,n)=>n(e))}static resolve(e){return e instanceof a?e:new a(r=>r(e))}static withResolvers(){let e={oncancelled:null};return e.promise=new a((r,n)=>{e.resolve=r,e.reject=n},r=>{var n;(n=e.oncancelled)===null||n===void 0||n.call(e,r)}),e}}function ee(t,e){let r;return n=>{if(e.settled||(e.settled=!0,e.reason=n,t.reject(n),Promise.prototype.then.call(t.promise,void 0,o=>{if(o!==n)throw o})),!(!e.reason||!t.oncancelled))return r=new Promise(o=>{try{o(t.oncancelled(e.reason.cause))}catch(i){Promise.reject(new S(t.promise,i,"Unhandled exception in oncancelled callback."))}}).catch(o=>{Promise.reject(new S(t.promise,o,"Unhandled rejection in oncancelled callback."))}),t.oncancelled=null,r}}function te(t,e){return r=>{if(!e.resolving){if(e.resolving=!0,r===t.promise){if(e.settled)return;e.settled=!0,t.reject(new TypeError("A promise cannot be resolved with itself."));return}if(r!=null&&(typeof r=="object"||typeof r=="function")){let n;try{n=r.then}catch(o){e.settled=!0,t.reject(o);return}if(p(n)){try{let s=r.cancel;if(p(s)){const l=c=>{Reflect.apply(s,r,[c])};e.reason?ee(Object.assign(Object.assign({},t),{oncancelled:l}),e)(e.reason):t.oncancelled=l}}catch{}const o={root:e.root,resolving:!1,get settled(){return this.root.settled},set settled(s){this.root.settled=s},get reason(){return this.root.reason}},i=re(t,o);try{Reflect.apply(n,r,[te(t,o),i])}catch(s){i(s)}return}}e.settled||(e.settled=!0,t.resolve(r))}}}function re(t,e){return r=>{if(!e.resolving)if(e.resolving=!0,e.settled){try{if(r instanceof U&&e.reason instanceof U&&Object.is(r.cause,e.reason.cause))return}catch{}Promise.reject(new S(t.promise,r))}else e.settled=!0,t.reject(r)}}function z(t,e,r){const n=[];for(const o of e){let i;try{if(!p(o.then)||(i=o.cancel,!p(i)))continue}catch{continue}let s;try{s=Reflect.apply(i,o,[r])}catch(l){Promise.reject(new S(t,l,"Unhandled exception in cancel method."));continue}s&&n.push((s instanceof Promise?s:Promise.resolve(s)).catch(l=>{Promise.reject(new S(t,l,"Unhandled rejection in cancel method."))}))}return Promise.all(n)}function q(t){return t}function G(t){throw t}function He(t){try{if(t instanceof Error||typeof t!="object"||t.toString!==Object.prototype.toString)return""+t}catch{}try{return JSON.stringify(t)}catch{}try{return Object.prototype.toString.call(t)}catch{}return""}function De(t){var e;let r=(e=t[f])!==null&&e!==void 0?e:{};return"promise"in r||Object.assign(r,m()),t[f]==null&&(r.resolve(),t[f]=r),r.promise}let m=Promise.withResolvers;m&&typeof m=="function"?m=m.bind(Promise):m=function(){let t,e;return{promise:new Promise((n,o)=>{t=n,e=o}),resolve:t,reject:e}};window._wails=window._wails||{};window._wails.callResultHandler=Ue;window._wails.callErrorHandler=Ne;const ke=R(O.Call),Ie=R(O.CancelCall),g=new Map,Fe=0,Be=0;class _e extends Error{constructor(e,r){super(e,r),this.name="RuntimeError"}}function Ue(t,e,r){const n=ne(t);if(n)if(!e)n.resolve(void 0);else if(!r)n.resolve(e);else try{n.resolve(JSON.parse(e))}catch(o){n.reject(new TypeError("could not parse result: "+o.message,{cause:o}))}}function Ne(t,e,r){const n=ne(t);if(n)if(!r)n.reject(new Error(e));else{let o;try{o=JSON.parse(e)}catch(l){n.reject(new TypeError("could not parse error: "+l.message,{cause:l}));return}let i={};o.cause&&(i.cause=o.cause);let s;switch(o.kind){case"ReferenceError":s=new ReferenceError(o.message,i);break;case"TypeError":s=new TypeError(o.message,i);break;case"RuntimeError":s=new _e(o.message,i);break;default:s=new Error(o.message,i);break}n.reject(s)}}function ne(t){const e=g.get(t);return g.delete(t),e}function We(){let t;do t=K();while(g.has(t));return t}function Xe(t){const e=We(),r=a.withResolvers();g.set(e,{resolve:r.resolve,reject:r.reject});const n=ke(Fe,Object.assign({"call-id":e},t));let o=!1;n.then(()=>{o=!0},s=>{g.delete(e),r.reject(s)});const i=()=>(g.delete(e),Ie(Be,{"call-id":e}).catch(s=>{console.error("Error while requesting binding call cancellation:",s)}));return r.oncancelled=()=>o?i():n.then(i),r.promise}function oe(t,...e){return Xe({methodID:t,args:e})}window._wails=window._wails||{};window._wails.invoke=M;M("wails:runtime:ready");function Ye(){return oe(3413658144)}function Je(){return oe(3409697379)}const Ve=document.getElementById("show"),qe=document.getElementById("hide");Ve.addEventListener("click",()=>{Je()});qe.addEventListener("click",()=>{Ye()}); diff --git a/v3/examples/dock/frontend/dist/index.html b/v3/examples/dock/frontend/dist/index.html new file mode 100644 index 000000000..1836ad6e7 --- /dev/null +++ b/v3/examples/dock/frontend/dist/index.html @@ -0,0 +1,33 @@ + + + + + + + + Wails App + + + +
+ +

Wails + Typescript

+
+
+ + +
+
+ +
+ + diff --git a/v3/examples/dock/frontend/dist/style.css b/v3/examples/dock/frontend/dist/style.css new file mode 100644 index 000000000..0b9c58279 --- /dev/null +++ b/v3/examples/dock/frontend/dist/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/examples/dock/frontend/dist/typescript.svg b/v3/examples/dock/frontend/dist/typescript.svg new file mode 100644 index 000000000..d91c910cc --- /dev/null +++ b/v3/examples/dock/frontend/dist/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/dock/frontend/dist/wails.png b/v3/examples/dock/frontend/dist/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/dock/frontend/dist/wails.png differ diff --git a/v3/examples/dock/frontend/index.html b/v3/examples/dock/frontend/index.html new file mode 100644 index 000000000..1a310f0e9 --- /dev/null +++ b/v3/examples/dock/frontend/index.html @@ -0,0 +1,33 @@ + + + + + + + + Wails App + + +
+ +

Wails + Typescript

+
+
+ + +
+
+ +
+ + + diff --git a/v3/examples/dock/frontend/package-lock.json b/v3/examples/dock/frontend/package-lock.json new file mode 100644 index 000000000..0c0df727a --- /dev/null +++ b/v3/examples/dock/frontend/package-lock.json @@ -0,0 +1,936 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "typescript": "^4.9.3", + "vite": "^5.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@wailsio/runtime": { + "version": "3.0.0-alpha.66", + "resolved": "https://registry.npmjs.org/@wailsio/runtime/-/runtime-3.0.0-alpha.66.tgz", + "integrity": "sha512-ENLu8rn1griL1gFHJqkq1i+BVxrrA0JPJHYneUJYuf/s54kjuQViW0RKDEe/WTDo56ABpfykrd/T8OYpPUyXUw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/v3/examples/dock/frontend/package.json b/v3/examples/dock/frontend/package.json new file mode 100644 index 000000000..b39da7ece --- /dev/null +++ b/v3/examples/dock/frontend/package.json @@ -0,0 +1,19 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "tsc && vite build --minify false --mode development", + "build": "tsc && vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "typescript": "^4.9.3", + "vite": "^5.0.0" + } +} diff --git a/v3/examples/dock/frontend/public/Inter-Medium.ttf b/v3/examples/dock/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/dock/frontend/public/Inter-Medium.ttf differ diff --git a/v3/examples/dock/frontend/public/style.css b/v3/examples/dock/frontend/public/style.css new file mode 100644 index 000000000..0b9c58279 --- /dev/null +++ b/v3/examples/dock/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/examples/dock/frontend/public/typescript.svg b/v3/examples/dock/frontend/public/typescript.svg new file mode 100644 index 000000000..d91c910cc --- /dev/null +++ b/v3/examples/dock/frontend/public/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/dock/frontend/public/wails.png b/v3/examples/dock/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/dock/frontend/public/wails.png differ diff --git a/v3/examples/dock/frontend/src/main.ts b/v3/examples/dock/frontend/src/main.ts new file mode 100644 index 000000000..6ef5af25f --- /dev/null +++ b/v3/examples/dock/frontend/src/main.ts @@ -0,0 +1,12 @@ +import { DockService } from "../bindings/github.com/wailsapp/wails/v3/pkg/services/dock" + +const showButton = document.getElementById('show')! as HTMLButtonElement; +const hideButton = document.getElementById('hide')! as HTMLButtonElement; + +showButton.addEventListener('click', () => { + DockService.ShowAppIcon(); +}); + +hideButton.addEventListener('click', () => { + DockService.HideAppIcon(); +}); \ No newline at end of file diff --git a/v3/examples/dock/frontend/src/vite-env.d.ts b/v3/examples/dock/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/examples/dock/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/examples/dock/frontend/tsconfig.json b/v3/examples/dock/frontend/tsconfig.json new file mode 100644 index 000000000..c267ecf24 --- /dev/null +++ b/v3/examples/dock/frontend/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "noImplicitReturns": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/v3/examples/dock/main.go b/v3/examples/dock/main.go new file mode 100644 index 000000000..446645448 --- /dev/null +++ b/v3/examples/dock/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/services/dock" +) + +// Wails uses Go's `embed` package to embed the frontend files into the binary. +// Any files in the frontend/dist folder will be embedded into the binary and +// made available to the frontend. +// See https://pkg.go.dev/embed for more information. + +//go:embed all:frontend/dist +var assets embed.FS + +// main function serves as the application's entry point. It initializes the application, creates a window, +// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and +// logs any error that might occur. +func main() { + + // Create a new Wails application by providing the necessary options. + // Variables 'Name' and 'Description' are for application metadata. + // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files. + // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances. + // 'Mac' options tailor the application when running an macOS. + + dockService := dock.New() + + app := application.New(application.Options{ + Name: "dock", + Description: "A demo of using raw HTML & CSS", + Services: []application.Service{ + application.NewService(dockService), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a new window with the necessary options. + // 'Title' is the title of the window. + // 'Mac' options tailor the window when running on macOS. + // 'BackgroundColour' is the background colour of the window. + // 'URL' is the URL that will be loaded into the webview. + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + BackgroundColour: application.NewRGB(27, 38, 54), + URL: "/", + }) + + // Run the application. This blocks until the application has been exited. + err := app.Run() + + // If an error occurred while running the application, log it and exit. + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/drag-n-drop/README.md b/v3/examples/drag-n-drop/README.md new file mode 100644 index 000000000..a2cde8844 --- /dev/null +++ b/v3/examples/drag-n-drop/README.md @@ -0,0 +1,75 @@ +# File Drop Example + +This example demonstrates how to handle files being dragged from the operating system (Finder, Explorer, file managers) into a Wails application. + +Dropped files are automatically categorised by type and displayed in separate buckets: documents, images, or other files. + +## How it works + +1. Enable file drops in window options: + ```go + EnableFileDrop: true + ``` + +2. Mark elements as drop targets in HTML: + ```html +
Drop files here
+ ``` + +3. Listen for the `WindowFilesDropped` event: + ```go + win.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { + files := event.Context().DroppedFiles() + details := event.Context().DropTargetDetails() + // Handle the dropped files + }) + ``` + +4. Optionally forward to frontend: + ```go + application.Get().Event.Emit("files-dropped", map[string]any{ + "files": files, + "details": details, + }) + ``` + +## Drop Target Details + +When files are dropped, you can get information about the drop location: + +- `ElementID` - The ID of the element that received the drop +- `ClassList` - CSS classes on the drop target +- `X`, `Y` - Coordinates of the drop within the element + +## Styling + +When files are dragged over a valid drop target, Wails adds the `file-drop-target-active` class: + +```css +.file-drop-target-active { + border-color: #4a9eff; + background: rgba(74, 158, 255, 0.1); +} +``` + +## Running the example + +```bash +go run main.go +``` + +Then drag files from your desktop or file manager into the drop zone. + +## HTML5 Drag and Drop API + +This example also includes a demonstration for dragging elements *within* your application via the HTML5 Drag and Drop API. + +Scroll down to the `Internal Drag and Drop` section within the launched application to interact with the demo. + +## Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | Working | diff --git a/v3/examples/drag-n-drop/assets/index.html b/v3/examples/drag-n-drop/assets/index.html new file mode 100644 index 000000000..53af94212 --- /dev/null +++ b/v3/examples/drag-n-drop/assets/index.html @@ -0,0 +1,435 @@ + + + + + + Drag and Drop Demo + + + +

Drag and Drop Demo

+ + +

External File Drop

+
+

+ Drop files from your operating system (Finder, Explorer, file managers). + Uses EnableFileDrop: true and data-file-drop-target +

+
+ +
+
+

Drop files from your desktop or file manager here

+
+ +
+
+

Documents

+
    +
  • No documents yet
  • +
+
+ +
+

Images

+
    +
  • No images yet
  • +
+
+ +
+

Other Files

+
    +
  • No other files yet
  • +
+
+
+
+ + +

Internal Drag and Drop

+
+

+ Drag items between zones using the HTML5 Drag and Drop API. + Uses draggable="true" and standard DOM events. +

+
+ +
+
+
+

Tasks

+
Fix login bug
+
Update documentation
+
Add dark mode
+
Refactor API calls
+
Write unit tests
+
+ +
+
+

High Priority

+
    +
    + +
    +

    Medium Priority

    +
      +
      + +
      +

      Low Priority

      +
        +
        +
        +
        +
        + +
        + Last action: No actions yet +
        + + + + diff --git a/v3/examples/drag-n-drop/main.go b/v3/examples/drag-n-drop/main.go new file mode 100644 index 000000000..5caafef1e --- /dev/null +++ b/v3/examples/drag-n-drop/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +//go:embed assets +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "File Drop Demo", + Description: "A demo of file drag and drop", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + win := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "File Drop Demo", + Width: 800, + Height: 600, + EnableFileDrop: true, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + // Listen for file drop events + win.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { + files := event.Context().DroppedFiles() + details := event.Context().DropTargetDetails() + + log.Printf("Files dropped: %v", files) + if details != nil { + log.Printf("Drop target: id=%s, classes=%v, x=%d, y=%d", + details.ElementID, details.ClassList, details.X, details.Y) + } + + // Emit event to frontend + application.Get().Event.Emit("files-dropped", map[string]any{ + "files": files, + "details": details, + }) + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/environment/README.md b/v3/examples/environment/README.md new file mode 100644 index 000000000..1f922f341 --- /dev/null +++ b/v3/examples/environment/README.md @@ -0,0 +1,19 @@ +# Screen Example + +This example will detect all attached screens and display their details. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/environment/assets/index.html b/v3/examples/environment/assets/index.html new file mode 100644 index 000000000..32bb5ebba --- /dev/null +++ b/v3/examples/environment/assets/index.html @@ -0,0 +1,66 @@ + + + + + Screens Demo + + + + + + + diff --git a/v3/examples/environment/main.go b/v3/examples/environment/main.go new file mode 100644 index 000000000..76822aaa9 --- /dev/null +++ b/v3/examples/environment/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Environment Demo", + Description: "A demo of the Environment API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Environment Demo", + Width: 800, + Height: 600, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/events-bug/main.go b/v3/examples/events-bug/main.go new file mode 100644 index 000000000..f3bf88554 --- /dev/null +++ b/v3/examples/events-bug/main.go @@ -0,0 +1,60 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "log" +) + +var app *application.App + +func main() { + app = application.New(application.Options{ + Name: "Key Bindings Demo", + Description: "A demo of the Key Bindings Options", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + KeyBindings: map[string]func(window application.Window){ + "shift+ctrl+c": func(window application.Window) { + selection, err := app.Dialog.OpenFile(). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + PromptForMultipleSelection() + if err != nil { + println(err.Error()) + } + println(selection) + }, + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "Window 1", + Title: "Window 1", + URL: "https://wails.io", + KeyBindings: map[string]func(window application.Window){ + "F12": func(window application.Window) { + window.OpenDevTools() + }, + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "Window 2", + Title: "Window 2", + URL: "https://google.com", + KeyBindings: map[string]func(window application.Window){ + "F12": func(window application.Window) { + println("Window 2: Toggle Dev Tools") + }, + }, + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/events/README.md b/v3/examples/events/README.md new file mode 100644 index 000000000..596540f5a --- /dev/null +++ b/v3/examples/events/README.md @@ -0,0 +1,25 @@ +# Events Example + +This example is a demonstration of using the new events API. +It has 2 windows that can emit events from the frontend and the backend emits an event every 10 seconds. +All events emitted are logged either to the console or the window. + +It also demonstrates the use of `RegisterHook` to register a function to be called when an event is emitted. +For one window, it captures the `WindowClosing` event and prevents the window from closing twice. +The other window uses both hooks and events to show the window is gaining focus. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run main.go +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | | +| Windows | Working | +| Linux | | \ No newline at end of file diff --git a/v3/examples/events/assets/index.html b/v3/examples/events/assets/index.html new file mode 100644 index 000000000..069baabdb --- /dev/null +++ b/v3/examples/events/assets/index.html @@ -0,0 +1,29 @@ + + + + + Title + + + +

        Events Demo

        +
        +The main program emits an event every 10s which will be displayed in the section below. +To send an event from this window, click here: +
        + + + + + diff --git a/v3/examples/events/main.go b/v3/examples/events/main.go new file mode 100644 index 000000000..6f3d71be5 --- /dev/null +++ b/v3/examples/events/main.go @@ -0,0 +1,129 @@ +package main + +import ( + "embed" + _ "embed" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +//go:embed assets +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "customEventProcessor Demo", + Description: "A demo of the customEventProcessor API", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Custom event handling + app.Event.On("myevent", func(e *application.CustomEvent) { + app.Logger.Info("[Go] CustomEvent received", "name", e.Name, "data", e.Data, "sender", e.Sender, "cancelled", e.IsCancelled()) + }) + + // OS specific application events + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(event *application.ApplicationEvent) { + go func() { + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + // This emits a custom event every 10 seconds + // As it's sent from the application, the sender will be blank + app.Event.Emit("myevent", "hello") + case <-app.Context().Done(): + return + } + } + }() + }) + + app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) { + app.Logger.Info("System theme changed!") + if event.Context().IsDarkMode() { + app.Logger.Info("System is now using dark mode!") + } else { + app.Logger.Info("System is now using light mode!") + } + }) + + // Platform agnostic events + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(event *application.ApplicationEvent) { + app.Logger.Info("events.Common.ApplicationStarted fired!") + }) + + win1 := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Name: "Window 1", + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + var countdown = 3 + + win1.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + countdown-- + if countdown == 0 { + app.Logger.Info("Window 1 Closing!") + return + } + app.Logger.Info("Window 1 Closing? Nope! Not closing!") + e.Cancel() + }) + + win2 := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 2", + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + go func() { + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + win2.EmitEvent("windowevent", "ooooh!") + case <-app.Context().Done(): + return + } + } + }() + + var cancel bool + + win2.RegisterHook(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("[Hook] Window focus!") + cancel = !cancel + if cancel { + e.Cancel() + } + }) + + win2.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("[OnWindowEvent] Window focus!") + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/file-association/.gitignore b/v3/examples/file-association/.gitignore new file mode 100644 index 000000000..4b51c175c --- /dev/null +++ b/v3/examples/file-association/.gitignore @@ -0,0 +1,3 @@ +.task +bin +wails.syso \ No newline at end of file diff --git a/v3/examples/file-association/Inter Font License.txt b/v3/examples/file-association/Inter Font License.txt new file mode 100644 index 000000000..b525cbf3a --- /dev/null +++ b/v3/examples/file-association/Inter Font License.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/v3/examples/file-association/README.md b/v3/examples/file-association/README.md new file mode 100644 index 000000000..0aa08023f --- /dev/null +++ b/v3/examples/file-association/README.md @@ -0,0 +1,11 @@ +# File Association Sample Project + +This sample project demonstrates how to associate a file type with an application. +More info at: https://v3.wails.io/learn/guides/file-associations/ + +To run the sample, follow these steps: + +1. Run `wails3 package` to generate the package. +2. On Windows, run the installer that was built in the `bin` directory. +3. Double-click on the `test.wails` file to open it with the application. +4. On macOS, double-click on the `test.wails` file and select the built application. \ No newline at end of file diff --git a/v3/examples/file-association/Taskfile.yml b/v3/examples/file-association/Taskfile.yml new file mode 100644 index 000000000..4ec68e23f --- /dev/null +++ b/v3/examples/file-association/Taskfile.yml @@ -0,0 +1,54 @@ +version: '3' + +includes: + common: ./build/Taskfile.common.yml + windows: ./build/Taskfile.windows.yml + darwin: ./build/Taskfile.darwin.yml + linux: ./build/Taskfile.linux.yml + +vars: + APP_NAME: "fileassoc" + BIN_DIR: "bin" + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{OS}}:package" + + run: + summary: Runs the application + cmds: + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} + + darwin:build:universal: + summary: Builds darwin universal binary (arm64 + amd64) + cmds: + - task: darwin:build + vars: + ARCH: amd64 + - mv {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}-amd64 + - task: darwin:build + vars: + ARCH: arm64 + - mv {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}-arm64 + - lipo -create -output {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}-amd64 {{.BIN_DIR}}/{{.APP_NAME}}-arm64 + - rm {{.BIN_DIR}}/{{.APP_NAME}}-amd64 {{.BIN_DIR}}/{{.APP_NAME}}-arm64 + + darwin:package:universal: + summary: Packages darwin universal binary (arm64 + amd64) + deps: + - darwin:build:universal + cmds: + - task: darwin:create:app:bundle diff --git a/v3/examples/file-association/build/Info.dev.plist b/v3/examples/file-association/build/Info.dev.plist new file mode 100644 index 000000000..327c94603 --- /dev/null +++ b/v3/examples/file-association/build/Info.dev.plist @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + fileassoc + CFBundleIdentifier + + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © now, My Company + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/examples/file-association/build/Info.plist b/v3/examples/file-association/build/Info.plist new file mode 100644 index 000000000..1b3520754 --- /dev/null +++ b/v3/examples/file-association/build/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + fileassoc + CFBundleIdentifier + + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © now, My Company + + \ No newline at end of file diff --git a/v3/examples/file-association/build/Taskfile.common.yml b/v3/examples/file-association/build/Taskfile.common.yml new file mode 100644 index 000000000..650c8ea83 --- /dev/null +++ b/v3/examples/file-association/build/Taskfile.common.yml @@ -0,0 +1,75 @@ +version: '3' + +tasks: + go:mod:tidy: + summary: Runs `go mod tidy` + internal: true + generates: + - go.sum + sources: + - go.mod + cmds: + - go mod tidy + + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/* + deps: + - task: install:frontend:deps + - task: generate:bindings + cmds: + - npm run build -q + + generate:bindings: + summary: Generates bindings for the frontend + sources: + - "**/*.go" + - go.mod + - go.sum + generates: + - "frontend/bindings/**/*" + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true {{if .UseTypescript}} -ts{{end}} + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + sources: + - "appicon.png" + generates: + - "icons.icns" + - "icon.ico" + cmds: + - wails3 generate icons -input appicon.png + + dev:frontend: + summary: Runs the frontend in development mode + dir: frontend + deps: + - task: install:frontend:deps + cmds: + - npm run dev -- --port {{.VITE_PORT}} --strictPort + + update:build-assets: + summary: Updates the build assets + dir: build + cmds: + - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir . \ No newline at end of file diff --git a/v3/examples/file-association/build/Taskfile.darwin.yml b/v3/examples/file-association/build/Taskfile.darwin.yml new file mode 100644 index 000000000..45db6d067 --- /dev/null +++ b/v3/examples/file-association/build/Taskfile.darwin.yml @@ -0,0 +1,45 @@ +version: '3' + +includes: + common: Taskfile.common.yml + +tasks: + build: + summary: Creates a production build of the application + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -ldflags="-w -s"{{else}}-gcflags=all="-l"{{end}}' + env: + GOOS: darwin + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + CGO_CFLAGS: "-mmacosx-version-min=10.15" + CGO_LDFLAGS: "-mmacosx-version-min=10.15" + MACOSX_DEPLOYMENT_TARGET: "10.15" + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application into a `.app` bundle + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:app:bundle + + create:app:bundle: + summary: Creates an `.app` bundle + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS + - cp build/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}' diff --git a/v3/examples/file-association/build/Taskfile.linux.yml b/v3/examples/file-association/build/Taskfile.linux.yml new file mode 100644 index 000000000..814ee0ae1 --- /dev/null +++ b/v3/examples/file-association/build/Taskfile.linux.yml @@ -0,0 +1,66 @@ +version: '3' + +includes: + common: Taskfile.common.yml + +tasks: + build: + summary: Builds the application for Linux + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -ldflags="-w -s"{{else}}-gcflags=all="-l"{{end}}' + env: + GOOS: linux + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application for Linux + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:appimage + + create:appimage: + summary: Creates an AppImage + dir: build/appimage + deps: + - task: build + vars: + PRODUCTION: "true" + - task: generate:dotdesktop + cmds: + - cp {{.APP_BINARY}} {{.APP_NAME}} + - cp ../appicon.png appicon.png + - wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/appimage + vars: + APP_NAME: '{{.APP_NAME}}' + APP_BINARY: '../../bin/{{.APP_NAME}}' + ICON: '../appicon.png' + DESKTOP_FILE: '{{.APP_NAME}}.desktop' + OUTPUT_DIR: '../../bin' + + generate:dotdesktop: + summary: Generates a `.desktop` file + dir: build + cmds: + - mkdir -p {{.ROOT_DIR}}/build/appimage + - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/appimage/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}" + vars: + APP_NAME: '{{.APP_NAME}}' + EXEC: '{{.APP_NAME}}' + ICON: 'appicon' + CATEGORIES: 'Development;' + OUTPUTFILE: '{{.ROOT_DIR}}/build/appimage/{{.APP_NAME}}.desktop' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}' diff --git a/v3/examples/file-association/build/Taskfile.windows.yml b/v3/examples/file-association/build/Taskfile.windows.yml new file mode 100644 index 000000000..f141fcd2f --- /dev/null +++ b/v3/examples/file-association/build/Taskfile.windows.yml @@ -0,0 +1,53 @@ +version: '3' + +includes: + common: Taskfile.common.yml + +tasks: + build: + summary: Builds the application for Windows + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + - task: common:generate:icons + - task: generate:syso + cmds: + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe + - powershell Remove-item *.syso + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -ldflags="-w -s -H windowsgui"{{else}}-gcflags=all="-l"{{end}}' + env: + GOOS: windows + CGO_ENABLED: 0 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application into a `.exe` bundle + cmds: + - task: create:nsis:installer + + generate:syso: + summary: Generates Windows `.syso` file + dir: build + cmds: + - wails3 generate syso -arch {{.ARCH}} -icon icon.ico -manifest wails.exe.manifest -info info.json + vars: + ARCH: '{{.ARCH | default ARCH}}' + + create:nsis:installer: + summary: Creates an NSIS installer + dir: build/nsis + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}\{{.BIN_DIR}}\{{.APP_NAME}}.exe" project.nsi + vars: + ARCH: '{{.ARCH | default ARCH}}' + ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}' + + run: + cmds: + - '{{.BIN_DIR}}\\{{.APP_NAME}}.exe' \ No newline at end of file diff --git a/v3/examples/file-association/build/appicon.png b/v3/examples/file-association/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/file-association/build/appicon.png differ diff --git a/v3/examples/file-association/build/appimage/build.sh b/v3/examples/file-association/build/appimage/build.sh new file mode 100644 index 000000000..fcba535e5 --- /dev/null +++ b/v3/examples/file-association/build/appimage/build.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Copyright (c) 2018-Present Lea Anthony +# SPDX-License-Identifier: MIT + +# Fail script on any error +set -euxo pipefail + +# Define variables +APP_DIR="${APP_NAME}.AppDir" + +# Create AppDir structure +mkdir -p "${APP_DIR}/usr/bin" +cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/" +cp "${ICON_PATH}" "${APP_DIR}/" +cp "${DESKTOP_FILE}" "${APP_DIR}/" + +# Download linuxdeploy and make it executable +wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage +chmod +x linuxdeploy-x86_64.AppImage + +# Run linuxdeploy to bundle the application +./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage + +# Rename the generated AppImage +mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage" + diff --git a/v3/examples/file-association/build/config.yml b/v3/examples/file-association/build/config.yml new file mode 100644 index 000000000..0786788ae --- /dev/null +++ b/v3/examples/file-association/build/config.yml @@ -0,0 +1,32 @@ +# This file contains the configuration for this project. +# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets. +# Note that this will overwrite any changes you have made to the assets. +version: '3' + +# This information is used to generate the build assets. +info: + companyName: "My Company" # The name of the company + productName: "My Product" # The name of the application + productIdentifier: "com.mycompany.myproduct" # The unique product identifier + description: "A program that does X" # The application description + copyright: "(c) 2025, My Company" # Copyright text + comments: "Some Product Comments" # Comments + version: "v0.0.1" # The application version + +# File Associations +# More information at: https://v3.wails.io/noit/done/yet +fileAssociations: + - ext: wails + name: Wails + description: Wails Application File + iconName: icon + role: Editor +# - ext: jpg +# name: JPEG +# description: Image File +# iconName: jpegFileIcon +# role: Editor + +# Other data +other: + - name: My Other Data \ No newline at end of file diff --git a/v3/examples/file-association/build/devmode.config.yaml b/v3/examples/file-association/build/devmode.config.yaml new file mode 100644 index 000000000..7d674a261 --- /dev/null +++ b/v3/examples/file-association/build/devmode.config.yaml @@ -0,0 +1,28 @@ +config: + root_path: . + log_level: warn + debounce: 1000 + ignore: + dir: + - .git + - node_modules + - frontend + - bin + file: + - .DS_Store + - .gitignore + - .gitkeep + watched_extension: + - "*.go" + git_ignore: true + executes: + - cmd: wails3 task common:install:frontend:deps + type: once + - cmd: wails3 task common:dev:frontend + type: background + - cmd: go mod tidy + type: blocking + - cmd: wails3 task build + type: blocking + - cmd: wails3 task run + type: primary diff --git a/v3/examples/file-association/build/icon.ico b/v3/examples/file-association/build/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/file-association/build/icon.ico differ diff --git a/v3/examples/file-association/build/icons.icns b/v3/examples/file-association/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/file-association/build/icons.icns differ diff --git a/v3/examples/file-association/build/info.json b/v3/examples/file-association/build/info.json new file mode 100644 index 000000000..850b2b5b0 --- /dev/null +++ b/v3/examples/file-association/build/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "0.1.0" + }, + "info": { + "0000": { + "ProductVersion": "0.1.0", + "CompanyName": "My Company", + "FileDescription": "My Product Description", + "LegalCopyright": "© now, My Company", + "ProductName": "My Product", + "Comments": "This is a comment" + } + } +} \ No newline at end of file diff --git a/v3/examples/file-association/build/nsis/project.nsi b/v3/examples/file-association/build/nsis/project.nsi new file mode 100644 index 000000000..11ebc1ec7 --- /dev/null +++ b/v3/examples/file-association/build/nsis/project.nsi @@ -0,0 +1,112 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the wails_tools.nsh file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "my-project" # Default "fileassoc" +## !define INFO_COMPANYNAME "My Company" # Default "My Company" +## !define INFO_PRODUCTNAME "My Product Name" # Default "My Product" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.1.0" +## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "© now, My Company" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/v3/examples/file-association/build/nsis/wails_tools.nsh b/v3/examples/file-association/build/nsis/wails_tools.nsh new file mode 100644 index 000000000..d49f6c803 --- /dev/null +++ b/v3/examples/file-association/build/nsis/wails_tools.nsh @@ -0,0 +1,218 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "fileassoc" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "My Company" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "My Product" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "0.1.0" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "© now, My Company" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + + !insertmacro APP_ASSOCIATE "wails" "Wails" "Wails Application File" "$INSTDIR\icon.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" + File "..\icon.ico" + +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + + !insertmacro APP_UNASSOCIATE "wails" "Wails" + Delete "$INSTDIR\icon.ico" + +!macroend \ No newline at end of file diff --git a/v3/examples/file-association/build/wails.exe.manifest b/v3/examples/file-association/build/wails.exe.manifest new file mode 100644 index 000000000..03a121e40 --- /dev/null +++ b/v3/examples/file-association/build/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/v3/examples/file-association/frontend/bindings/changeme/greetservice.js b/v3/examples/file-association/frontend/bindings/changeme/greetservice.js new file mode 100644 index 000000000..860020abd --- /dev/null +++ b/v3/examples/file-association/frontend/bindings/changeme/greetservice.js @@ -0,0 +1,16 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, Create as $Create} from "@wailsio/runtime"; + +/** + * @param {string} name + * @returns {Promise & { cancel(): void }} + */ +export function Greet(name) { + let $resultPromise = /** @type {any} */($Call.ByID(1411160069, name)); + return $resultPromise; +} diff --git a/v3/examples/file-association/frontend/bindings/changeme/index.js b/v3/examples/file-association/frontend/bindings/changeme/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/examples/file-association/frontend/bindings/changeme/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/examples/file-association/frontend/index.html b/v3/examples/file-association/frontend/index.html new file mode 100644 index 000000000..b81d9729f --- /dev/null +++ b/v3/examples/file-association/frontend/index.html @@ -0,0 +1,35 @@ + + + + + + + + Wails App + + +
        + +

        Wails + Javascript

        +
        +
        Please enter your name below 👇
        +
        + + +
        +
        + +
        + + + diff --git a/v3/examples/file-association/frontend/main.js b/v3/examples/file-association/frontend/main.js new file mode 100644 index 000000000..c24b3b1ef --- /dev/null +++ b/v3/examples/file-association/frontend/main.js @@ -0,0 +1,21 @@ +import {GreetService} from "./bindings/changeme"; +import {Events} from "@wailsio/runtime"; + +const resultElement = document.getElementById('result'); +const timeElement = document.getElementById('time'); + +window.doGreet = () => { + let name = document.getElementById('name').value; + if (!name) { + name = 'anonymous'; + } + GreetService.Greet(name).then((result) => { + resultElement.innerText = result; + }).catch((err) => { + console.log(err); + }); +} + +Events.On('time', (time) => { + timeElement.innerText = time.data; +}); diff --git a/v3/examples/file-association/frontend/package-lock.json b/v3/examples/file-association/frontend/package-lock.json new file mode 100644 index 000000000..9ba47fffa --- /dev/null +++ b/v3/examples/file-association/frontend/package-lock.json @@ -0,0 +1,860 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "devDependencies": { + "@wailsio/runtime": "latest", + "vite": "^5.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.3.tgz", + "integrity": "sha512-ufb2CH2KfBWPJok95frEZZ82LtDl0A6QKTa8MoM+cWwDZvVGl5/jNb79pIhRvAalUu+7LD91VYR0nwRD799HkQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.3.tgz", + "integrity": "sha512-iAHpft/eQk9vkWIV5t22V77d90CRofgR2006UiCjHcHJFVI1E0oBkQIAbz+pLtthFw3hWEmVB4ilxGyBf48i2Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.3.tgz", + "integrity": "sha512-QPW2YmkWLlvqmOa2OwrfqLJqkHm7kJCIMq9kOz40Zo9Ipi40kf9ONG5Sz76zszrmIZZ4hgRIkez69YnTHgEz1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.3.tgz", + "integrity": "sha512-KO0pN5x3+uZm1ZXeIfDqwcvnQ9UEGN8JX5ufhmgH5Lz4ujjZMAnxQygZAVGemFWn+ZZC0FQopruV4lqmGMshow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.3.tgz", + "integrity": "sha512-CsC+ZdIiZCZbBI+aRlWpYJMSWvVssPuWqrDy/zi9YfnatKKSLFCe6fjna1grHuo/nVaHG+kiglpRhyBQYRTK4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.3.tgz", + "integrity": "sha512-F0nqiLThcfKvRQhZEzMIXOQG4EeX61im61VYL1jo4eBxv4aZRmpin6crnBJQ/nWnCsjH5F6J3W6Stdm0mBNqBg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.3.tgz", + "integrity": "sha512-KRSFHyE/RdxQ1CSeOIBVIAxStFC/hnBgVcaiCkQaVC+EYDtTe4X7z5tBkFyRoBgUGtB6Xg6t9t2kulnX6wJc6A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.3.tgz", + "integrity": "sha512-h6Q8MT+e05zP5BxEKz0vi0DhthLdrNEnspdLzkoFqGwnmOzakEHSlXfVyA4HJ322QtFy7biUAVFPvIDEDQa6rw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.3.tgz", + "integrity": "sha512-fKElSyXhXIJ9pqiYRqisfirIo2Z5pTTve5K438URf08fsypXrEkVmShkSfM8GJ1aUyvjakT+fn2W7Czlpd/0FQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.3.tgz", + "integrity": "sha512-YlddZSUk8G0px9/+V9PVilVDC6ydMz7WquxozToozSnfFK6wa6ne1ATUjUvjin09jp34p84milxlY5ikueoenw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.3.tgz", + "integrity": "sha512-yNaWw+GAO8JjVx3s3cMeG5Esz1cKVzz8PkTJSfYzE5u7A+NvGmbVFEHP+BikTIyYWuz0+DX9kaA3pH9Sqxp69g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.3.tgz", + "integrity": "sha512-lWKNQfsbpv14ZCtM/HkjCTm4oWTKTfxPmr7iPfp3AHSqyoTz5AgLemYkWLwOBWc+XxBbrU9SCokZP0WlBZM9lA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.3.tgz", + "integrity": "sha512-HoojGXTC2CgCcq0Woc/dn12wQUlkNyfH0I1ABK4Ni9YXyFQa86Fkt2Q0nqgLfbhkyfQ6003i3qQk9pLh/SpAYw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.3.tgz", + "integrity": "sha512-mnEOh4iE4USSccBOtcrjF5nj+5/zm6NcNhbSEfR3Ot0pxBwvEn5QVUXcuOwwPkapDtGZ6pT02xLoPaNv06w7KQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.3.tgz", + "integrity": "sha512-rMTzawBPimBQkG9NKpNHvquIUTQPzrnPxPbCY1Xt+mFkW7pshvyIS5kYgcf74goxXOQk0CP3EoOC1zcEezKXhw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.3.tgz", + "integrity": "sha512-2lg1CE305xNvnH3SyiKwPVsTVLCg4TmNCF1z7PSHX2uZY2VbUpdkgAllVoISD7JO7zu+YynpWNSKAtOrX3AiuA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.3.tgz", + "integrity": "sha512-9SjYp1sPyxJsPWuhOCX6F4jUMXGbVVd5obVpoVEi8ClZqo52ViZewA6eFz85y8ezuOA+uJMP5A5zo6Oz4S5rVQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.3.tgz", + "integrity": "sha512-HGZgRFFYrMrP3TJlq58nR1xy8zHKId25vhmm5S9jETEfDf6xybPxsavFTJaufe2zgOGYJBskGlj49CwtEuFhWQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@wailsio/runtime": { + "version": "3.0.0-alpha.28", + "resolved": "https://registry.npmjs.org/@wailsio/runtime/-/runtime-3.0.0-alpha.28.tgz", + "integrity": "sha512-caMnAcKxxDrIWYgCZAMY2kdL++X4ehO2+JvH5na21xfDqz3VnHkEjxsH3jfhgd34M8LY80QEH8iqoMYytDFE/g==", + "dev": true, + "dependencies": { + "nanoid": "^5.0.7" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.8.tgz", + "integrity": "sha512-TcJPw+9RV9dibz1hHUzlLVy8N4X9TnwirAjrU08Juo6BNKggzVfP2ZJ/3ZUSq15Xl5i85i+Z89XBO90pB2PghQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/rollup": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.3.tgz", + "integrity": "sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.3", + "@rollup/rollup-android-arm64": "4.24.3", + "@rollup/rollup-darwin-arm64": "4.24.3", + "@rollup/rollup-darwin-x64": "4.24.3", + "@rollup/rollup-freebsd-arm64": "4.24.3", + "@rollup/rollup-freebsd-x64": "4.24.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.3", + "@rollup/rollup-linux-arm-musleabihf": "4.24.3", + "@rollup/rollup-linux-arm64-gnu": "4.24.3", + "@rollup/rollup-linux-arm64-musl": "4.24.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.3", + "@rollup/rollup-linux-riscv64-gnu": "4.24.3", + "@rollup/rollup-linux-s390x-gnu": "4.24.3", + "@rollup/rollup-linux-x64-gnu": "4.24.3", + "@rollup/rollup-linux-x64-musl": "4.24.3", + "@rollup/rollup-win32-arm64-msvc": "4.24.3", + "@rollup/rollup-win32-ia32-msvc": "4.24.3", + "@rollup/rollup-win32-x64-msvc": "4.24.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vite": { + "version": "5.4.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", + "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/v3/examples/file-association/frontend/package.json b/v3/examples/file-association/frontend/package.json new file mode 100644 index 000000000..2642d7a41 --- /dev/null +++ b/v3/examples/file-association/frontend/package.json @@ -0,0 +1,16 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build --minify false --mode development", + "build:prod": "vite build --mode production", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^5.0.0", + "@wailsio/runtime": "latest" + } +} \ No newline at end of file diff --git a/v3/examples/file-association/frontend/public/Inter-Medium.ttf b/v3/examples/file-association/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/file-association/frontend/public/Inter-Medium.ttf differ diff --git a/v3/examples/file-association/frontend/public/javascript.svg b/v3/examples/file-association/frontend/public/javascript.svg new file mode 100644 index 000000000..f9abb2b72 --- /dev/null +++ b/v3/examples/file-association/frontend/public/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/file-association/frontend/public/style.css b/v3/examples/file-association/frontend/public/style.css new file mode 100644 index 000000000..259397254 --- /dev/null +++ b/v3/examples/file-association/frontend/public/style.css @@ -0,0 +1,160 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +* { + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/examples/file-association/frontend/public/wails.png b/v3/examples/file-association/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/file-association/frontend/public/wails.png differ diff --git a/v3/examples/file-association/go.mod b/v3/examples/file-association/go.mod new file mode 100644 index 000000000..296394de3 --- /dev/null +++ b/v3/examples/file-association/go.mod @@ -0,0 +1,49 @@ +module changeme + +go 1.25 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.62 + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-git/go-git/v5 v5.16.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/lmittmann/tint v1.1.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.52.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/wailsapp/go-webview2 v1.0.23 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/file-association/go.sum b/v3/examples/file-association/go.sum new file mode 100644 index 000000000..56f1153ea --- /dev/null +++ b/v3/examples/file-association/go.sum @@ -0,0 +1,147 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= +github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/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-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/examples/file-association/greetservice.go b/v3/examples/file-association/greetservice.go new file mode 100644 index 000000000..8972c39cd --- /dev/null +++ b/v3/examples/file-association/greetservice.go @@ -0,0 +1,7 @@ +package main + +type GreetService struct{} + +func (g *GreetService) Greet(name string) string { + return "Hello " + name + "!" +} diff --git a/v3/examples/file-association/main.go b/v3/examples/file-association/main.go new file mode 100644 index 000000000..a81506b52 --- /dev/null +++ b/v3/examples/file-association/main.go @@ -0,0 +1,97 @@ +package main + +import ( + "embed" + _ "embed" + "github.com/wailsapp/wails/v3/pkg/events" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// Wails uses Go's `embed` package to embed the frontend files into the binary. +// Any files in the frontend/dist folder will be embedded into the binary and +// made available to the frontend. +// See https://pkg.go.dev/embed for more information. + +//go:embed frontend +var assets embed.FS + +// main function serves as the application's entry point. It initializes the application, creates a window, +// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and +// logs any error that might occur. +func main() { + + // Create a new Wails application by providing the necessary options. + // Variables 'Name' and 'Description' are for application metadata. + // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files. + // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances. + // 'Mac' options tailor the application when running an macOS. + app := application.New(application.Options{ + Name: "fileassoc", + Description: "A demo of using raw HTML & CSS", + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + FileAssociations: []string{".wails"}, + }) + + // Create a new window with the necessary options. + // 'Title' is the title of the window. + // 'Mac' options tailor the window when running on macOS. + // 'BackgroundColour' is the background colour of the window. + // 'URL' is the URL that will be loaded into the webview. + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + BackgroundColour: application.NewRGB(27, 38, 54), + URL: "/", + }) + + var filename string + app.Event.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(event *application.ApplicationEvent) { + filename = event.Context().Filename() + }) + + window.OnWindowEvent(events.Common.WindowShow, func(event *application.WindowEvent) { + app.Dialog.Info(). + SetTitle("File Opened"). + SetMessage("Application opened with file: " + filename). + Show() + }) + + // Create a goroutine that emits an event containing the current time every second. + // The frontend can listen to this event and update the UI accordingly. + go func() { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + now := time.Now().Format(time.RFC1123) + app.Event.Emit("time", now) + case <-app.Context().Done(): + return + } + } + }() + + // Run the application. This blocks until the application has been exited. + err := app.Run() + + // If an error occurred while running the application, log it and exit. + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/file-association/test.wails b/v3/examples/file-association/test.wails new file mode 100644 index 000000000..dde58d5e8 --- /dev/null +++ b/v3/examples/file-association/test.wails @@ -0,0 +1 @@ +Once the application is built and installed, double click on this file to open the application. \ No newline at end of file diff --git a/v3/examples/frameless/README.md b/v3/examples/frameless/README.md new file mode 100644 index 000000000..f17f7a5d3 --- /dev/null +++ b/v3/examples/frameless/README.md @@ -0,0 +1,19 @@ +# Frameless Example + +This example is a demonstration of using frameless windows in Wails. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/frameless/assets/index.html b/v3/examples/frameless/assets/index.html new file mode 100644 index 000000000..2776cfaa9 --- /dev/null +++ b/v3/examples/frameless/assets/index.html @@ -0,0 +1,53 @@ + + + + + + + + +
        +
        Draggable
        +
        Not Draggable
        +
        Not Draggable
        +
        Not Draggable
        +
        Draggable
        +
        + + diff --git a/v3/examples/frameless/main.go b/v3/examples/frameless/main.go new file mode 100644 index 000000000..93be412fd --- /dev/null +++ b/v3/examples/frameless/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Frameless Demo", + Description: "A demo of frameless windows", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/gin-example/README.md b/v3/examples/gin-example/README.md new file mode 100644 index 000000000..b4d85cb9b --- /dev/null +++ b/v3/examples/gin-example/README.md @@ -0,0 +1,104 @@ +# Gin Example + +This example demonstrates how to use the [Gin web framework](https://github.com/gin-gonic/gin) with Wails. + +## Overview + +This example shows how to: + +- Set up Gin as the asset handler for a Wails application +- Create a middleware that routes requests between Wails and Gin +- Define API endpoints with Gin +- Communicate between the Gin-served frontend and Wails backend +- Implement custom Gin middleware + +## Running the Example + +```bash +cd v3/examples/gin-example +go mod tidy +go run . +``` + +## How It Works + +The example uses Gin's HTTP router to serve the frontend content whilst still allowing Wails to handle its internal routes. This is achieved through: + +1. Creating a Gin router with routes for the frontend +2. Implementing a middleware function that decides whether to pass requests to Gin or let Wails handle them +3. Configuring the Wails application to use both the Gin router as the asset handler and the custom middleware + +### Wails-Gin Integration + +The key part of the integration is the middleware function: + +```go +func GinMiddleware(ginEngine *gin.Engine) application.Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Let Wails handle its internal routes + if r.URL.Path == "/wails/runtime.js" || r.URL.Path == "/wails/ipc" { + next.ServeHTTP(w, r) + return + } + + // Let Gin handle everything else + ginEngine.ServeHTTP(w, r) + }) + } +} +``` + +This allows you to leverage Gin's powerful routing and middleware capabilities whilst still maintaining full access to Wails features. + +### Custom Gin Middleware + +The example also demonstrates how to create custom Gin middleware: + +```go +func LoggingMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + startTime := time.Now() + + // Process request + c.Next() + + // Calculate latency + latency := time.Since(startTime) + + // Log request details + log.Printf("[GIN] %s | %s | %s | %d | %s", + c.Request.Method, + c.Request.URL.Path, + c.ClientIP(), + c.Writer.Status(), + latency, + ) + } +} +``` + +This middleware is applied to all Gin routes and logs details about each request. + +### Application Configuration + +The Wails application is configured to use Gin as follows: + +```go +app := application.New(application.Options{ + Name: "Gin Example", + Description: "A demo of using Gin with Wails", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: ginEngine, + Middleware: GinMiddleware(ginEngine), + }, +}) +``` + +This configuration tells Wails to: +1. Use the Gin engine as the primary handler for HTTP requests +2. Use our custom middleware to route requests between Wails and Gin diff --git a/v3/examples/gin-example/go.mod b/v3/examples/gin-example/go.mod new file mode 100644 index 000000000..4badef3b4 --- /dev/null +++ b/v3/examples/gin-example/go.mod @@ -0,0 +1,75 @@ +module gin-example + +go 1.25 + +require ( + github.com/gin-gonic/gin v1.11.0 + github.com/wailsapp/wails/v3 v3.0.0 +) + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-git/go-git/v5 v5.16.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/lmittmann/tint v1.1.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.52.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + github.com/wailsapp/go-webview2 v1.0.23 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + go.uber.org/mock v0.6.0 // indirect + golang.org/x/arch v0.23.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/gin-example/go.sum b/v3/examples/gin-example/go.sum new file mode 100644 index 000000000..9ca8a1297 --- /dev/null +++ b/v3/examples/gin-example/go.sum @@ -0,0 +1,208 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= +github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= +golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/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-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= diff --git a/v3/examples/gin-example/main.go b/v3/examples/gin-example/main.go new file mode 100644 index 000000000..8138d5403 --- /dev/null +++ b/v3/examples/gin-example/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "embed" + "log" + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed static +var staticFiles embed.FS + +// GinMiddleware creates a middleware that passes requests to Gin if they're not handled by Wails +func GinMiddleware(ginEngine *gin.Engine) application.Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Let Wails handle the `/wails` route + if strings.HasPrefix(r.URL.Path, "/wails") { + next.ServeHTTP(w, r) + return + } + // Let Gin handle everything else + ginEngine.ServeHTTP(w, r) + }) + } +} + +// LoggingMiddleware is a Gin middleware that logs request details +func LoggingMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + startTime := time.Now() + + // Process request + c.Next() + + // Calculate latency + latency := time.Since(startTime) + + // Log request details + log.Printf("[GIN] %s | %s | %s | %d | %s", + c.Request.Method, + c.Request.URL.Path, + c.ClientIP(), + c.Writer.Status(), + latency, + ) + } +} + +func main() { + // Create a new Gin router + ginEngine := gin.New() // Using New() instead of Default() to add our own middleware + + // Add middlewares + ginEngine.Use(gin.Recovery()) + ginEngine.Use(LoggingMiddleware()) + + // Serve embedded static files + ginEngine.StaticFS("/static", http.FS(staticFiles)) + + // Define routes + ginEngine.GET("/", func(c *gin.Context) { + file, err := staticFiles.ReadFile("static/index.html") + if err != nil { + c.String(http.StatusInternalServerError, "Error reading index.html") + return + } + c.Data(http.StatusOK, "text/html; charset=utf-8", file) + }) + + ginEngine.GET("/api/hello", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "message": "Hello from Gin API!", + "time": time.Now().Format(time.RFC3339), + }) + }) + + // Create a new Wails application + app := application.New(application.Options{ + Name: "Gin Example", + Description: "A demo of using Gin with Wails", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: ginEngine, + Middleware: GinMiddleware(ginEngine), + }, + }) + + // Register event handler and store cleanup function + removeGinHandler := app.Event.On("gin-button-clicked", func(event *application.CustomEvent) { + log.Printf("Received event from frontend: %v", event.Data) + }) + // Note: In production, call removeGinHandler() during cleanup + _ = removeGinHandler + + // Create window + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Wails + Gin Example", + Width: 900, + Height: 700, + URL: "/", + }) + + // Run the app + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/gin-example/static/index.html b/v3/examples/gin-example/static/index.html new file mode 100644 index 000000000..df02b840f --- /dev/null +++ b/v3/examples/gin-example/static/index.html @@ -0,0 +1,96 @@ + + + Wails + Gin Example + + + +
        +

        Wails + Gin Integration

        +
        +

        Hello World!

        +

        This page is being served by Gin.

        +

        Click the button below to trigger a Wails event:

        + + +
        +
        +

        API Example

        +

        Try the Gin API endpoint:

        + +
        +
        +
        + + + diff --git a/v3/examples/gin-routing/README.md b/v3/examples/gin-routing/README.md new file mode 100644 index 000000000..5d8babc31 --- /dev/null +++ b/v3/examples/gin-routing/README.md @@ -0,0 +1,26 @@ +# Gin Routing Example + +This example demonstrates how to use the [Gin web framework](https://github.com/gin-gonic/gin) as a router with Wails. + +## Overview + +This example shows how to: + +- Set up Gin as the asset handler for a Wails application +- Create a middleware that routes requests between Wails and Gin +- Define API endpoints with Gin +- Communicate between the Gin-served frontend and Wails backend +- Implement custom Gin middleware + +## Running the Example + +```bash +cd v3/examples/gin-routing +go mod tidy +go run . +``` + +## Documentation + +Please consult the [Using Gin for Routing](https://v3.wails.io/guides/gin-routing/) guide for a detailed explanation of the example. + diff --git a/v3/examples/gin-routing/go.mod b/v3/examples/gin-routing/go.mod new file mode 100644 index 000000000..41069befd --- /dev/null +++ b/v3/examples/gin-routing/go.mod @@ -0,0 +1,75 @@ +module gin-routing + +go 1.25 + +require ( + github.com/gin-gonic/gin v1.11.0 + github.com/wailsapp/wails/v3 v3.0.0 +) + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-git/go-git/v5 v5.16.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/lmittmann/tint v1.1.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.52.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + github.com/wailsapp/go-webview2 v1.0.23 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + go.uber.org/mock v0.6.0 // indirect + golang.org/x/arch v0.23.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/gin-routing/go.sum b/v3/examples/gin-routing/go.sum new file mode 100644 index 000000000..9ca8a1297 --- /dev/null +++ b/v3/examples/gin-routing/go.sum @@ -0,0 +1,208 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= +github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= +golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/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-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= diff --git a/v3/examples/gin-routing/main.go b/v3/examples/gin-routing/main.go new file mode 100644 index 000000000..a78494808 --- /dev/null +++ b/v3/examples/gin-routing/main.go @@ -0,0 +1,114 @@ +package main + +import ( + "embed" + "log" + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed static +var staticFiles embed.FS + +// GinMiddleware creates a middleware that passes requests to Gin if they're not handled by Wails +func GinMiddleware(ginEngine *gin.Engine) application.Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Let Wails handle the `/wails` route + if strings.HasPrefix(r.URL.Path, "/wails") { + next.ServeHTTP(w, r) + return + } + // Let Gin handle everything else + ginEngine.ServeHTTP(w, r) + }) + } +} + +// LoggingMiddleware is a Gin middleware that logs request details +func LoggingMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + startTime := time.Now() + + // Process request + c.Next() + + // Calculate latency + latency := time.Since(startTime) + + // Log request details + log.Printf("[GIN] %s | %s | %s | %d | %s", + c.Request.Method, + c.Request.URL.Path, + c.ClientIP(), + c.Writer.Status(), + latency, + ) + } +} + +func main() { + // Create a new Gin router + ginEngine := gin.New() // Using New() instead of Default() to add our own middleware + + // Add middlewares + ginEngine.Use(gin.Recovery()) + ginEngine.Use(LoggingMiddleware()) + + // Serve embedded static files + ginEngine.StaticFS("/static", http.FS(staticFiles)) + + // Define routes + ginEngine.GET("/", func(c *gin.Context) { + file, err := staticFiles.ReadFile("static/index.html") + if err != nil { + c.String(http.StatusInternalServerError, "Error reading index.html") + return + } + c.Data(http.StatusOK, "text/html; charset=utf-8", file) + }) + + ginEngine.GET("/api/hello", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "message": "Hello from Gin API!", + "time": time.Now().Format(time.RFC3339), + }) + }) + + // Create a new Wails application + app := application.New(application.Options{ + Name: "Gin Example", + Description: "A demo of using Gin with Wails", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: ginEngine, + Middleware: GinMiddleware(ginEngine), + }, + }) + + // Register event handler + app.Event.On("gin-button-clicked", func(event *application.CustomEvent) { + log.Printf("Received event from frontend: %v", event.Data) + }) + + // Create window + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Wails + Gin Example", + Width: 900, + Height: 700, + URL: "/", + }) + + // Run the app + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/gin-routing/static/index.html b/v3/examples/gin-routing/static/index.html new file mode 100644 index 000000000..aefbd7a28 --- /dev/null +++ b/v3/examples/gin-routing/static/index.html @@ -0,0 +1,94 @@ + + + Wails + Gin Example + + + +
        +

        Wails + Gin Integration

        +
        +

        Hello World!

        +

        This page is being served by Gin.

        +

        Click the button below to trigger a Wails event:

        + + +
        +
        +

        API Example

        +

        Try the Gin API endpoint:

        + +
        +
        +
        + + + diff --git a/v3/examples/gin-service/README.md b/v3/examples/gin-service/README.md new file mode 100644 index 000000000..89e5019d7 --- /dev/null +++ b/v3/examples/gin-service/README.md @@ -0,0 +1,21 @@ +# Gin Service Example + +This example demonstrates how to use the [Gin web framework](https://github.com/gin-gonic/gin) in a Wails Service. + +## Overview + +This example shows how to: + +- Set up Gin as the asset handler for a Wails application +- Create a middleware that routes requests between Wails and Gin +- Define API endpoints with Gin +- Communicate between the Gin-served frontend and Wails backend +- Implement custom Gin middleware + +## Running the Example + + +## Documentation + +Please consult the [Using Gin for Routing](https://v3.wails.io/guides/gin-routing/) guide for a detailed explanation of the example. + diff --git a/v3/examples/gin-service/assets/index.html b/v3/examples/gin-service/assets/index.html new file mode 100644 index 000000000..66aea00eb --- /dev/null +++ b/v3/examples/gin-service/assets/index.html @@ -0,0 +1,249 @@ + + + + + + Gin Service Example + + + +

        Gin Service Example

        + +
        +

        API Endpoints

        +

        Try the Gin API endpoints mounted at /api:

        + + + + + + + +
        +
        Results will appear here...
        +
        +
        + +
        +

        Event Communication

        +

        Trigger an event to communicate with the Gin service:

        + + + +
        +
        Event responses will appear here...
        +
        +
        + + + + + + diff --git a/v3/examples/gin-service/go.mod b/v3/examples/gin-service/go.mod new file mode 100644 index 000000000..5b46f88af --- /dev/null +++ b/v3/examples/gin-service/go.mod @@ -0,0 +1,74 @@ +module gin-service + +go 1.25 + +require ( + github.com/gin-gonic/gin v1.11.0 + github.com/wailsapp/wails/v3 v3.0.0-alpha.62 +) + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-git/go-git/v5 v5.16.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/lmittmann/tint v1.1.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.52.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + github.com/wailsapp/go-webview2 v1.0.23 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + go.uber.org/mock v0.6.0 // indirect + golang.org/x/arch v0.23.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/gin-service/go.sum b/v3/examples/gin-service/go.sum new file mode 100644 index 000000000..07a22c6eb --- /dev/null +++ b/v3/examples/gin-service/go.sum @@ -0,0 +1,208 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= +github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/wails/v3 v3.0.0-alpha.62 h1:ihEh+skkAt26PcVCil1xWbhPQIfl7Kkh0g64u8gBTe0= +github.com/wailsapp/wails/v3 v3.0.0-alpha.62/go.mod h1:ynGPamjQDXoaWjOGKAHJ6vw94PUDbeIxtbapunWcDjk= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= +golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/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-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= diff --git a/v3/examples/gin-service/main.go b/v3/examples/gin-service/main.go new file mode 100644 index 000000000..f27fef415 --- /dev/null +++ b/v3/examples/gin-service/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "embed" + "log/slog" + "os" + + "gin-service/services" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "Gin Service Demo", + Description: "A demo of using Gin in Wails services", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + LogLevel: slog.LevelDebug, + Services: []application.Service{ + application.NewServiceWithOptions(services.NewGinService(), application.ServiceOptions{ + Route: "/api", + }), + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Gin Service Demo", + Width: 1024, + Height: 768, + }) + + err := app.Run() + + if err != nil { + println(err.Error()) + os.Exit(1) + } +} diff --git a/v3/examples/gin-service/services/gin_service.go b/v3/examples/gin-service/services/gin_service.go new file mode 100644 index 000000000..e0fa290ec --- /dev/null +++ b/v3/examples/gin-service/services/gin_service.go @@ -0,0 +1,250 @@ +package services + +import ( + "context" + "net/http" + "strconv" + "strings" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// User represents a user in the system +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + CreatedAt time.Time `json:"createdAt"` +} + +// GinService implements a Wails service that uses Gin for HTTP handling +type GinService struct { + ginEngine *gin.Engine + users []User + nextID int + mu sync.RWMutex + app *application.App + maxUsers int // Maximum number of users to prevent unbounded growth + removeEventHandler func() // Store cleanup function for event handler +} + +type EventData struct { + Message string `json:"message"` + Timestamp string `json:"timestamp"` +} + +// NewGinService creates a new GinService instance +func NewGinService() *GinService { + // Create a new Gin router + ginEngine := gin.New() + + // Add middlewares + ginEngine.Use(gin.Recovery()) + ginEngine.Use(LoggingMiddleware()) + + service := &GinService{ + ginEngine: ginEngine, + users: []User{ + {ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now().Add(-72 * time.Hour)}, + {ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now().Add(-48 * time.Hour)}, + {ID: 3, Name: "Charlie", Email: "charlie@example.com", CreatedAt: time.Now().Add(-24 * time.Hour)}, + }, + nextID: 4, + maxUsers: 1000, // Limit to prevent unbounded slice growth + } + + // Define routes + service.setupRoutes() + + return service +} + +// ServiceName returns the name of the service +func (s *GinService) ServiceName() string { + return "Gin API Service" +} + +// ServiceStartup is called when the service starts +func (s *GinService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // You can access the application instance via ctx + s.app = application.Get() + + // Register an event handler that can be triggered from the frontend + // Store the cleanup function for proper resource management + s.removeEventHandler = s.app.Event.On("gin-api-event", func(event *application.CustomEvent) { + // Log the event data + // Parse the event data + s.app.Logger.Info("Received event from frontend", "data", event.Data) + + // You could also emit an event back to the frontend + s.app.Event.Emit("gin-api-response", map[string]interface{}{ + "message": "Response from Gin API Service", + "time": time.Now().Format(time.RFC3339), + }) + }) + + return nil +} + +// ServiceShutdown is called when the service shuts down +func (s *GinService) ServiceShutdown(ctx context.Context) error { + // Clean up event handler to prevent memory leaks + if s.removeEventHandler != nil { + s.removeEventHandler() + } + return nil +} + +// ServeHTTP implements the http.Handler interface +func (s *GinService) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // All other requests go to the Gin router + s.ginEngine.ServeHTTP(w, r) +} + +// setupRoutes configures the API routes +func (s *GinService) setupRoutes() { + // Basic info endpoint + s.ginEngine.GET("/info", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "service": "Gin API Service", + "version": "1.0.0", + "time": time.Now().Format(time.RFC3339), + }) + }) + + // Users group + users := s.ginEngine.Group("/users") + { + // Get all users + users.GET("", func(c *gin.Context) { + s.mu.RLock() + defer s.mu.RUnlock() + c.JSON(http.StatusOK, s.users) + }) + + // Get user by ID + users.GET("/:id", func(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + s.mu.RLock() + defer s.mu.RUnlock() + + for _, user := range s.users { + if user.ID == id { + c.JSON(http.StatusOK, user) + return + } + } + + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + }) + + // Create a new user + users.POST("", func(c *gin.Context) { + var newUser User + if err := c.ShouldBindJSON(&newUser); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Validate required fields + if newUser.Name == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Name is required"}) + return + } + if newUser.Email == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Email is required"}) + return + } + // Basic email validation (consider using a proper validator library in production) + if !strings.Contains(newUser.Email, "@") { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid email format"}) + return + } + + s.mu.Lock() + defer s.mu.Unlock() + + // Check if we've reached the maximum number of users + if len(s.users) >= s.maxUsers { + c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Maximum number of users reached"}) + return + } + + // Set the ID and creation time + newUser.ID = s.nextID + newUser.CreatedAt = time.Now() + s.nextID++ + + // Add to the users slice + s.users = append(s.users, newUser) + + c.JSON(http.StatusCreated, newUser) + + // Emit an event to notify about the new user + s.app.Event.Emit("user-created", newUser) + }) + + // Delete a user + users.DELETE("/:id", func(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + s.mu.Lock() + defer s.mu.Unlock() + + for i, user := range s.users { + if user.ID == id { + // Remove the user from the slice + s.users = append(s.users[:i], s.users[i+1:]...) + c.JSON(http.StatusOK, gin.H{"message": "User deleted"}) + return + } + } + + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + }) + } +} + +// LoggingMiddleware is a Gin middleware that logs request details +func LoggingMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + start := time.Now() + + // Process request + c.Next() + + // Calculate latency + latency := time.Since(start) + + // Log request details + statusCode := c.Writer.Status() + clientIP := c.ClientIP() + method := c.Request.Method + path := c.Request.URL.Path + + // Get the application instance + app := application.Get() + if app != nil { + app.Logger.Info("HTTP Request", + "status", statusCode, + "method", method, + "path", path, + "ip", clientIP, + "latency", latency, + ) + } + } +} diff --git a/v3/examples/hide-window/main.go b/v3/examples/hide-window/main.go new file mode 100644 index 000000000..1a65c7498 --- /dev/null +++ b/v3/examples/hide-window/main.go @@ -0,0 +1,69 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" + "github.com/wailsapp/wails/v3/pkg/icons" + "log" + "runtime" +) + +func main() { + app := application.New(application.Options{ + Name: "Hide Window Demo", + Description: "A test of Hidden window and display it", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, + }) + + systemTray := app.SystemTray.New() + + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Width: 500, + Height: 800, + Frameless: false, + AlwaysOnTop: false, + Hidden: false, + DisableResize: false, + Windows: application.WindowsWindow{ + HiddenOnTaskbar: true, + }, + }) + + window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + window.Hide() + e.Cancel() + }) + + if runtime.GOOS == "darwin" { + systemTray.SetTemplateIcon(icons.SystrayMacTemplate) + } + + // Click Dock icon tigger application show + app.Event.OnApplicationEvent(events.Mac.ApplicationShouldHandleReopen, func(event *application.ApplicationEvent) { + println("reopen") + window.Show() + }) + + myMenu := app.NewMenu() + myMenu.Add("Show").OnClick(func(ctx *application.Context) { + window.Show() + }) + + myMenu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + + systemTray.SetMenu(myMenu) + systemTray.OnClick(func() { + window.Show() + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/ignore-mouse/README.md b/v3/examples/ignore-mouse/README.md new file mode 100644 index 000000000..596d2c015 --- /dev/null +++ b/v3/examples/ignore-mouse/README.md @@ -0,0 +1,19 @@ +# Window Example + +This example is a demonstration of how to disable or enable mouse events. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | | +| Windows | Working | +| Linux | | diff --git a/v3/examples/ignore-mouse/main.go b/v3/examples/ignore-mouse/main.go new file mode 100644 index 000000000..1fc0304f2 --- /dev/null +++ b/v3/examples/ignore-mouse/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "WebviewWindow Demo", + Description: "A demo of the WebviewWindow API", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, + }) + + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Width: 800, + Height: 600, + Title: "Ignore Mouse Example", + URL: "https://wails.io", + IgnoreMouseEvents: false, + }) + + window.SetIgnoreMouseEvents(true) + log.Println("IgnoreMouseEvents set", window.IsIgnoreMouseEvents()) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/ios-poc/frontend/index.html b/v3/examples/ios-poc/frontend/index.html new file mode 100644 index 000000000..75070d43e --- /dev/null +++ b/v3/examples/ios-poc/frontend/index.html @@ -0,0 +1,247 @@ + + + + + + Wails v3 iOS Demo + + + +
        +

        🚀 Wails v3 iOS

        +

        Proof of Concept Demo

        + +
        +

        1. WebView Test

        + +
        Ready to test...
        +
        + +
        +

        2. Asset Server Test

        + +
        Ready to test...
        +
        + +
        +

        3. JavaScript Bridge Test

        + +
        Ready to test...
        +
        + +
        +

        4. Go Communication Test

        + + +
        Ready to test...
        +
        + +
        +

        Test Results:

        +
        + + WebView Creation +
        +
        + + Request Interception +
        +
        + + JS Execution +
        +
        + + iOS Simulator +
        +
        + +
        +
        +
        + iOS WebView Active +
        +
        + Platform: iOS +
        +
        +
        + + + + \ No newline at end of file diff --git a/v3/examples/ios-poc/main.go b/v3/examples/ios-poc/main.go new file mode 100644 index 000000000..62cae636b --- /dev/null +++ b/v3/examples/ios-poc/main.go @@ -0,0 +1,43 @@ +//go:build ios + +package main + +import ( + "embed" + "fmt" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/* +var assets embed.FS + +// App struct for binding methods +type App struct{} + +// Greet returns a greeting message +func (a *App) Greet(name string) string { + return fmt.Sprintf("Hello %s from Wails v3 on iOS!", name) +} + +func main() { + // Create application with options + app := application.New(application.Options{ + Name: "Wails iOS PoC", + Description: "Proof of concept for Wails v3 on iOS", + Assets: application.AssetOptions{ + FS: assets, + }, + Services: []application.Service{ + application.NewService(&App{}), + }, + LogLevel: application.LogLevelDebug, + }) + + // Run the application + err := app.Run() + if err != nil { + log.Fatal(err) + } +} \ No newline at end of file diff --git a/v3/examples/ios/.gitignore b/v3/examples/ios/.gitignore new file mode 100644 index 000000000..ba8194ab6 --- /dev/null +++ b/v3/examples/ios/.gitignore @@ -0,0 +1,6 @@ +.task +bin +frontend/dist +frontend/node_modules +build/linux/appimage/build +build/windows/nsis/MicrosoftEdgeWebview2Setup.exe \ No newline at end of file diff --git a/v3/examples/ios/README.md b/v3/examples/ios/README.md new file mode 100644 index 000000000..ad12c3f40 --- /dev/null +++ b/v3/examples/ios/README.md @@ -0,0 +1,59 @@ +# Welcome to Your New Wails3 Project! + +Congratulations on generating your Wails3 application! This README will guide you through the next steps to get your project up and running. + +## Getting Started + +1. Navigate to your project directory in the terminal. + +2. To run your application in development mode, use the following command: + + ``` + wails3 dev + ``` + + This will start your application and enable hot-reloading for both frontend and backend changes. + +3. To build your application for production, use: + + ``` + wails3 build + ``` + + This will create a production-ready executable in the `build` directory. + +## Exploring Wails3 Features + +Now that you have your project set up, it's time to explore the features that Wails3 offers: + +1. **Check out the examples**: The best way to learn is by example. Visit the `examples` directory in the `v3/examples` directory to see various sample applications. + +2. **Run an example**: To run any of the examples, navigate to the example's directory and use: + + ``` + go run . + ``` + + Note: Some examples may be under development during the alpha phase. + +3. **Explore the documentation**: Visit the [Wails3 documentation](https://v3.wails.io/) for in-depth guides and API references. + +4. **Join the community**: Have questions or want to share your progress? Join the [Wails Discord](https://discord.gg/JDdSxwjhGf) or visit the [Wails discussions on GitHub](https://github.com/wailsapp/wails/discussions). + +## Project Structure + +Take a moment to familiarize yourself with your project structure: + +- `frontend/`: Contains your frontend code (HTML, CSS, JavaScript/TypeScript) +- `main.go`: The entry point of your Go backend +- `app.go`: Define your application structure and methods here +- `wails.json`: Configuration file for your Wails project + +## Next Steps + +1. Modify the frontend in the `frontend/` directory to create your desired UI. +2. Add backend functionality in `main.go`. +3. Use `wails3 dev` to see your changes in real-time. +4. When ready, build your application with `wails3 build`. + +Happy coding with Wails3! If you encounter any issues or have questions, don't hesitate to consult the documentation or reach out to the Wails community. diff --git a/v3/examples/ios/Taskfile.yml b/v3/examples/ios/Taskfile.yml new file mode 100644 index 000000000..e198e184f --- /dev/null +++ b/v3/examples/ios/Taskfile.yml @@ -0,0 +1,34 @@ +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + ios: ./build/ios/Taskfile.yml + +vars: + APP_NAME: "ios" + BIN_DIR: "bin" + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{OS}}:package" + + run: + summary: Runs the application + cmds: + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} diff --git a/v3/examples/ios/build/Taskfile.yml b/v3/examples/ios/build/Taskfile.yml new file mode 100644 index 000000000..209793bfd --- /dev/null +++ b/v3/examples/ios/build/Taskfile.yml @@ -0,0 +1,174 @@ +version: '3' + +tasks: + go:mod:tidy: + summary: Runs `go mod tidy` + internal: true + cmds: + - go mod tidy + + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + label: build:frontend (PRODUCTION={{.PRODUCTION}}) + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/**/* + deps: + - task: install:frontend:deps + - task: generate:bindings + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + cmds: + - npm run {{.BUILD_COMMAND}} -q + env: + PRODUCTION: '{{.PRODUCTION | default "false"}}' + vars: + BUILD_COMMAND: '{{if eq .PRODUCTION "true"}}build{{else}}build:dev{{end}}' + + + frontend:vendor:puppertino: + summary: Fetches Puppertino CSS into frontend/public for consistent mobile styling + sources: + - frontend/public/puppertino/puppertino.css + generates: + - frontend/public/puppertino/puppertino.css + cmds: + - | + set -euo pipefail + mkdir -p frontend/public/puppertino + # Fetch Puppertino full.css and LICENSE from GitHub main branch + curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/dist/css/full.css -o frontend/public/puppertino/puppertino.css + curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/LICENSE -o frontend/public/puppertino/LICENSE + echo "Puppertino CSS updated at frontend/public/puppertino/puppertino.css" + # Ensure index.html includes Puppertino CSS and button classes + INDEX_HTML=frontend/index.html + if [ -f "$INDEX_HTML" ]; then + if ! grep -q 'href="/puppertino/puppertino.css"' "$INDEX_HTML"; then + # Insert Puppertino link tag after style.css link + awk ' + /href="\/style.css"\/?/ && !x { print; print " "; x=1; next }1 + ' "$INDEX_HTML" > "$INDEX_HTML.tmp" && mv "$INDEX_HTML.tmp" "$INDEX_HTML" + fi + # Replace default .btn with Puppertino primary button classes if present + sed -E -i'' 's/class=\"btn\"/class=\"p-btn p-prim-col\"/g' "$INDEX_HTML" || true + fi + + generate:bindings: + label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}}) + summary: Generates bindings for the frontend + deps: + - task: go:mod:tidy + sources: + - "**/*.[jt]s" + - exclude: frontend/**/* + - frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output + - "**/*.go" + - go.mod + - go.sum + generates: + - frontend/bindings/**/* + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + sources: + - "appicon.png" + generates: + - "darwin/icons.icns" + - "windows/icon.ico" + cmds: + - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico + + dev:frontend: + summary: Runs the frontend in development mode + dir: frontend + deps: + - task: install:frontend:deps + cmds: + - npm run dev -- --port {{.VITE_PORT}} --strictPort + + update:build-assets: + summary: Updates the build assets + dir: build + cmds: + - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir . + + + ios:device:list: + summary: Lists connected iOS devices (UDIDs) + cmds: + - xcrun xcdevice list + + ios:run:device: + summary: Build, install, and launch on a physical iPhone using Apple tools (xcodebuild/devicectl) + vars: + PROJECT: '{{.PROJECT}}' # e.g., build/ios/xcode/.xcodeproj + SCHEME: '{{.SCHEME}}' # e.g., ios.dev + CONFIG: '{{.CONFIG | default "Debug"}}' + DERIVED: '{{.DERIVED | default "build/ios/DerivedData"}}' + UDID: '{{.UDID}}' # from `task ios:device:list` + BUNDLE_ID: '{{.BUNDLE_ID}}' # e.g., com.yourco.wails.ios.dev + TEAM_ID: '{{.TEAM_ID}}' # optional, if your project is not already set up for signing + preconditions: + - sh: xcrun -f xcodebuild + msg: "xcodebuild not found. Please install Xcode." + - sh: xcrun -f devicectl + msg: "devicectl not found. Please update to Xcode 15+ (which includes devicectl)." + - sh: test -n "{{.PROJECT}}" + msg: "Set PROJECT to your .xcodeproj path (e.g., PROJECT=build/ios/xcode/App.xcodeproj)." + - sh: test -n "{{.SCHEME}}" + msg: "Set SCHEME to your app scheme (e.g., SCHEME=ios.dev)." + - sh: test -n "{{.UDID}}" + msg: "Set UDID to your device UDID (see: task ios:device:list)." + - sh: test -n "{{.BUNDLE_ID}}" + msg: "Set BUNDLE_ID to your app's bundle identifier (e.g., com.yourco.wails.ios.dev)." + cmds: + - | + set -euo pipefail + echo "Building for device: UDID={{.UDID}} SCHEME={{.SCHEME}} PROJECT={{.PROJECT}}" + XCB_ARGS=( + -project "{{.PROJECT}}" + -scheme "{{.SCHEME}}" + -configuration "{{.CONFIG}}" + -destination "id={{.UDID}}" + -derivedDataPath "{{.DERIVED}}" + -allowProvisioningUpdates + -allowProvisioningDeviceRegistration + ) + # Optionally inject signing identifiers if provided + if [ -n "{{.TEAM_ID}}" ]; then XCB_ARGS+=(DEVELOPMENT_TEAM={{.TEAM_ID}}); fi + if [ -n "{{.BUNDLE_ID}}" ]; then XCB_ARGS+=(PRODUCT_BUNDLE_IDENTIFIER={{.BUNDLE_ID}}); fi + xcodebuild "${XCB_ARGS[@]}" build | xcpretty || true + # If xcpretty isn't installed, run without it + if [ "${PIPESTATUS[0]}" -ne 0 ]; then + xcodebuild "${XCB_ARGS[@]}" build + fi + # Find built .app + APP_PATH=$(find "{{.DERIVED}}/Build/Products" -type d -name "*.app" -maxdepth 3 | head -n 1) + if [ -z "$APP_PATH" ]; then + echo "Could not locate built .app under {{.DERIVED}}/Build/Products" >&2 + exit 1 + fi + echo "Installing: $APP_PATH" + xcrun devicectl device install app --device "{{.UDID}}" "$APP_PATH" + echo "Launching: {{.BUNDLE_ID}}" + xcrun devicectl device process launch --device "{{.UDID}}" --stderr console --stdout console "{{.BUNDLE_ID}}" diff --git a/v3/examples/ios/build/appicon.png b/v3/examples/ios/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/ios/build/appicon.png differ diff --git a/v3/examples/ios/build/config.yml b/v3/examples/ios/build/config.yml new file mode 100644 index 000000000..a8e7e1fa5 --- /dev/null +++ b/v3/examples/ios/build/config.yml @@ -0,0 +1,77 @@ +# This file contains the configuration for this project. +# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets. +# Note that this will overwrite any changes you have made to the assets. +version: '3' + +# This information is used to generate the build assets. +info: + companyName: "My Company" # The name of the company + productName: "My Product" # The name of the application + productIdentifier: "com.mycompany.myproduct" # The unique product identifier + description: "A program that does X" # The application description + copyright: "(c) 2025, My Company" # Copyright text + comments: "Some Product Comments" # Comments + version: "0.0.1" # The application version + +# iOS build configuration (uncomment to customise iOS project generation) +# Note: Keys under `ios` OVERRIDE values under `info` when set. +# ios: +# # The iOS bundle identifier used in the generated Xcode project (CFBundleIdentifier) +# bundleID: "com.mycompany.myproduct" +# # The display name shown under the app icon (CFBundleDisplayName/CFBundleName) +# displayName: "My Product" +# # The app version to embed in Info.plist (CFBundleShortVersionString/CFBundleVersion) +# version: "0.0.1" +# # The company/organisation name for templates and project settings +# company: "My Company" +# # Additional comments to embed in Info.plist metadata +# comments: "Some Product Comments" + +# Dev mode configuration +dev_mode: + root_path: . + log_level: warn + debounce: 1000 + ignore: + dir: + - .git + - node_modules + - frontend + - bin + file: + - .DS_Store + - .gitignore + - .gitkeep + watched_extension: + - "*.go" + git_ignore: true + executes: + - cmd: wails3 task common:install:frontend:deps + type: once + - cmd: wails3 task common:dev:frontend + type: background + - cmd: go mod tidy + type: blocking + - cmd: wails3 task build + type: blocking + - cmd: wails3 task run + type: primary + +# File Associations +# More information at: https://v3.wails.io/noit/done/yet +fileAssociations: +# - ext: wails +# name: Wails +# description: Wails Application File +# iconName: wailsFileIcon +# role: Editor +# - ext: jpg +# name: JPEG +# description: Image File +# iconName: jpegFileIcon +# role: Editor +# mimeType: image/jpeg # (optional) + +# Other data +other: + - name: My Other Data \ No newline at end of file diff --git a/v3/examples/ios/build/darwin/Info.dev.plist b/v3/examples/ios/build/darwin/Info.dev.plist new file mode 100644 index 000000000..bd6a537fa --- /dev/null +++ b/v3/examples/ios/build/darwin/Info.dev.plist @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + ios + CFBundleIdentifier + com.wails.ios + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © now, My Company + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/examples/ios/build/darwin/Info.plist b/v3/examples/ios/build/darwin/Info.plist new file mode 100644 index 000000000..fb52df715 --- /dev/null +++ b/v3/examples/ios/build/darwin/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + ios + CFBundleIdentifier + com.wails.ios + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © now, My Company + + \ No newline at end of file diff --git a/v3/examples/ios/build/darwin/Taskfile.yml b/v3/examples/ios/build/darwin/Taskfile.yml new file mode 100644 index 000000000..f0791fea9 --- /dev/null +++ b/v3/examples/ios/build/darwin/Taskfile.yml @@ -0,0 +1,81 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Creates a production build of the application + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + env: + GOOS: darwin + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + CGO_CFLAGS: "-mmacosx-version-min=10.15" + CGO_LDFLAGS: "-mmacosx-version-min=10.15" + MACOSX_DEPLOYMENT_TARGET: "10.15" + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + build:universal: + summary: Builds darwin universal binary (arm64 + amd64) + deps: + - task: build + vars: + ARCH: amd64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" + - task: build + vars: + ARCH: arm64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + cmds: + - lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + - rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + + package: + summary: Packages a production build of the application into a `.app` bundle + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:app:bundle + + package:universal: + summary: Packages darwin universal binary (arm64 + amd64) + deps: + - task: build:universal + cmds: + - task: create:app:bundle + + + create:app:bundle: + summary: Creates an `.app` bundle + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS + - cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app + + run: + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS + - cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app + - '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}' diff --git a/v3/examples/ios/build/darwin/icons.icns b/v3/examples/ios/build/darwin/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/ios/build/darwin/icons.icns differ diff --git a/v3/examples/ios/build/ios/Assets.xcassets b/v3/examples/ios/build/ios/Assets.xcassets new file mode 100644 index 000000000..46fbb8787 --- /dev/null +++ b/v3/examples/ios/build/ios/Assets.xcassets @@ -0,0 +1,116 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "images" : [ + { + "filename" : "icon-20@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-20@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "icon-29@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-29@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "icon-40@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-40@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "icon-60@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "icon-60@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "icon-20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "icon-20@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "icon-29@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "icon-40@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "icon-76@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "icon-83.5@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon-1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ] +} \ No newline at end of file diff --git a/v3/examples/ios/build/ios/Info.dev.plist b/v3/examples/ios/build/ios/Info.dev.plist new file mode 100644 index 000000000..89f0b201e --- /dev/null +++ b/v3/examples/ios/build/ios/Info.dev.plist @@ -0,0 +1,62 @@ + + + + + CFBundleExecutable + ios + CFBundleIdentifier + com.wails.ios.dev + CFBundleName + My Product (Dev) + CFBundleDisplayName + My Product (Dev) + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.1.0-dev + CFBundleVersion + 0.1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 15.0 + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + + + + WailsDevelopmentMode + + + NSHumanReadableCopyright + © now, My Company + + + CFBundleGetInfoString + This is a comment + + + \ No newline at end of file diff --git a/v3/examples/ios/build/ios/Info.plist b/v3/examples/ios/build/ios/Info.plist new file mode 100644 index 000000000..68d63a232 --- /dev/null +++ b/v3/examples/ios/build/ios/Info.plist @@ -0,0 +1,59 @@ + + + + + CFBundleExecutable + ios + CFBundleIdentifier + com.wails.ios + CFBundleName + My Product + CFBundleDisplayName + My Product + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.1.0 + CFBundleVersion + 0.1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 15.0 + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + + + + NSHumanReadableCopyright + © now, My Company + + + CFBundleGetInfoString + This is a comment + + + \ No newline at end of file diff --git a/v3/examples/ios/build/ios/LaunchScreen.storyboard b/v3/examples/ios/build/ios/LaunchScreen.storyboard new file mode 100644 index 000000000..eac48ae35 --- /dev/null +++ b/v3/examples/ios/build/ios/LaunchScreen.storyboard @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/v3/examples/ios/build/ios/Taskfile.yml b/v3/examples/ios/build/ios/Taskfile.yml new file mode 100644 index 000000000..bf1502a71 --- /dev/null +++ b/v3/examples/ios/build/ios/Taskfile.yml @@ -0,0 +1,282 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +vars: + BUNDLE_ID: '{{.BUNDLE_ID | default "com.wails.app"}}' + SDK_PATH: + sh: xcrun --sdk iphonesimulator --show-sdk-path + +tasks: + install:deps: + summary: Check and install iOS development dependencies + cmds: + - go run build/ios/scripts/deps/install_deps.go + env: + TASK_FORCE_YES: '{{if .YES}}true{{else}}false{{end}}' + prompt: This will check and install iOS development dependencies. Continue? + + # Note: Bindings generation may show CGO warnings for iOS C imports. + # These warnings are harmless and don't affect the generated bindings, + # as the generator only needs to parse Go types, not C implementations. + build: + summary: Creates a build of the application for iOS + deps: + - task: generate:ios:overlay + - task: generate:ios:xcode + - task: common:go:mod:tidy + - task: generate:ios:bindings + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - echo "Building iOS app {{.APP_NAME}}..." + - go build -buildmode=c-archive -overlay build/ios/xcode/overlay.json {{.BUILD_FLAGS}} -o {{.OUTPUT}}.a + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production,ios -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags ios,debug -buildvcs=false -gcflags=all="-l"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + env: + GOOS: ios + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default "arm64"}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + CGO_CFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator -mios-simulator-version-min=15.0' + CGO_LDFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator' + + compile:objc: + summary: Compile Objective-C iOS wrapper + cmds: + - xcrun -sdk iphonesimulator clang -target arm64-apple-ios15.0-simulator -isysroot {{.SDK_PATH}} -framework Foundation -framework UIKit -framework WebKit -o {{.BIN_DIR}}/{{.APP_NAME}} build/ios/main.m + - codesign --force --sign - {{.BIN_DIR}}/{{.APP_NAME}} + + package: + summary: Packages a production build of the application into a `.app` bundle + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:app:bundle + + create:app:bundle: + summary: Creates an iOS `.app` bundle + cmds: + - rm -rf {{.BIN_DIR}}/{{.APP_NAME}}.app + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/ + - cp build/ios/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/ + - | + # Compile asset catalog and embed icons in the app bundle + APP_BUNDLE="{{.BIN_DIR}}/{{.APP_NAME}}.app" + AC_IN="build/ios/xcode/main/Assets.xcassets" + if [ -d "$AC_IN" ]; then + TMP_AC=$(mktemp -d) + xcrun actool \ + --compile "$TMP_AC" \ + --app-icon AppIcon \ + --platform iphonesimulator \ + --minimum-deployment-target 15.0 \ + --product-type com.apple.product-type.application \ + --target-device iphone \ + --target-device ipad \ + --output-partial-info-plist "$APP_BUNDLE/assetcatalog_generated_info.plist" \ + "$AC_IN" + if [ -f "$TMP_AC/Assets.car" ]; then + cp -f "$TMP_AC/Assets.car" "$APP_BUNDLE/Assets.car" + fi + rm -rf "$TMP_AC" + if [ -f "$APP_BUNDLE/assetcatalog_generated_info.plist" ]; then + /usr/libexec/PlistBuddy -c "Merge $APP_BUNDLE/assetcatalog_generated_info.plist" "$APP_BUNDLE/Info.plist" || true + fi + fi + - codesign --force --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app + + deploy-simulator: + summary: Deploy to iOS Simulator + deps: [package] + cmds: + - xcrun simctl terminate booted {{.BUNDLE_ID}} 2>/dev/null || true + - xcrun simctl uninstall booted {{.BUNDLE_ID}} 2>/dev/null || true + - xcrun simctl install booted {{.BIN_DIR}}/{{.APP_NAME}}.app + - xcrun simctl launch booted {{.BUNDLE_ID}} + + compile:ios: + summary: Compile the iOS executable from Go archive and main.m + deps: + - task: build + cmds: + - | + MAIN_M=build/ios/xcode/main/main.m + if [ ! -f "$MAIN_M" ]; then + MAIN_M=build/ios/main.m + fi + xcrun -sdk iphonesimulator clang \ + -target arm64-apple-ios15.0-simulator \ + -isysroot {{.SDK_PATH}} \ + -framework Foundation -framework UIKit -framework WebKit \ + -framework Security -framework CoreFoundation \ + -lresolv \ + -o {{.BIN_DIR}}/{{.APP_NAME | lower}} \ + "$MAIN_M" {{.BIN_DIR}}/{{.APP_NAME}}.a + + generate:ios:bindings: + internal: true + summary: Generates bindings for iOS with proper CGO flags + sources: + - "**/*.go" + - go.mod + - go.sum + generates: + - frontend/bindings/**/* + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true + env: + GOOS: ios + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default "arm64"}}' + CGO_CFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator -mios-simulator-version-min=15.0' + CGO_LDFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator' + + ensure-simulator: + internal: true + summary: Ensure iOS Simulator is running and booted + silent: true + cmds: + - | + if ! xcrun simctl list devices booted | grep -q "Booted"; then + echo "Starting iOS Simulator..." + # Get first available iPhone device + DEVICE_ID=$(xcrun simctl list devices available | grep "iPhone" | head -1 | grep -o "[A-F0-9-]\{36\}" || true) + if [ -z "$DEVICE_ID" ]; then + echo "No iPhone simulator found. Creating one..." + RUNTIME=$(xcrun simctl list runtimes | grep iOS | tail -1 | awk '{print $NF}') + DEVICE_ID=$(xcrun simctl create "iPhone 15 Pro" "iPhone 15 Pro" "$RUNTIME") + fi + # Boot the device + echo "Booting device $DEVICE_ID..." + xcrun simctl boot "$DEVICE_ID" 2>/dev/null || true + # Open Simulator app + open -a Simulator + # Wait for boot (max 30 seconds) + for i in {1..30}; do + if xcrun simctl list devices booted | grep -q "Booted"; then + echo "Simulator booted successfully" + break + fi + sleep 1 + done + # Final check + if ! xcrun simctl list devices booted | grep -q "Booted"; then + echo "Failed to boot simulator after 30 seconds" + exit 1 + fi + fi + preconditions: + - sh: command -v xcrun + msg: "xcrun not found. Please run 'wails3 task ios:install:deps' to install iOS development dependencies" + + generate:ios:overlay: + internal: true + summary: Generate Go build overlay and iOS shim + sources: + - build/config.yml + generates: + - build/ios/xcode/overlay.json + - build/ios/xcode/gen/main_ios.gen.go + cmds: + - wails3 ios overlay:gen -out build/ios/xcode/overlay.json -config build/config.yml + + generate:ios:xcode: + internal: true + summary: Generate iOS Xcode project structure and assets + sources: + - build/config.yml + - build/appicon.png + generates: + - build/ios/xcode/main/main.m + - build/ios/xcode/main/Assets.xcassets/**/* + - build/ios/xcode/project.pbxproj + cmds: + - wails3 ios xcode:gen -outdir build/ios/xcode -config build/config.yml + + run: + summary: Run the application in iOS Simulator + deps: + - task: ensure-simulator + - task: compile:ios + cmds: + - rm -rf {{.BIN_DIR}}/{{.APP_NAME}}.dev.app + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app + - cp {{.BIN_DIR}}/{{.APP_NAME | lower}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/{{.APP_NAME | lower}} + - cp build/ios/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Info.plist + - | + # Compile asset catalog and embed icons for dev bundle + APP_BUNDLE="{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" + AC_IN="build/ios/xcode/main/Assets.xcassets" + if [ -d "$AC_IN" ]; then + TMP_AC=$(mktemp -d) + xcrun actool \ + --compile "$TMP_AC" \ + --app-icon AppIcon \ + --platform iphonesimulator \ + --minimum-deployment-target 15.0 \ + --product-type com.apple.product-type.application \ + --target-device iphone \ + --target-device ipad \ + --output-partial-info-plist "$APP_BUNDLE/assetcatalog_generated_info.plist" \ + "$AC_IN" + if [ -f "$TMP_AC/Assets.car" ]; then + cp -f "$TMP_AC/Assets.car" "$APP_BUNDLE/Assets.car" + fi + rm -rf "$TMP_AC" + if [ -f "$APP_BUNDLE/assetcatalog_generated_info.plist" ]; then + /usr/libexec/PlistBuddy -c "Merge $APP_BUNDLE/assetcatalog_generated_info.plist" "$APP_BUNDLE/Info.plist" || true + fi + fi + - codesign --force --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app + - xcrun simctl terminate booted com.wails.{{.APP_NAME | lower}}.dev 2>/dev/null || true + - xcrun simctl uninstall booted com.wails.{{.APP_NAME | lower}}.dev 2>/dev/null || true + - xcrun simctl install booted {{.BIN_DIR}}/{{.APP_NAME}}.dev.app + - xcrun simctl launch booted com.wails.{{.APP_NAME | lower}}.dev + + xcode: + summary: Open the generated Xcode project for this app + cmds: + - task: generate:ios:xcode + - open build/ios/xcode/main.xcodeproj + + logs: + summary: Stream iOS Simulator logs filtered to this app + cmds: + - | + xcrun simctl spawn booted log stream \ + --level debug \ + --style compact \ + --predicate 'senderImagePath CONTAINS[c] "{{.APP_NAME | lower}}.app/" OR composedMessage CONTAINS[c] "{{.APP_NAME | lower}}" OR eventMessage CONTAINS[c] "{{.APP_NAME | lower}}" OR process == "{{.APP_NAME | lower}}" OR category CONTAINS[c] "{{.APP_NAME | lower}}"' + + logs:dev: + summary: Stream logs for the dev bundle (used by `task ios:run`) + cmds: + - | + xcrun simctl spawn booted log stream \ + --level debug \ + --style compact \ + --predicate 'senderImagePath CONTAINS[c] ".dev.app/" OR subsystem == "com.wails.{{.APP_NAME | lower}}.dev" OR process == "{{.APP_NAME | lower}}"' + + logs:wide: + summary: Wide log stream to help discover the exact process/bundle identifiers + cmds: + - | + xcrun simctl spawn booted log stream \ + --level debug \ + --style compact \ + --predicate 'senderImagePath CONTAINS[c] ".app/"' \ No newline at end of file diff --git a/v3/examples/ios/build/ios/app_options_default.go b/v3/examples/ios/build/ios/app_options_default.go new file mode 100644 index 000000000..04e4f1bc9 --- /dev/null +++ b/v3/examples/ios/build/ios/app_options_default.go @@ -0,0 +1,10 @@ +//go:build !ios + +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +// modifyOptionsForIOS is a no-op on non-iOS platforms +func modifyOptionsForIOS(opts *application.Options) { + // No modifications needed for non-iOS platforms +} \ No newline at end of file diff --git a/v3/examples/ios/build/ios/app_options_ios.go b/v3/examples/ios/build/ios/app_options_ios.go new file mode 100644 index 000000000..565fbaad6 --- /dev/null +++ b/v3/examples/ios/build/ios/app_options_ios.go @@ -0,0 +1,20 @@ +//go:build ios + +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +// modifyOptionsForIOS adjusts the application options for iOS +func modifyOptionsForIOS(opts *application.Options) { + // Disable signal handlers on iOS to prevent crashes + opts.DisableDefaultSignalHandler = true + + // Enable native UITabBar in the iOS example by default + opts.IOS.EnableNativeTabs = true + // Configure example tab items (titles + SF Symbols) + opts.IOS.NativeTabsItems = []application.NativeTabItem{ + {Title: "Bindings", SystemImage: "link"}, + {Title: "Go Runtime", SystemImage: "gearshape"}, + {Title: "JS Runtime", SystemImage: "chevron.left.slash.chevron.right"}, + } +} \ No newline at end of file diff --git a/v3/examples/ios/build/ios/build.sh b/v3/examples/ios/build/ios/build.sh new file mode 100644 index 000000000..fbb47a673 --- /dev/null +++ b/v3/examples/ios/build/ios/build.sh @@ -0,0 +1,72 @@ +#!/bin/bash +set -e + +# Build configuration +APP_NAME="ios" +BUNDLE_ID="com.wails.ios" +VERSION="0.1.0" +BUILD_NUMBER="0.1.0" +BUILD_DIR="build/ios" +TARGET="simulator" + +echo "Building iOS app: $APP_NAME" +echo "Bundle ID: $BUNDLE_ID" +echo "Version: $VERSION ($BUILD_NUMBER)" +echo "Target: $TARGET" + +# Ensure build directory exists +mkdir -p "$BUILD_DIR" + +# Determine SDK and target architecture +if [ "$TARGET" = "simulator" ]; then + SDK="iphonesimulator" + ARCH="arm64-apple-ios15.0-simulator" +elif [ "$TARGET" = "device" ]; then + SDK="iphoneos" + ARCH="arm64-apple-ios15.0" +else + echo "Unknown target: $TARGET" + exit 1 +fi + +# Get SDK path +SDK_PATH=$(xcrun --sdk $SDK --show-sdk-path) + +# Compile the application +echo "Compiling with SDK: $SDK" +xcrun -sdk $SDK clang \ + -target $ARCH \ + -isysroot "$SDK_PATH" \ + -framework Foundation \ + -framework UIKit \ + -framework WebKit \ + -framework CoreGraphics \ + -o "$BUILD_DIR/$APP_NAME" \ + "$BUILD_DIR/main.m" + +# Create app bundle +echo "Creating app bundle..." +APP_BUNDLE="$BUILD_DIR/$APP_NAME.app" +rm -rf "$APP_BUNDLE" +mkdir -p "$APP_BUNDLE" + +# Move executable +mv "$BUILD_DIR/$APP_NAME" "$APP_BUNDLE/" + +# Copy Info.plist +cp "$BUILD_DIR/Info.plist" "$APP_BUNDLE/" + +# Sign the app +echo "Signing app..." +codesign --force --sign - "$APP_BUNDLE" + +echo "Build complete: $APP_BUNDLE" + +# Deploy to simulator if requested +if [ "$TARGET" = "simulator" ]; then + echo "Deploying to simulator..." + xcrun simctl terminate booted "$BUNDLE_ID" 2>/dev/null || true + xcrun simctl install booted "$APP_BUNDLE" + xcrun simctl launch booted "$BUNDLE_ID" + echo "App launched on simulator" +fi \ No newline at end of file diff --git a/v3/examples/ios/build/ios/entitlements.plist b/v3/examples/ios/build/ios/entitlements.plist new file mode 100644 index 000000000..cc5d9582b --- /dev/null +++ b/v3/examples/ios/build/ios/entitlements.plist @@ -0,0 +1,21 @@ + + + + + + get-task-allow + + + + com.apple.security.app-sandbox + + + + com.apple.security.network.client + + + + com.apple.security.files.user-selected.read-only + + + \ No newline at end of file diff --git a/v3/examples/ios/build/ios/icon.png b/v3/examples/ios/build/ios/icon.png new file mode 100644 index 000000000..be7d59173 --- /dev/null +++ b/v3/examples/ios/build/ios/icon.png @@ -0,0 +1,3 @@ +# iOS Icon Placeholder +# This file should be replaced with the actual app icon (1024x1024 PNG) +# The build process will generate all required icon sizes from this base icon \ No newline at end of file diff --git a/v3/examples/ios/build/ios/main.m b/v3/examples/ios/build/ios/main.m new file mode 100644 index 000000000..0f2e7e8e1 --- /dev/null +++ b/v3/examples/ios/build/ios/main.m @@ -0,0 +1,22 @@ +// Minimal bootstrap: delegate comes from Go archive (WailsAppDelegate) +#import +#include + +// External Go initialization function from the c-archive (declare before use) +extern void WailsIOSMain(); + +int main(int argc, char * argv[]) { + @autoreleasepool { + // Disable buffering so stdout/stderr from Go log.Printf flush immediately + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + + // Start Go runtime on a background queue to avoid blocking main thread/UI + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ + WailsIOSMain(); + }); + + // Run UIApplicationMain using WailsAppDelegate provided by the Go archive + return UIApplicationMain(argc, argv, nil, @"WailsAppDelegate"); + } +} \ No newline at end of file diff --git a/v3/examples/ios/build/ios/main_ios.go b/v3/examples/ios/build/ios/main_ios.go new file mode 100644 index 000000000..b75a40321 --- /dev/null +++ b/v3/examples/ios/build/ios/main_ios.go @@ -0,0 +1,24 @@ +//go:build ios + +package main + +import ( + "C" +) + +// For iOS builds, we need to export a function that can be called from Objective-C +// This wrapper allows us to keep the original main.go unmodified + +//export WailsIOSMain +func WailsIOSMain() { + // DO NOT lock the goroutine to the current OS thread on iOS! + // This causes signal handling issues: + // "signal 16 received on thread with no signal stack" + // "fatal error: non-Go code disabled sigaltstack" + // iOS apps run in a sandboxed environment where the Go runtime's + // signal handling doesn't work the same way as desktop platforms. + + // Call the actual main function from main.go + // This ensures all the user's code is executed + main() +} \ No newline at end of file diff --git a/v3/examples/ios/build/ios/project.pbxproj b/v3/examples/ios/build/ios/project.pbxproj new file mode 100644 index 000000000..627ab5b17 --- /dev/null +++ b/v3/examples/ios/build/ios/project.pbxproj @@ -0,0 +1,222 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = {}; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + C0DEBEEF0000000000000001 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000002 /* main.m */; }; + C0DEBEEF00000000000000F1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000101 /* UIKit.framework */; }; + C0DEBEEF00000000000000F2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000102 /* Foundation.framework */; }; + C0DEBEEF00000000000000F3 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000103 /* WebKit.framework */; }; + C0DEBEEF00000000000000F4 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000104 /* Security.framework */; }; + C0DEBEEF00000000000000F5 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000105 /* CoreFoundation.framework */; }; + C0DEBEEF00000000000000F6 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000106 /* libresolv.tbd */; }; + C0DEBEEF00000000000000F7 /* My Product.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000107 /* My Product.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + C0DEBEEF0000000000000002 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + C0DEBEEF0000000000000003 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C0DEBEEF0000000000000004 /* My Product.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "My Product.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + C0DEBEEF0000000000000101 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000102 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000103 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000104 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000105 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000106 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.text-based-dylib-definition; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000107 /* My Product.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "My Product.a"; path = ../../../bin/My Product.a; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + C0DEBEEF0000000000000010 = { + isa = PBXGroup; + children = ( + C0DEBEEF0000000000000020 /* Products */, + C0DEBEEF0000000000000045 /* Frameworks */, + C0DEBEEF0000000000000030 /* main */, + ); + sourceTree = ""; + }; + C0DEBEEF0000000000000020 /* Products */ = { + isa = PBXGroup; + children = ( + C0DEBEEF0000000000000004 /* My Product.app */, + ); + name = Products; + sourceTree = ""; + }; + C0DEBEEF0000000000000030 /* main */ = { + isa = PBXGroup; + children = ( + C0DEBEEF0000000000000002 /* main.m */, + C0DEBEEF0000000000000003 /* Info.plist */, + ); + path = main; + sourceTree = SOURCE_ROOT; + }; + C0DEBEEF0000000000000045 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C0DEBEEF0000000000000101 /* UIKit.framework */, + C0DEBEEF0000000000000102 /* Foundation.framework */, + C0DEBEEF0000000000000103 /* WebKit.framework */, + C0DEBEEF0000000000000104 /* Security.framework */, + C0DEBEEF0000000000000105 /* CoreFoundation.framework */, + C0DEBEEF0000000000000106 /* libresolv.tbd */, + C0DEBEEF0000000000000107 /* My Product.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + C0DEBEEF0000000000000040 /* My Product */ = { + isa = PBXNativeTarget; + buildConfigurationList = C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "My Product" */; + buildPhases = ( + C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */, + C0DEBEEF0000000000000050 /* Sources */, + C0DEBEEF0000000000000056 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "My Product"; + productName = "My Product"; + productReference = C0DEBEEF0000000000000004 /* My Product.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C0DEBEEF0000000000000060 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1500; + ORGANIZATIONNAME = "My Company"; + TargetAttributes = { + C0DEBEEF0000000000000040 = { + CreatedOnToolsVersion = 15.0; + }; + }; + }; + buildConfigurationList = C0DEBEEF0000000000000080 /* Build configuration list for PBXProject "main" */; + compatibilityVersion = "Xcode 15.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = C0DEBEEF0000000000000010; + productRefGroup = C0DEBEEF0000000000000020 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C0DEBEEF0000000000000040 /* My Product */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXFrameworksBuildPhase section */ + C0DEBEEF0000000000000056 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C0DEBEEF00000000000000F7 /* My Product.a in Frameworks */, + C0DEBEEF00000000000000F1 /* UIKit.framework in Frameworks */, + C0DEBEEF00000000000000F2 /* Foundation.framework in Frameworks */, + C0DEBEEF00000000000000F3 /* WebKit.framework in Frameworks */, + C0DEBEEF00000000000000F4 /* Security.framework in Frameworks */, + C0DEBEEF00000000000000F5 /* CoreFoundation.framework in Frameworks */, + C0DEBEEF00000000000000F6 /* libresolv.tbd in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Prebuild: Wails Go Archive"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\nAPP_ROOT=\"${PROJECT_DIR}/../../..\"\nSDK_PATH=$(xcrun --sdk iphonesimulator --show-sdk-path)\nexport GOOS=ios\nexport GOARCH=arm64\nexport CGO_ENABLED=1\nexport CGO_CFLAGS=\"-isysroot ${SDK_PATH} -target arm64-apple-ios15.0-simulator -mios-simulator-version-min=15.0\"\nexport CGO_LDFLAGS=\"-isysroot ${SDK_PATH} -target arm64-apple-ios15.0-simulator\"\ncd \"${APP_ROOT}\"\n# Ensure overlay exists\nif [ ! -f build/ios/xcode/overlay.json ]; then\n wails3 ios overlay:gen -out build/ios/xcode/overlay.json -config build/config.yml || true\nfi\n# Build Go c-archive if missing or older than sources\nif [ ! -f bin/My Product.a ]; then\n echo \"Building Go c-archive...\"\n go build -buildmode=c-archive -overlay build/ios/xcode/overlay.json -o bin/My Product.a\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C0DEBEEF0000000000000050 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C0DEBEEF0000000000000001 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + C0DEBEEF0000000000000090 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = main/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.wails.ios"; + PRODUCT_NAME = "My Product"; + CODE_SIGNING_ALLOWED = NO; + SDKROOT = iphonesimulator; + }; + name = Debug; + }; + C0DEBEEF00000000000000A0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = main/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.wails.ios"; + PRODUCT_NAME = "My Product"; + CODE_SIGNING_ALLOWED = NO; + SDKROOT = iphonesimulator; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "My Product" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C0DEBEEF0000000000000090 /* Debug */, + C0DEBEEF00000000000000A0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + C0DEBEEF0000000000000080 /* Build configuration list for PBXProject "main" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C0DEBEEF0000000000000090 /* Debug */, + C0DEBEEF00000000000000A0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = C0DEBEEF0000000000000060 /* Project object */; +} diff --git a/v3/examples/ios/build/ios/scripts/deps/install_deps.go b/v3/examples/ios/build/ios/scripts/deps/install_deps.go new file mode 100644 index 000000000..88ed47a4a --- /dev/null +++ b/v3/examples/ios/build/ios/scripts/deps/install_deps.go @@ -0,0 +1,319 @@ +// install_deps.go - iOS development dependency checker +// This script checks for required iOS development tools. +// It's designed to be portable across different shells by using Go instead of shell scripts. +// +// Usage: +// go run install_deps.go # Interactive mode +// TASK_FORCE_YES=true go run install_deps.go # Auto-accept prompts +// CI=true go run install_deps.go # CI mode (auto-accept) + +package main + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "strings" +) + +type Dependency struct { + Name string + CheckFunc func() (bool, string) // Returns (success, details) + Required bool + InstallCmd []string + InstallMsg string + SuccessMsg string + FailureMsg string +} + +func main() { + fmt.Println("Checking iOS development dependencies...") + fmt.Println("=" + strings.Repeat("=", 50)) + fmt.Println() + + hasErrors := false + dependencies := []Dependency{ + { + Name: "Xcode", + CheckFunc: func() (bool, string) { + // Check if xcodebuild exists + if !checkCommand([]string{"xcodebuild", "-version"}) { + return false, "" + } + // Get version info + out, err := exec.Command("xcodebuild", "-version").Output() + if err != nil { + return false, "" + } + lines := strings.Split(string(out), "\n") + if len(lines) > 0 { + return true, strings.TrimSpace(lines[0]) + } + return true, "" + }, + Required: true, + InstallMsg: "Please install Xcode from the Mac App Store:\n https://apps.apple.com/app/xcode/id497799835\n Xcode is REQUIRED for iOS development (includes iOS SDKs, simulators, and frameworks)", + SuccessMsg: "✅ Xcode found", + FailureMsg: "❌ Xcode not found (REQUIRED)", + }, + { + Name: "Xcode Developer Path", + CheckFunc: func() (bool, string) { + // Check if xcode-select points to a valid Xcode path + out, err := exec.Command("xcode-select", "-p").Output() + if err != nil { + return false, "xcode-select not configured" + } + path := strings.TrimSpace(string(out)) + + // Check if path exists and is in Xcode.app + if _, err := os.Stat(path); err != nil { + return false, "Invalid Xcode path" + } + + // Verify it's pointing to Xcode.app (not just Command Line Tools) + if !strings.Contains(path, "Xcode.app") { + return false, fmt.Sprintf("Points to %s (should be Xcode.app)", path) + } + + return true, path + }, + Required: true, + InstallCmd: []string{"sudo", "xcode-select", "-s", "/Applications/Xcode.app/Contents/Developer"}, + InstallMsg: "Xcode developer path needs to be configured", + SuccessMsg: "✅ Xcode developer path configured", + FailureMsg: "❌ Xcode developer path not configured correctly", + }, + { + Name: "iOS SDK", + CheckFunc: func() (bool, string) { + // Get the iOS Simulator SDK path + cmd := exec.Command("xcrun", "--sdk", "iphonesimulator", "--show-sdk-path") + output, err := cmd.Output() + if err != nil { + return false, "Cannot find iOS SDK" + } + sdkPath := strings.TrimSpace(string(output)) + + // Check if the SDK path exists + if _, err := os.Stat(sdkPath); err != nil { + return false, "iOS SDK path not found" + } + + // Check for UIKit framework (essential for iOS development) + uikitPath := fmt.Sprintf("%s/System/Library/Frameworks/UIKit.framework", sdkPath) + if _, err := os.Stat(uikitPath); err != nil { + return false, "UIKit.framework not found" + } + + // Get SDK version + versionCmd := exec.Command("xcrun", "--sdk", "iphonesimulator", "--show-sdk-version") + versionOut, _ := versionCmd.Output() + version := strings.TrimSpace(string(versionOut)) + + return true, fmt.Sprintf("iOS %s SDK", version) + }, + Required: true, + InstallMsg: "iOS SDK comes with Xcode. Please ensure Xcode is properly installed.", + SuccessMsg: "✅ iOS SDK found with UIKit framework", + FailureMsg: "❌ iOS SDK not found or incomplete", + }, + { + Name: "iOS Simulator Runtime", + CheckFunc: func() (bool, string) { + if !checkCommand([]string{"xcrun", "simctl", "help"}) { + return false, "" + } + // Check if we can list runtimes + out, err := exec.Command("xcrun", "simctl", "list", "runtimes").Output() + if err != nil { + return false, "Cannot access simulator" + } + // Count iOS runtimes + lines := strings.Split(string(out), "\n") + count := 0 + var versions []string + for _, line := range lines { + if strings.Contains(line, "iOS") && !strings.Contains(line, "unavailable") { + count++ + // Extract version number + if parts := strings.Fields(line); len(parts) > 2 { + for _, part := range parts { + if strings.HasPrefix(part, "(") && strings.HasSuffix(part, ")") { + versions = append(versions, strings.Trim(part, "()")) + break + } + } + } + } + } + if count > 0 { + return true, fmt.Sprintf("%d runtime(s): %s", count, strings.Join(versions, ", ")) + } + return false, "No iOS runtimes installed" + }, + Required: true, + InstallMsg: "iOS Simulator runtimes come with Xcode. You may need to download them:\n Xcode → Settings → Platforms → iOS", + SuccessMsg: "✅ iOS Simulator runtime available", + FailureMsg: "❌ iOS Simulator runtime not available", + }, + } + + // Check each dependency + for _, dep := range dependencies { + success, details := dep.CheckFunc() + if success { + msg := dep.SuccessMsg + if details != "" { + msg = fmt.Sprintf("%s (%s)", dep.SuccessMsg, details) + } + fmt.Println(msg) + } else { + fmt.Println(dep.FailureMsg) + if details != "" { + fmt.Printf(" Details: %s\n", details) + } + if dep.Required { + hasErrors = true + if len(dep.InstallCmd) > 0 { + fmt.Println() + fmt.Println(" " + dep.InstallMsg) + fmt.Printf(" Fix command: %s\n", strings.Join(dep.InstallCmd, " ")) + if promptUser("Do you want to run this command?") { + fmt.Println("Running command...") + cmd := exec.Command(dep.InstallCmd[0], dep.InstallCmd[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + if err := cmd.Run(); err != nil { + fmt.Printf("Command failed: %v\n", err) + os.Exit(1) + } + fmt.Println("✅ Command completed. Please run this check again.") + } else { + fmt.Printf(" Please run manually: %s\n", strings.Join(dep.InstallCmd, " ")) + } + } else { + fmt.Println(" " + dep.InstallMsg) + } + } + } + } + + // Check for iPhone simulators + fmt.Println() + fmt.Println("Checking for iPhone simulator devices...") + if !checkCommand([]string{"xcrun", "simctl", "list", "devices"}) { + fmt.Println("❌ Cannot check for iPhone simulators") + hasErrors = true + } else { + out, err := exec.Command("xcrun", "simctl", "list", "devices").Output() + if err != nil { + fmt.Println("❌ Failed to list simulator devices") + hasErrors = true + } else if !strings.Contains(string(out), "iPhone") { + fmt.Println("⚠️ No iPhone simulator devices found") + fmt.Println() + + // Get the latest iOS runtime + runtimeOut, err := exec.Command("xcrun", "simctl", "list", "runtimes").Output() + if err != nil { + fmt.Println(" Failed to get iOS runtimes:", err) + } else { + lines := strings.Split(string(runtimeOut), "\n") + var latestRuntime string + for _, line := range lines { + if strings.Contains(line, "iOS") && !strings.Contains(line, "unavailable") { + // Extract runtime identifier + parts := strings.Fields(line) + if len(parts) > 0 { + latestRuntime = parts[len(parts)-1] + } + } + } + + if latestRuntime == "" { + fmt.Println(" No iOS runtime found. Please install iOS simulators in Xcode:") + fmt.Println(" Xcode → Settings → Platforms → iOS") + } else { + fmt.Println(" Would you like to create an iPhone 15 Pro simulator?") + createCmd := []string{"xcrun", "simctl", "create", "iPhone 15 Pro", "iPhone 15 Pro", latestRuntime} + fmt.Printf(" Command: %s\n", strings.Join(createCmd, " ")) + if promptUser("Create simulator?") { + cmd := exec.Command(createCmd[0], createCmd[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + fmt.Printf(" Failed to create simulator: %v\n", err) + } else { + fmt.Println(" ✅ iPhone 15 Pro simulator created") + } + } else { + fmt.Println(" Skipping simulator creation") + fmt.Printf(" Create manually: %s\n", strings.Join(createCmd, " ")) + } + } + } + } else { + // Count iPhone devices + count := 0 + lines := strings.Split(string(out), "\n") + for _, line := range lines { + if strings.Contains(line, "iPhone") && !strings.Contains(line, "unavailable") { + count++ + } + } + fmt.Printf("✅ %d iPhone simulator device(s) available\n", count) + } + } + + // Final summary + fmt.Println() + fmt.Println("=" + strings.Repeat("=", 50)) + if hasErrors { + fmt.Println("❌ Some required dependencies are missing or misconfigured.") + fmt.Println() + fmt.Println("Quick setup guide:") + fmt.Println("1. Install Xcode from Mac App Store (if not installed)") + fmt.Println("2. Open Xcode once and agree to the license") + fmt.Println("3. Install additional components when prompted") + fmt.Println("4. Run: sudo xcode-select -s /Applications/Xcode.app/Contents/Developer") + fmt.Println("5. Download iOS simulators: Xcode → Settings → Platforms → iOS") + fmt.Println("6. Run this check again") + os.Exit(1) + } else { + fmt.Println("✅ All required dependencies are installed!") + fmt.Println(" You're ready for iOS development with Wails!") + } +} + +func checkCommand(args []string) bool { + if len(args) == 0 { + return false + } + cmd := exec.Command(args[0], args[1:]...) + cmd.Stdout = nil + cmd.Stderr = nil + err := cmd.Run() + return err == nil +} + +func promptUser(question string) bool { + // Check if we're in a non-interactive environment + if os.Getenv("CI") != "" || os.Getenv("TASK_FORCE_YES") == "true" { + fmt.Printf("%s [y/N]: y (auto-accepted)\n", question) + return true + } + + reader := bufio.NewReader(os.Stdin) + fmt.Printf("%s [y/N]: ", question) + + response, err := reader.ReadString('\n') + if err != nil { + return false + } + + response = strings.ToLower(strings.TrimSpace(response)) + return response == "y" || response == "yes" +} \ No newline at end of file diff --git a/v3/examples/ios/build/ios/xcode/gen/main_ios.gen.go b/v3/examples/ios/build/ios/xcode/gen/main_ios.gen.go new file mode 100644 index 000000000..b75a40321 --- /dev/null +++ b/v3/examples/ios/build/ios/xcode/gen/main_ios.gen.go @@ -0,0 +1,24 @@ +//go:build ios + +package main + +import ( + "C" +) + +// For iOS builds, we need to export a function that can be called from Objective-C +// This wrapper allows us to keep the original main.go unmodified + +//export WailsIOSMain +func WailsIOSMain() { + // DO NOT lock the goroutine to the current OS thread on iOS! + // This causes signal handling issues: + // "signal 16 received on thread with no signal stack" + // "fatal error: non-Go code disabled sigaltstack" + // iOS apps run in a sandboxed environment where the Go runtime's + // signal handling doesn't work the same way as desktop platforms. + + // Call the actual main function from main.go + // This ensures all the user's code is executed + main() +} \ No newline at end of file diff --git a/v3/examples/ios/build/ios/xcode/main.xcodeproj/project.pbxproj b/v3/examples/ios/build/ios/xcode/main.xcodeproj/project.pbxproj new file mode 100644 index 000000000..6db4c3928 --- /dev/null +++ b/v3/examples/ios/build/ios/xcode/main.xcodeproj/project.pbxproj @@ -0,0 +1,222 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = {}; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + C0DEBEEF0000000000000001 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000002 /* main.m */; }; + C0DEBEEF00000000000000F1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000101 /* UIKit.framework */; }; + C0DEBEEF00000000000000F2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000102 /* Foundation.framework */; }; + C0DEBEEF00000000000000F3 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000103 /* WebKit.framework */; }; + C0DEBEEF00000000000000F4 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000104 /* Security.framework */; }; + C0DEBEEF00000000000000F5 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000105 /* CoreFoundation.framework */; }; + C0DEBEEF00000000000000F6 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000106 /* libresolv.tbd */; }; + C0DEBEEF00000000000000F7 /* My Product.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000107 /* My Product.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + C0DEBEEF0000000000000002 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + C0DEBEEF0000000000000003 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C0DEBEEF0000000000000004 /* My Product.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "My Product.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + C0DEBEEF0000000000000101 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000102 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000103 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000104 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000105 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000106 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.text-based-dylib-definition; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000107 /* My Product.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "My Product.a"; path = ../../../bin/My Product.a; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + C0DEBEEF0000000000000010 = { + isa = PBXGroup; + children = ( + C0DEBEEF0000000000000020 /* Products */, + C0DEBEEF0000000000000045 /* Frameworks */, + C0DEBEEF0000000000000030 /* main */, + ); + sourceTree = ""; + }; + C0DEBEEF0000000000000020 /* Products */ = { + isa = PBXGroup; + children = ( + C0DEBEEF0000000000000004 /* My Product.app */, + ); + name = Products; + sourceTree = ""; + }; + C0DEBEEF0000000000000030 /* main */ = { + isa = PBXGroup; + children = ( + C0DEBEEF0000000000000002 /* main.m */, + C0DEBEEF0000000000000003 /* Info.plist */, + ); + path = main; + sourceTree = SOURCE_ROOT; + }; + C0DEBEEF0000000000000045 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C0DEBEEF0000000000000101 /* UIKit.framework */, + C0DEBEEF0000000000000102 /* Foundation.framework */, + C0DEBEEF0000000000000103 /* WebKit.framework */, + C0DEBEEF0000000000000104 /* Security.framework */, + C0DEBEEF0000000000000105 /* CoreFoundation.framework */, + C0DEBEEF0000000000000106 /* libresolv.tbd */, + C0DEBEEF0000000000000107 /* My Product.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + C0DEBEEF0000000000000040 /* My Product */ = { + isa = PBXNativeTarget; + buildConfigurationList = C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "My Product" */; + buildPhases = ( + C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */, + C0DEBEEF0000000000000050 /* Sources */, + C0DEBEEF0000000000000056 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "My Product"; + productName = "My Product"; + productReference = C0DEBEEF0000000000000004 /* My Product.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C0DEBEEF0000000000000060 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1500; + ORGANIZATIONNAME = "My Company"; + TargetAttributes = { + C0DEBEEF0000000000000040 = { + CreatedOnToolsVersion = 15.0; + }; + }; + }; + buildConfigurationList = C0DEBEEF0000000000000080 /* Build configuration list for PBXProject "main" */; + compatibilityVersion = "Xcode 15.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = C0DEBEEF0000000000000010; + productRefGroup = C0DEBEEF0000000000000020 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C0DEBEEF0000000000000040 /* My Product */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXFrameworksBuildPhase section */ + C0DEBEEF0000000000000056 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C0DEBEEF00000000000000F7 /* My Product.a in Frameworks */, + C0DEBEEF00000000000000F1 /* UIKit.framework in Frameworks */, + C0DEBEEF00000000000000F2 /* Foundation.framework in Frameworks */, + C0DEBEEF00000000000000F3 /* WebKit.framework in Frameworks */, + C0DEBEEF00000000000000F4 /* Security.framework in Frameworks */, + C0DEBEEF00000000000000F5 /* CoreFoundation.framework in Frameworks */, + C0DEBEEF00000000000000F6 /* libresolv.tbd in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Prebuild: Wails Go Archive"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\nAPP_ROOT=\"${PROJECT_DIR}/../../..\"\nSDK_PATH=$(xcrun --sdk iphonesimulator --show-sdk-path)\nexport GOOS=ios\nexport GOARCH=arm64\nexport CGO_ENABLED=1\nexport CGO_CFLAGS=\"-isysroot ${SDK_PATH} -target arm64-apple-ios15.0-simulator -mios-simulator-version-min=15.0\"\nexport CGO_LDFLAGS=\"-isysroot ${SDK_PATH} -target arm64-apple-ios15.0-simulator\"\ncd \"${APP_ROOT}\"\n# Ensure overlay exists\nif [ ! -f build/ios/xcode/overlay.json ]; then\n wails3 ios overlay:gen -out build/ios/xcode/overlay.json -config build/config.yml || true\nfi\n# Build Go c-archive if missing or older than sources\nif [ ! -f bin/My Product.a ]; then\n echo \"Building Go c-archive...\"\n go build -buildmode=c-archive -overlay build/ios/xcode/overlay.json -o bin/My Product.a\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C0DEBEEF0000000000000050 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C0DEBEEF0000000000000001 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + C0DEBEEF0000000000000090 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = main/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.mycompany.myproduct"; + PRODUCT_NAME = "My Product"; + CODE_SIGNING_ALLOWED = NO; + SDKROOT = iphonesimulator; + }; + name = Debug; + }; + C0DEBEEF00000000000000A0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = main/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.mycompany.myproduct"; + PRODUCT_NAME = "My Product"; + CODE_SIGNING_ALLOWED = NO; + SDKROOT = iphonesimulator; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "My Product" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C0DEBEEF0000000000000090 /* Debug */, + C0DEBEEF00000000000000A0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + C0DEBEEF0000000000000080 /* Build configuration list for PBXProject "main" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C0DEBEEF0000000000000090 /* Debug */, + C0DEBEEF00000000000000A0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = C0DEBEEF0000000000000060 /* Project object */; +} diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/Contents.json b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..46fbb8787 --- /dev/null +++ b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "images" : [ + { + "filename" : "icon-20@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-20@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "icon-29@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-29@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "icon-40@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-40@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "icon-60@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "icon-60@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "icon-20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "icon-20@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "icon-29@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "icon-40@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "icon-76@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "icon-83.5@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon-1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ] +} \ No newline at end of file diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-1024.png new file mode 100644 index 000000000..50d762121 Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-1024.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20.png new file mode 100644 index 000000000..8559d2b1d Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png new file mode 100644 index 000000000..0f2720a87 Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png new file mode 100644 index 000000000..aa0892d9e Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29.png new file mode 100644 index 000000000..21de36f8b Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png new file mode 100644 index 000000000..43f7766ae Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png new file mode 100644 index 000000000..db48d12db Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40.png new file mode 100644 index 000000000..0f2720a87 Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png new file mode 100644 index 000000000..b0c40d1b7 Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png new file mode 100644 index 000000000..a3c751376 Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png new file mode 100644 index 000000000..a3c751376 Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png new file mode 100644 index 000000000..fe6a86faf Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-76.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-76.png new file mode 100644 index 000000000..8ff321e60 Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-76.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png new file mode 100644 index 000000000..8e09d8adc Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png new file mode 100644 index 000000000..fdfb581c9 Binary files /dev/null and b/v3/examples/ios/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ diff --git a/v3/examples/ios/build/ios/xcode/main/Info.plist b/v3/examples/ios/build/ios/xcode/main/Info.plist new file mode 100644 index 000000000..c96f4f2ec --- /dev/null +++ b/v3/examples/ios/build/ios/xcode/main/Info.plist @@ -0,0 +1,59 @@ + + + + + CFBundleExecutable + wailsapp + CFBundleIdentifier + com.mycompany.myproduct + CFBundleName + My Product + CFBundleDisplayName + My Product + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.0.1 + CFBundleVersion + 0.0.1 + LSRequiresIPhoneOS + + MinimumOSVersion + 15.0 + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + + + + NSHumanReadableCopyright + (c) 2025, My Company + + + CFBundleGetInfoString + Some Product Comments + + + \ No newline at end of file diff --git a/v3/examples/ios/build/ios/xcode/main/LaunchScreen.storyboard b/v3/examples/ios/build/ios/xcode/main/LaunchScreen.storyboard new file mode 100644 index 000000000..d6178d6b5 --- /dev/null +++ b/v3/examples/ios/build/ios/xcode/main/LaunchScreen.storyboard @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/v3/examples/ios/build/ios/xcode/main/main.m b/v3/examples/ios/build/ios/xcode/main/main.m new file mode 100644 index 000000000..0f2e7e8e1 --- /dev/null +++ b/v3/examples/ios/build/ios/xcode/main/main.m @@ -0,0 +1,22 @@ +// Minimal bootstrap: delegate comes from Go archive (WailsAppDelegate) +#import +#include + +// External Go initialization function from the c-archive (declare before use) +extern void WailsIOSMain(); + +int main(int argc, char * argv[]) { + @autoreleasepool { + // Disable buffering so stdout/stderr from Go log.Printf flush immediately + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + + // Start Go runtime on a background queue to avoid blocking main thread/UI + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ + WailsIOSMain(); + }); + + // Run UIApplicationMain using WailsAppDelegate provided by the Go archive + return UIApplicationMain(argc, argv, nil, @"WailsAppDelegate"); + } +} \ No newline at end of file diff --git a/v3/examples/ios/build/ios/xcode/overlay.json b/v3/examples/ios/build/ios/xcode/overlay.json new file mode 100644 index 000000000..9ceefc4dc --- /dev/null +++ b/v3/examples/ios/build/ios/xcode/overlay.json @@ -0,0 +1,5 @@ +{ + "Replace": { + "/Users/leaanthony/test/wails/v3/examples/ios/main_ios.gen.go": "/Users/leaanthony/test/wails/v3/examples/ios/build/ios/xcode/gen/main_ios.gen.go" + } +} \ No newline at end of file diff --git a/v3/examples/ios/build/linux/Taskfile.yml b/v3/examples/ios/build/linux/Taskfile.yml new file mode 100644 index 000000000..87fd599cc --- /dev/null +++ b/v3/examples/ios/build/linux/Taskfile.yml @@ -0,0 +1,119 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Linux + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: linux + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application for Linux + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:appimage + - task: create:deb + - task: create:rpm + - task: create:aur + + create:appimage: + summary: Creates an AppImage + dir: build/linux/appimage + deps: + - task: build + vars: + PRODUCTION: "true" + - task: generate:dotdesktop + cmds: + - cp {{.APP_BINARY}} {{.APP_NAME}} + - cp ../../appicon.png appicon.png + - wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build + vars: + APP_NAME: '{{.APP_NAME}}' + APP_BINARY: '../../../bin/{{.APP_NAME}}' + ICON: '../../appicon.png' + DESKTOP_FILE: '../{{.APP_NAME}}.desktop' + OUTPUT_DIR: '../../../bin' + + create:deb: + summary: Creates a deb package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:deb + + create:rpm: + summary: Creates a rpm package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:rpm + + create:aur: + summary: Creates a arch linux packager package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:aur + + generate:deb: + summary: Creates a deb package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:rpm: + summary: Creates a rpm package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:aur: + summary: Creates a arch linux packager package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:dotdesktop: + summary: Generates a `.desktop` file + dir: build + cmds: + - mkdir -p {{.ROOT_DIR}}/build/linux/appimage + - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}" + vars: + APP_NAME: '{{.APP_NAME}}' + EXEC: '{{.APP_NAME}}' + ICON: '{{.APP_NAME}}' + CATEGORIES: 'Development;' + OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}' diff --git a/v3/examples/ios/build/linux/appimage/build.sh b/v3/examples/ios/build/linux/appimage/build.sh new file mode 100644 index 000000000..858f091ab --- /dev/null +++ b/v3/examples/ios/build/linux/appimage/build.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Copyright (c) 2018-Present Lea Anthony +# SPDX-License-Identifier: MIT + +# Fail script on any error +set -euxo pipefail + +# Define variables +APP_DIR="${APP_NAME}.AppDir" + +# Create AppDir structure +mkdir -p "${APP_DIR}/usr/bin" +cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/" +cp "${ICON_PATH}" "${APP_DIR}/" +cp "${DESKTOP_FILE}" "${APP_DIR}/" + +if [[ $(uname -m) == *x86_64* ]]; then + # Download linuxdeploy and make it executable + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + + # Run linuxdeploy to bundle the application + ./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage +else + # Download linuxdeploy and make it executable (arm64) + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage + chmod +x linuxdeploy-aarch64.AppImage + + # Run linuxdeploy to bundle the application (arm64) + ./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage +fi + +# Rename the generated AppImage +mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage" diff --git a/v3/examples/ios/build/linux/desktop b/v3/examples/ios/build/linux/desktop new file mode 100644 index 000000000..e9b30cf39 --- /dev/null +++ b/v3/examples/ios/build/linux/desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Version=1.0 +Name=My Product +Comment=My Product Description +# The Exec line includes %u to pass the URL to the application +Exec=/usr/local/bin/ios %u +Terminal=false +Type=Application +Icon=ios +Categories=Utility; +StartupWMClass=ios diff --git a/v3/examples/ios/build/linux/nfpm/nfpm.yaml b/v3/examples/ios/build/linux/nfpm/nfpm.yaml new file mode 100644 index 000000000..7b78433f4 --- /dev/null +++ b/v3/examples/ios/build/linux/nfpm/nfpm.yaml @@ -0,0 +1,67 @@ +# Feel free to remove those if you don't want/need to use them. +# Make sure to check the documentation at https://nfpm.goreleaser.com +# +# The lines below are called `modelines`. See `:help modeline` + +name: "ios" +arch: ${GOARCH} +platform: "linux" +version: "0.1.0" +section: "default" +priority: "extra" +maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> +description: "My Product Description" +vendor: "My Company" +homepage: "https://wails.io" +license: "MIT" +release: "1" + +contents: + - src: "./bin/ios" + dst: "/usr/local/bin/ios" + - src: "./build/appicon.png" + dst: "/usr/share/icons/hicolor/128x128/apps/ios.png" + - src: "./build/linux/ios.desktop" + dst: "/usr/share/applications/ios.desktop" + +# Default dependencies for Debian 12/Ubuntu 22.04+ with WebKit 4.1 +depends: + - libgtk-3-0 + - libwebkit2gtk-4.1-0 + +# Distribution-specific overrides for different package formats and WebKit versions +overrides: + # RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0) + rpm: + depends: + - gtk3 + - webkit2gtk4.1 + + # Arch Linux packages (WebKit 4.1) + archlinux: + depends: + - gtk3 + - webkit2gtk-4.1 + +# scripts section to ensure desktop database is updated after install +scripts: + postinstall: "./build/linux/nfpm/scripts/postinstall.sh" + # You can also add preremove, postremove if needed + # preremove: "./build/linux/nfpm/scripts/preremove.sh" + # postremove: "./build/linux/nfpm/scripts/postremove.sh" + +# replaces: +# - foobar +# provides: +# - bar +# depends: +# - gtk3 +# - libwebkit2gtk +# recommends: +# - whatever +# suggests: +# - something-else +# conflicts: +# - not-foo +# - not-bar +# changelog: "changelog.yaml" diff --git a/v3/examples/ios/build/linux/nfpm/scripts/postinstall.sh b/v3/examples/ios/build/linux/nfpm/scripts/postinstall.sh new file mode 100644 index 000000000..4bbb815a3 --- /dev/null +++ b/v3/examples/ios/build/linux/nfpm/scripts/postinstall.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# Update desktop database for .desktop file changes +# This makes the application appear in application menus and registers its capabilities. +if command -v update-desktop-database >/dev/null 2>&1; then + echo "Updating desktop database..." + update-desktop-database -q /usr/share/applications +else + echo "Warning: update-desktop-database command not found. Desktop file may not be immediately recognized." >&2 +fi + +# Update MIME database for custom URL schemes (x-scheme-handler) +# This ensures the system knows how to handle your custom protocols. +if command -v update-mime-database >/dev/null 2>&1; then + echo "Updating MIME database..." + update-mime-database -n /usr/share/mime +else + echo "Warning: update-mime-database command not found. Custom URL schemes may not be immediately recognized." >&2 +fi + +exit 0 diff --git a/v3/examples/ios/build/linux/nfpm/scripts/postremove.sh b/v3/examples/ios/build/linux/nfpm/scripts/postremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/ios/build/linux/nfpm/scripts/postremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/ios/build/linux/nfpm/scripts/preinstall.sh b/v3/examples/ios/build/linux/nfpm/scripts/preinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/ios/build/linux/nfpm/scripts/preinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/ios/build/linux/nfpm/scripts/preremove.sh b/v3/examples/ios/build/linux/nfpm/scripts/preremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/ios/build/linux/nfpm/scripts/preremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/ios/build/windows/Taskfile.yml b/v3/examples/ios/build/windows/Taskfile.yml new file mode 100644 index 000000000..19f137616 --- /dev/null +++ b/v3/examples/ios/build/windows/Taskfile.yml @@ -0,0 +1,98 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Windows + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - task: generate:syso + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe + - cmd: powershell Remove-item *.syso + platforms: [windows] + - cmd: rm -f *.syso + platforms: [linux, darwin] + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: windows + CGO_ENABLED: 0 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application + cmds: + - |- + if [ "{{.FORMAT | default "nsis"}}" = "msix" ]; then + task: create:msix:package + else + task: create:nsis:installer + fi + vars: + FORMAT: '{{.FORMAT | default "nsis"}}' + + generate:syso: + summary: Generates Windows `.syso` file + dir: build + cmds: + - wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso + vars: + ARCH: '{{.ARCH | default ARCH}}' + + create:nsis:installer: + summary: Creates an NSIS installer + dir: build/windows/nsis + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + # Create the Microsoft WebView2 bootstrapper if it doesn't exist + - wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis" + - makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi + vars: + ARCH: '{{.ARCH | default ARCH}}' + ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}' + + create:msix:package: + summary: Creates an MSIX package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - |- + wails3 tool msix \ + --config "{{.ROOT_DIR}}/wails.json" \ + --name "{{.APP_NAME}}" \ + --executable "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" \ + --arch "{{.ARCH}}" \ + --out "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}-{{.ARCH}}.msix" \ + {{if .CERT_PATH}}--cert "{{.CERT_PATH}}"{{end}} \ + {{if .PUBLISHER}}--publisher "{{.PUBLISHER}}"{{end}} \ + {{if .USE_MSIX_TOOL}}--use-msix-tool{{else}}--use-makeappx{{end}} + vars: + ARCH: '{{.ARCH | default ARCH}}' + CERT_PATH: '{{.CERT_PATH | default ""}}' + PUBLISHER: '{{.PUBLISHER | default ""}}' + USE_MSIX_TOOL: '{{.USE_MSIX_TOOL | default "false"}}' + + install:msix:tools: + summary: Installs tools required for MSIX packaging + cmds: + - wails3 tool msix-install-tools + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}.exe' diff --git a/v3/examples/ios/build/windows/icon.ico b/v3/examples/ios/build/windows/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/ios/build/windows/icon.ico differ diff --git a/v3/examples/ios/build/windows/info.json b/v3/examples/ios/build/windows/info.json new file mode 100644 index 000000000..850b2b5b0 --- /dev/null +++ b/v3/examples/ios/build/windows/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "0.1.0" + }, + "info": { + "0000": { + "ProductVersion": "0.1.0", + "CompanyName": "My Company", + "FileDescription": "My Product Description", + "LegalCopyright": "© now, My Company", + "ProductName": "My Product", + "Comments": "This is a comment" + } + } +} \ No newline at end of file diff --git a/v3/examples/ios/build/windows/msix/app_manifest.xml b/v3/examples/ios/build/windows/msix/app_manifest.xml new file mode 100644 index 000000000..ecf2506b3 --- /dev/null +++ b/v3/examples/ios/build/windows/msix/app_manifest.xml @@ -0,0 +1,52 @@ + + + + + + + My Product + My Company + My Product Description + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v3/examples/ios/build/windows/msix/template.xml b/v3/examples/ios/build/windows/msix/template.xml new file mode 100644 index 000000000..cab2f31e0 --- /dev/null +++ b/v3/examples/ios/build/windows/msix/template.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + false + My Product + My Company + My Product Description + Assets\AppIcon.png + + + + + + + diff --git a/v3/examples/ios/build/windows/nsis/project.nsi b/v3/examples/ios/build/windows/nsis/project.nsi new file mode 100644 index 000000000..74b8d6ad0 --- /dev/null +++ b/v3/examples/ios/build/windows/nsis/project.nsi @@ -0,0 +1,112 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the wails_tools.nsh file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "my-project" # Default "ios" +## !define INFO_COMPANYNAME "My Company" # Default "My Company" +## !define INFO_PRODUCTNAME "My Product Name" # Default "My Product" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.1.0" +## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "© now, My Company" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/v3/examples/ios/build/windows/nsis/wails_tools.nsh b/v3/examples/ios/build/windows/nsis/wails_tools.nsh new file mode 100644 index 000000000..dc9aebc17 --- /dev/null +++ b/v3/examples/ios/build/windows/nsis/wails_tools.nsh @@ -0,0 +1,212 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "ios" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "My Company" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "My Product" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "0.1.0" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "© now, My Company" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + +!macroend \ No newline at end of file diff --git a/v3/examples/ios/build/windows/wails.exe.manifest b/v3/examples/ios/build/windows/wails.exe.manifest new file mode 100644 index 000000000..025555a69 --- /dev/null +++ b/v3/examples/ios/build/windows/wails.exe.manifest @@ -0,0 +1,22 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + + + + + + + + \ No newline at end of file diff --git a/v3/examples/ios/frontend/Inter Font License.txt b/v3/examples/ios/frontend/Inter Font License.txt new file mode 100644 index 000000000..00287df15 --- /dev/null +++ b/v3/examples/ios/frontend/Inter Font License.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/v3/examples/ios/frontend/bindings/changeme/greetservice.js b/v3/examples/ios/frontend/bindings/changeme/greetservice.js new file mode 100644 index 000000000..0b93e6d75 --- /dev/null +++ b/v3/examples/ios/frontend/bindings/changeme/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/examples/ios/frontend/bindings/changeme/index.js b/v3/examples/ios/frontend/bindings/changeme/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/examples/ios/frontend/bindings/changeme/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/examples/ios/frontend/index.html b/v3/examples/ios/frontend/index.html new file mode 100644 index 000000000..e5a37ff0c --- /dev/null +++ b/v3/examples/ios/frontend/index.html @@ -0,0 +1,96 @@ + + + + + + + + + Wails App + + +
        + +

        Wails + Javascript

        +
        +
        Demo Screens
        +
        + +
        +
        Please enter your name below 👇
        +
        + + +
        +
        + + +
        +
        + +
        +
        + +
        +
        + +
        +
        + +
        +
        + +
        +
        + + +
        +
        + + +
        +
        + + + + +
        +
        + +
        +
        + +
        +
        + +
        +
        + +
        +
        + +
        +
        + + +
        +
        
        +          
        + +
        +
        + +
        + + + diff --git a/v3/examples/ios/frontend/main.js b/v3/examples/ios/frontend/main.js new file mode 100644 index 000000000..ad8064576 --- /dev/null +++ b/v3/examples/ios/frontend/main.js @@ -0,0 +1,113 @@ +import {GreetService} from "./bindings/changeme"; +import * as Runtime from "@wailsio/runtime"; + +const resultElement = document.getElementById('result'); +const timeElement = document.getElementById('time'); +const deviceInfoElement = document.getElementById('deviceInfo'); +const Events = Runtime.Events; +const IOS = Runtime.IOS; // May be undefined in published package; we guard usages below. + +window.doGreet = () => { + let name = document.getElementById('name').value; + if (!name) { + name = 'anonymous'; + } + GreetService.Greet(name).then((result) => { + resultElement.innerText = result; + }).catch((err) => { + console.log(err); + }); +} + +window.doHaptic = (style) => { + if (!IOS || !IOS.Haptics?.Impact) { + console.warn('IOS runtime not available in @wailsio/runtime. Skipping haptic call.'); + return; + } + IOS.Haptics.Impact(style).catch((err) => { + console.error('Haptics error:', err); + }); +} + +window.getDeviceInfo = async () => { + if (!IOS || !IOS.Device?.Info) { + deviceInfoElement.innerText = 'iOS runtime not available; cannot fetch device info.'; + return; + } + try { + const info = await IOS.Device.Info(); + deviceInfoElement.innerText = JSON.stringify(info, null, 2); + } catch (e) { + deviceInfoElement.innerText = `Error: ${e?.message || e}`; + } +} + +// Generic caller for IOS..(args) +window.iosJsSet = async (methodPath, args) => { + if (!IOS) { + console.warn('IOS runtime not available in @wailsio/runtime.'); + return; + } + try { + const [group, method] = methodPath.split('.'); + const target = IOS?.[group]; + const fn = target?.[method]; + if (typeof fn !== 'function') { + console.warn('IOS method not found:', methodPath); + return; + } + await fn(args); + } catch (e) { + console.error('iosJsSet error for', methodPath, e); + } +} + +// Emit events for Go handlers +window.emitGo = (eventName, data) => { + try { + Events.Emit(eventName, data); + } catch (e) { + console.error('emitGo error:', e); + } +} + +// Toggle helpers for UI switches +window.setGoToggle = (eventName, enabled) => { + emitGo(eventName, { enabled: !!enabled }); +} + +window.setJsToggle = (methodPath, enabled) => { + iosJsSet(methodPath, { enabled: !!enabled }); +} + +Events.On('time', (payload) => { + // payload may be a plain value or an object with a `data` field depending on emitter/runtime + const value = (payload && typeof payload === 'object' && 'data' in payload) ? payload.data : payload; + console.log('[frontend] time event:', payload, '->', value); + timeElement.innerText = value; +}); + +// Simple pane switcher responding to native UITabBar +function showPaneByIndex(index) { + const panes = [ + document.getElementById('screen-bindings'), + document.getElementById('screen-go'), + document.getElementById('screen-js'), + ]; + panes.forEach((el, i) => { + if (!el) return; + if (i === index) el.classList.add('active'); + else el.classList.remove('active'); + }); +} + +// Listen for native tab selection events posted by the iOS layer +window.addEventListener('nativeTabSelected', (e) => { + const idx = (e && e.detail && typeof e.detail.index === 'number') ? e.detail.index : 0; + showPaneByIndex(idx); +}); + +// Ensure default pane is visible on load (index 0) +window.addEventListener('DOMContentLoaded', () => { + showPaneByIndex(0); +}); diff --git a/v3/examples/ios/frontend/package-lock.json b/v3/examples/ios/frontend/package-lock.json new file mode 100644 index 000000000..b0b7dc68f --- /dev/null +++ b/v3/examples/ios/frontend/package-lock.json @@ -0,0 +1,936 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "vite": "^5.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.0.tgz", + "integrity": "sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.0.tgz", + "integrity": "sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.0.tgz", + "integrity": "sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.0.tgz", + "integrity": "sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.0.tgz", + "integrity": "sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.0.tgz", + "integrity": "sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.0.tgz", + "integrity": "sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.0.tgz", + "integrity": "sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.0.tgz", + "integrity": "sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.0.tgz", + "integrity": "sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.0.tgz", + "integrity": "sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.0.tgz", + "integrity": "sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.0.tgz", + "integrity": "sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.0.tgz", + "integrity": "sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.0.tgz", + "integrity": "sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.0.tgz", + "integrity": "sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.0.tgz", + "integrity": "sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.0.tgz", + "integrity": "sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.0.tgz", + "integrity": "sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.0.tgz", + "integrity": "sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz", + "integrity": "sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@wailsio/runtime": { + "version": "3.0.0-alpha.66", + "resolved": "https://registry.npmjs.org/@wailsio/runtime/-/runtime-3.0.0-alpha.66.tgz", + "integrity": "sha512-ENLu8rn1griL1gFHJqkq1i+BVxrrA0JPJHYneUJYuf/s54kjuQViW0RKDEe/WTDo56ABpfykrd/T8OYpPUyXUw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.0.tgz", + "integrity": "sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.50.0", + "@rollup/rollup-android-arm64": "4.50.0", + "@rollup/rollup-darwin-arm64": "4.50.0", + "@rollup/rollup-darwin-x64": "4.50.0", + "@rollup/rollup-freebsd-arm64": "4.50.0", + "@rollup/rollup-freebsd-x64": "4.50.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.50.0", + "@rollup/rollup-linux-arm-musleabihf": "4.50.0", + "@rollup/rollup-linux-arm64-gnu": "4.50.0", + "@rollup/rollup-linux-arm64-musl": "4.50.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.50.0", + "@rollup/rollup-linux-ppc64-gnu": "4.50.0", + "@rollup/rollup-linux-riscv64-gnu": "4.50.0", + "@rollup/rollup-linux-riscv64-musl": "4.50.0", + "@rollup/rollup-linux-s390x-gnu": "4.50.0", + "@rollup/rollup-linux-x64-gnu": "4.50.0", + "@rollup/rollup-linux-x64-musl": "4.50.0", + "@rollup/rollup-openharmony-arm64": "4.50.0", + "@rollup/rollup-win32-arm64-msvc": "4.50.0", + "@rollup/rollup-win32-ia32-msvc": "4.50.0", + "@rollup/rollup-win32-x64-msvc": "4.50.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/v3/examples/ios/frontend/package.json b/v3/examples/ios/frontend/package.json new file mode 100644 index 000000000..0a118e984 --- /dev/null +++ b/v3/examples/ios/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "vite": "^5.0.0" + } +} diff --git a/v3/examples/ios/frontend/public/Inter-Medium.ttf b/v3/examples/ios/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/ios/frontend/public/Inter-Medium.ttf differ diff --git a/v3/examples/ios/frontend/public/javascript.svg b/v3/examples/ios/frontend/public/javascript.svg new file mode 100644 index 000000000..f9abb2b72 --- /dev/null +++ b/v3/examples/ios/frontend/public/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/ios/frontend/public/puppertino/LICENSE b/v3/examples/ios/frontend/public/puppertino/LICENSE new file mode 100644 index 000000000..ed9065e06 --- /dev/null +++ b/v3/examples/ios/frontend/public/puppertino/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Edgar Pérez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/v3/examples/ios/frontend/public/puppertino/css/actions.css b/v3/examples/ios/frontend/public/puppertino/css/actions.css new file mode 100644 index 000000000..22a9d5a13 --- /dev/null +++ b/v3/examples/ios/frontend/public/puppertino/css/actions.css @@ -0,0 +1,149 @@ +:root { + --font: -apple-system, "Inter", sans-serif; + --primary-col-ac: #0f75f5; + --p-modal-bg: rgba(255, 255, 255, 0.8); + --p-modal-bd-color: rgba(0,0,0,.1); + --p-modal-fallback-color: rgba(255,255,255,.95); + --p-actions-static-color: #555761; +} + +.p-modal-opened { + overflow: hidden; +} + +.p-action-background{ + background: rgba(0, 0, 0, 0.7); + height: 100vh; + left: 0; + opacity: 0; + pointer-events: none; + position: fixed; + top: 0; + transition: 0.3s; + width: 100vw; + z-index: 5; +} + +.p-action-background.nowactive { + opacity: 1; + pointer-events: auto; +} + + +.p-action-big-container{ + position:fixed; + width: 100%; + box-sizing: border-box; + padding: 1rem 5vw; + bottom:0; +} + +.p-action-container{ + background: var(--p-modal-bg); + display:block; + margin:auto; + margin-bottom: 10px; + border-radius: 10px; + max-width: 700px; +} + +.p-action-big-container .p-action-container:first-child{ + margin-bottom:10px; +} + +.p-action--intern{ + width: 100%; + display:block; + margin:auto; + font-size: 1rem; + font-weight: 600; + text-align:center; + padding: 15px 0; + border: 0; + border-bottom: 1px solid #bfbfbf; + color: #0f75f5; + text-decoration:none; + background-color: transparent; +} + +.p-action-destructive{ + color: #c6262e; +} + +.p-action-neutral{ + color: var(--p-actions-static-color); +} + +.p-action-cancel, .p-action-container a:last-child{ + border-bottom:none; +} + +.p-action-cancel{ + font-weight:bold; +} + +.p-action-icon{ + position:relative; +} +.p-action-icon svg, .p-action-icon img{ + position:absolute; + left:5%; + top:50%; + transform:translateY(-50%); +} + +.p-action-icon-inline{ + text-align: left; + display: flex; + align-items: center; +} + +.p-action-icon-inline svg, .p-action-icon-inline img{ + margin-left: 5%; + margin-right: 3%; +} + +.p-action-title{ + padding: 30px 15px; + border-bottom: 1px solid #bfbfbf; +} + +.p-action-title--intern,.p-action-text{ + margin:0; + color:var(--p-actions-static-color); +} + +.p-action-title--intern{ + margin-bottom: .3rem; +} + +@supports not (backdrop-filter: blur(10px)) { + .p-action-container { + background: var(--p-modal-fallback-color); + } +} + +.p-action-big-container{ + -webkit-transform: translateY(30%); + transform: translateY(30%); + opacity: 0; + transition: opacity 0.4s, transform 0.4s; + transition-timing-function: ease; + pointer-events: none; +} + +.p-action-big-container.active { + -webkit-transform: translateY(0); + transform: translateY(0); + opacity: 1; + pointer-events: all; +} + + +.p-action-big-container.active .p-action-container { + backdrop-filter: saturate(180%) blur(10px); +} + +.p-action-big-container[aria-hidden="true"] .p-action--intern { + display: none; +} diff --git a/v3/examples/ios/frontend/public/puppertino/css/buttons.css b/v3/examples/ios/frontend/public/puppertino/css/buttons.css new file mode 100644 index 000000000..4950b0053 --- /dev/null +++ b/v3/examples/ios/frontend/public/puppertino/css/buttons.css @@ -0,0 +1,158 @@ +@charset "UTF-8"; +:root{ + --p-btn-border: #cacaca; + --p-btn-def-bg: #FFFFFF; + --p-btn-def-col: #000000; + --p-btn-dir-col: #242424; + --p-prim-text-col: #f5f5f5; + --p-btn-scope-unactive: #212136; + --p-btn-scope-action: #212136; +} + +.p-btn { + background: var(--p-btn-def-bg); + border: 1px solid var(--p-btn-border); + border-radius: 10px; + color: var(--p-btn-def-col); + display: inline-block; + font-family: -apple-system, "Inter", sans-serif; + font-size: 1.1rem; + margin: .7rem; + padding: .4rem 1.2rem; + text-decoration: none; + text-align: center; + box-shadow: 0 1px 0.375px rgba(0, 0, 0, 0.05), 0 0.25px 0.375px rgba(0, 0, 0, 0.15); + user-select: none; + cursor: pointer; +} +.p-btn:focus{ + outline: 2px solid #64baff; +} +.p-btn.p-btn-block{ + display: block; +} +.p-btn.p-btn-sm { + padding: .3rem 1.1rem; + font-size: 1rem; +} +.p-btn.p-btn-md { + padding: .8rem 2.4rem; + font-size: 1.6rem; +} +.p-btn.p-btn-lg { + padding: 1.2rem 2.8rem; + font-size: 1.8rem; +} +.p-btn-destructive{ + color: #FF3B30; +} +.p-btn-mob{ + padding: 10px 40px; + background: #227bec; + color: #fff; + border: 0; + box-shadow: inset 0 1px 1px rgb(255 255 255 / 41%), 0px 2px 3px -2px rgba(0,0,0,.3); +} +.p-btn[disabled], +.p-btn:disabled, +.p-btn-disabled{ + filter:contrast(0.5) grayscale(.5) opacity(.8); + cursor: not-allowed; + box-shadow: none; + pointer-events: none; +} + +.p-prim-col { + position: relative; + background: #007AFF; + border: none; + box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.41), 0px 2px 3px -2px rgba(0, 0, 0, 0.3); + color: var(--p-prim-text-col); + overflow: hidden; /* Ensure the ::before element doesn't overflow */ +} + +.p-prim-col:before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(180deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%); + opacity: 0.17; + pointer-events: none; +} + +.p-btn.p-prim-col:active { + background: #0f75f5; +} + +.p-btn-more::after { + content: "..."; +} + +.p-btn-round { + border: 0; + border-radius: 50px; + box-shadow: inset 0 1px 1px rgb(255 255 255 / 41%); + padding: 10px 30px; +} + +.p-btn-icon { + align-items: center; + background: var(--p-btn-def-bg); + border: 2px solid currentColor; + border-radius: 50%; + color: #0f75f5; + display: inline-flex; + font-weight: 900; + height: 40px; + width: 40px; + justify-content: center; + margin: 5px; + text-align: center; + text-decoration: none; + box-sizing: border-box; + user-select: none; + vertical-align: bottom; +} + +.p-btn-icon.p-btn-icon-no-border{ + border: 0px; +} + +.p-btn-scope { + background: #8e8e8e; + color: #fff; + margin: 5px; + padding: 2px 20px; + box-shadow: none; +} +.p-btn-scope-unactive { + background: transparent; + border-color: transparent; + color: var(--p-btn-scope-unactive); + transition: border-color 0.2s; +} +.p-btn-scope-unactive:hover { + border-color: var(--p-btn-border); +} + +.p-btn-scope-outline { + background: transparent; + color: var(--p-btn-scope-action); + box-shadow: none; +} + +.p-btn-outline { + background: none; + border-color: currentColor; + box-shadow: none; +} + +.p-btn-outline-dash { + background: none; + border-color: currentColor; + border-style: dashed; + box-shadow: none; +} diff --git a/v3/examples/ios/frontend/public/puppertino/css/cards.css b/v3/examples/ios/frontend/public/puppertino/css/cards.css new file mode 100644 index 000000000..b4fa2e397 --- /dev/null +++ b/v3/examples/ios/frontend/public/puppertino/css/cards.css @@ -0,0 +1,55 @@ +:root{ + --p-color-card: #1a1a1a; + --p-bg-card: #fff; + --p-bd-card: #c5c5c55e; +} +.p-card { + background: var(--p-bg-card); + border: 1px solid var(--p-bd-card); + color: var(--p-color-card); + display: block; + margin: 15px; + margin-left:7.5px; + margin-right:7.5px; + text-decoration: none; + border-radius: 25px; + padding: 20px 0px; + transition: .3s ease; + box-shadow: 0 0.5px 1px rgba(0, 0, 0, 0.1); +} +.p-card-image > img { + border-bottom: 3px solid var(--accent-article); + display: block; + margin: auto; + width: 100%; +} +.p-card-tags { + display: flex; + overflow: hidden; + position: relative; + width: 100%; +} +.p-card-tags::before { + background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 75%, white 100%); + content: ""; + height: 100%; + position: absolute; + right: 0; + top: 0; + width: 30%; +} +.p-card-title { + font-size: 2rem; + margin-bottom: 15px; + margin-top: 15px; +} +.p-card-content { + padding: 15px; + padding-top: 15px; +} +.p-card-text { + font-size: 17px; + margin-bottom: 10px; + margin-left: 10px; + margin-top: 0; +} diff --git a/v3/examples/ios/frontend/public/puppertino/css/color_palette.css b/v3/examples/ios/frontend/public/puppertino/css/color_palette.css new file mode 100644 index 000000000..33a66b91c --- /dev/null +++ b/v3/examples/ios/frontend/public/puppertino/css/color_palette.css @@ -0,0 +1,917 @@ +:root{ +--p-strawberry: #c6262e; +--p-strawberry-100: #ff8c82; +--p-strawberry-300: #ed5353; +--p-strawberry-500: #c6262e; +--p-strawberry-700: #a10705; +--p-strawberry-900: #7a0000; + +--p-orange: #f37329; +--p-orange-100: #ffc27d; +--p-orange-300: #ffa154; +--p-orange-500: #f37329; +--p-orange-700: #cc3b02; +--p-orange-900: #a62100; + + +--p-banana: #f9c440; +--p-banana-100: #fff394; +--p-banana-300: #ffe16b; +--p-banana-500: #f9c440; +--p-banana-700: #d48e15; +--p-banana-900: #ad5f00; + +--p-lime: #68b723; +--p-lime-100: #d1ff82; +--p-lime-300: #9bdb4d; +--p-lime-500: #68b723; +--p-lime-700: #3a9104; +--p-lime-900: #206b00; + +--p-mint: #28bca3; +--p-mint-100: #89ffdd; +--p-mint-300: #43d6b5; +--p-mint-500: #28bca3; +--p-mint-700: #0e9a83; +--p-mint-900: #007367; + + +--p-blueberry: #3689e6; +--p-blueberry-100: #8cd5ff; +--p-blueberry-300: #64baff; +--p-blueberry-500: #3689e6; +--p-blueberry-700: #0d52bf; +--p-blueberry-900: #002e99; + +--p-grape: #a56de2; +--p-grape-100: #e4c6fa; +--p-grape-300: #cd9ef7; +--p-grape-500: #a56de2; +--p-grape-700: #7239b3; +--p-grape-900: #452981; + +--p-bubblegum: #de3e80; +--p-bubblegum-100: #fe9ab8; +--p-bubblegum-300: #f4679d; +--p-bubblegum-500: #de3e80; +--p-bubblegum-700: #bc245d; +--p-bubblegum-900: #910e38; + + +--p-cocoa: #715344; +--p-cocoa-100: #a3907c; +--p-cocoa-300: #8a715e; +--p-cocoa-500: #715344; +--p-cocoa-700: #57392d; +--p-cocoa-900: #3d211b; + +--p-silver: #abacae; +--p-silver-100: #fafafa; +--p-silver-300: #d4d4d4; +--p-silver-500: #abacae; +--p-silver-700: #7e8087; +--p-silver-900: #555761; + +--p-slate: #485a6c; +--p-slate-100: #95a3ab; +--p-slate-300: #667885; +--p-slate-500: #485a6c; +--p-slate-700: #273445; +--p-slate-900: #0e141f; + + +--p-dark: #333; +--p-dark-100: #666; +--p-dark-300: #4d4d4d; +--p-dark-500: #333; +--p-dark-700: #1a1a1a; +--p-dark-900: #000; + + +--p-apple-red: rgb(255, 59 , 48); +--p-apple-red-dark: rgb(255, 69 , 58); +--p-apple-orange: rgb(255,149,0); +--p-apple-orange-dark: rgb(255,159,10); +--p-apple-yellow: rgb(255,204,0); +--p-apple-yellow-dark: rgb(255,214,10); +--p-apple-green: rgb(40,205,65); +--p-apple-green-dark: rgb(40,215,75); +--p-apple-mint: rgb(0,199,190); +--p-apple-mint-dark: rgb(102,212,207); +--p-apple-teal: rgb(89, 173, 196); +--p-apple-teal-dark: rgb(106, 196, 220); +--p-apple-cyan: rgb(85,190,240); +--p-apple-cyan-dark: rgb(90,200,245); +--p-apple-blue: rgb(0, 122, 255); +--p-apple-blue-dark: rgb(10, 132, 255); +--p-apple-indigo: rgb(88, 86, 214); +--p-apple-indigo-dark: rgb(94, 92, 230); +--p-apple-purple: rgb(175, 82, 222); +--p-apple-purple-dark: rgb(191, 90, 242); +--p-apple-pink: rgb(255, 45, 85); +--p-apple-pink-dark: rgb(255, 55, 95); +--p-apple-brown: rgb(162, 132, 94); +--p-apple-brown-dark: rgb(172, 142, 104); +--p-apple-gray: rgb(142, 142, 147); +--p-apple-gray-dark: rgb(152, 152, 157); + +} + + +/* +APPLE OFFICIAL COLORS +*/ + +.p-apple-red{ + background: rgb(255, 59 , 48); +} + +.p-apple-red-dark{ + background: rgb(255, 69 , 58); +} + +.p-apple-orange{ + background: rgb(255,149,0); +} + +.p-apple-orange-dark{ + background: rgb(255,159,10); +} + +.p-apple-yellow{ + background: rgb(255,204,0); +} + +.p-apple-yellow-dark{ + background: rgb(255,214,10); +} + +.p-apple-green{ + background: rgb(40,205,65); +} + +.p-apple-green-dark{ + background: rgb(40,215,75); +} + +.p-apple-mint{ + background: rgb(0,199,190); +} + +.p-apple-mint-dark{ + background: rgb(102,212,207); +} + +.p-apple-teal{ + background: rgb(89, 173, 196); +} + +.p-apple-teal-dark{ + background: rgb(106, 196, 220); +} + +.p-apple-cyan{ + background: rgb(85,190,240); +} + +.p-apple-cyan-dark{ + background: rgb(90,200,245); +} + +.p-apple-blue{ + background: rgb(0, 122, 255); +} + +.p-apple-blue-dark{ + background: rgb(10, 132, 255); +} + +.p-apple-indigo{ + background: rgb(88, 86, 214); +} + +.p-apple-indigo-dark{ + background: rgb(94, 92, 230); +} + +.p-apple-purple{ + background: rgb(175, 82, 222); +} + +.p-apple-purple-dark{ + background: rgb(191, 90, 242); +} + +.p-apple-pink{ + background: rgb(255, 45, 85); +} + +.p-apple-pink-dark{ + background: rgb(255, 55, 95); +} + +.p-apple-brown{ + background: rgb(162, 132, 94); +} + +.p-apple-brown-dark{ + background: rgb(172, 142, 104); +} + +.p-apple-gray{ + background: rgb(142, 142, 147); +} + +.p-apple-gray-dark{ + background: rgb(152, 152, 157); +} + +.p-apple-red-color{ + color: rgb(255, 59 , 48); +} + +.p-apple-red-dark-color{ + color: rgb(255, 69 , 58); +} + +.p-apple-orange-color{ + color: rgb(255,149,0); +} + +.p-apple-orange-dark-color{ + color: rgb(255,159,10); +} + +.p-apple-yellow-color{ + color: rgb(255,204,0); +} + +.p-apple-yellow-dark-color{ + color: rgb(255,214,10); +} + +.p-apple-green-color{ + color: rgb(40,205,65); +} + +.p-apple-green-dark-color{ + color: rgb(40,215,75); +} + +.p-apple-mint-color{ + color: rgb(0,199,190); +} + +.p-apple-mint-dark-color{ + color: rgb(102,212,207); +} + +.p-apple-teal-color{ + color: rgb(89, 173, 196); +} + +.p-apple-teal-dark-color{ + color: rgb(106, 196, 220); +} + +.p-apple-cyan-color{ + color: rgb(85,190,240); +} + +.p-apple-cyan-dark-color{ + color: rgb(90,200,245); +} + +.p-apple-blue-color{ + color: rgb(0, 122, 255); +} + +.p-apple-blue-dark-color{ + color: rgb(10, 132, 255); +} + +.p-apple-indigo-color{ + color: rgb(88, 86, 214); +} + +.p-apple-indigo-dark-color{ + color: rgb(94, 92, 230); +} + +.p-apple-purple-color{ + color: rgb(175, 82, 222); +} + +.p-apple-purple-dark-color{ + color: rgb(191, 90, 242); +} + +.p-apple-pink-color{ + color: rgb(255, 45, 85); +} + +.p-apple-pink-dark-color{ + color: rgb(255, 55, 95); +} + +.p-apple-brown-color{ + color: rgb(162, 132, 94); +} + +.p-apple-brown-dark-color{ + color: rgb(172, 142, 104); +} + +.p-apple-gray-color{ + color: rgb(142, 142, 147); +} + +.p-apple-gray-dark-color{ + color: rgb(152, 152, 157); +} + +.p-strawberry { + background: #c6262e; +} + +.p-strawberry-100 { + background: #ff8c82; +} + +.p-strawberry-300 { + background: #ed5353; +} + +.p-strawberry-500 { + background: #c6262e; +} + +.p-strawberry-700 { + background: #a10705; +} + +.p-strawberry-900 { + background: #7a0000; +} + +.p-orange { + background: #f37329; +} + +.p-orange-100 { + background: #ffc27d; +} + +.p-orange-300 { + background: #ffa154; +} + +.p-orange-500 { + background: #f37329; +} + +.p-orange-700 { + background: #cc3b02; +} + +.p-orange-900 { + background: #a62100; +} + +.p-banana { + background: #f9c440; +} + +.p-banana-100 { + background: #fff394; +} + +.p-banana-300 { + background: #ffe16b; +} + +.p-banana-500 { + background: #f9c440; +} + +.p-banana-700 { + background: #d48e15; +} + +.p-banana-900 { + background: #ad5f00; +} + +.p-lime { + background: #68b723; +} + +.p-lime-100 { + background: #d1ff82; +} + +.p-lime-300 { + background: #9bdb4d; +} + +.p-lime-500 { + background: #68b723; +} + +.p-lime-700 { + background: #3a9104; +} + +.p-lime-900 { + background: #206b00; +} + +.p-mint { + background: #28bca3; +} + +.p-mint-100 { + background: #89ffdd; +} + +.p-mint-300 { + background: #43d6b5; +} + +.p-mint-500 { + background: #28bca3; +} + +.p-mint-700 { + background: #0e9a83; +} + +.p-mint-900 { + background: #007367; +} + +.p-blueberry { + background: #3689e6; +} + +.p-blueberry-100 { + background: #8cd5ff; +} + +.p-blueberry-300 { + background: #64baff; +} + +.p-blueberry-500 { + background: #3689e6; +} + +.p-blueberry-700 { + background: #0d52bf; +} + +.p-blueberry-900 { + background: #002e99; +} + +.p-grape { + background: #a56de2; +} + +.p-grape-100 { + background: #e4c6fa; +} + +.p-grape-300 { + background: #cd9ef7; +} + +.p-grape-500 { + background: #a56de2; +} + +.p-grape-700 { + background: #7239b3; +} + +.p-grape-900 { + background: #452981; +} + +.p-bubblegum { + background: #de3e80; +} + +.p-bubblegum-100 { + background: #fe9ab8; +} + +.p-bubblegum-300 { + background: #f4679d; +} + +.p-bubblegum-500 { + background: #de3e80; +} + +.p-bubblegum-700 { + background: #bc245d; +} + +.p-bubblegum-900 { + background: #910e38; +} + +.p-cocoa { + background: #715344; +} + +.p-cocoa-100 { + background: #a3907c; +} + +.p-cocoa-300 { + background: #8a715e; +} + +.p-cocoa-500 { + background: #715344; +} + +.p-cocoa-700 { + background: #57392d; +} + +.p-cocoa-900 { + background: #3d211b; +} + +.p-silver { + background: #abacae; +} + +.p-silver-100 { + background: #fafafa; +} + +.p-silver-300 { + background: #d4d4d4; +} + +.p-silver-500 { + background: #abacae; +} + +.p-silver-700 { + background: #7e8087; +} + +.p-silver-900 { + background: #555761; +} + +.p-slate { + background: #485a6c; +} + +.p-slate-100 { + background: #95a3ab; +} + +.p-slate-300 { + background: #667885; +} + +.p-slate-500 { + background: #485a6c; +} + +.p-slate-700 { + background: #273445; +} + +.p-slate-900 { + background: #0e141f; +} + +.p-dark { + background: #333; +} + +.p-dark-100 { + background: #666; + /* hehe */ +} + +.p-dark-300 { + background: #4d4d4d; +} + +.p-dark-500 { + background: #333; +} + +.p-dark-700 { + background: #1a1a1a; +} + +.p-dark-900 { + background: #000; +} + +.p-white{ + background: #fff; +} + +.p-strawberry-color { + color: #c6262e; +} + +.p-strawberry-100-color { + color: #ff8c82; +} + +.p-strawberry-300-color { + color: #ed5353; +} + +.p-strawberry-500-color { + color: #c6262e; +} + +.p-strawberry-700-color { + color: #a10705; +} + +.p-strawberry-900-color { + color: #7a0000; +} + +.p-orange-color { + color: #f37329; +} + +.p-orange-100-color { + color: #ffc27d; +} + +.p-orange-300-color { + color: #ffa154; +} + +.p-orange-500-color { + color: #f37329; +} + +.p-orange-700-color { + color: #cc3b02; +} + +.p-orange-900-color { + color: #a62100; +} + +.p-banana-color { + color: #f9c440; +} + +.p-banana-100-color { + color: #fff394; +} + +.p-banana-300-color { + color: #ffe16b; +} + +.p-banana-500-color { + color: #f9c440; +} + +.p-banana-700-color { + color: #d48e15; +} + +.p-banana-900-color { + color: #ad5f00; +} + +.p-lime-color { + color: #68b723; +} + +.p-lime-100-color { + color: #d1ff82; +} + +.p-lime-300-color { + color: #9bdb4d; +} + +.p-lime-500-color { + color: #68b723; +} + +.p-lime-700-color { + color: #3a9104; +} + +.p-lime-900-color { + color: #206b00; +} + +.p-mint-color { + color: #28bca3; +} + +.p-mint-100-color { + color: #89ffdd; +} + +.p-mint-300-color { + color: #43d6b5; +} + +.p-mint-500-color { + color: #28bca3; +} + +.p-mint-700-color { + color: #0e9a83; +} + +.p-mint-900-color { + color: #007367; +} + +.p-blueberry-color { + color: #3689e6; +} + +.p-blueberry-100-color { + color: #8cd5ff; +} + +.p-blueberry-300-color { + color: #64baff; +} + +.p-blueberry-500-color { + color: #3689e6; +} + +.p-blueberry-700-color { + color: #0d52bf; +} + +.p-blueberry-900-color { + color: #002e99; +} + +.p-grape-color { + color: #a56de2; +} + +.p-grape-100-color { + color: #e4c6fa; +} + +.p-grape-300-color { + color: #cd9ef7; +} + +.p-grape-500-color { + color: #a56de2; +} + +.p-grape-700-color { + color: #7239b3; +} + +.p-grape-900-color { + color: #452981; +} + +.p-bubblegum-color { + color: #de3e80; +} + +.p-bubblegum-100-color { + color: #fe9ab8; +} + +.p-bubblegum-300-color { + color: #f4679d; +} + +.p-bubblegum-500-color { + color: #de3e80; +} + +.p-bubblegum-700-color { + color: #bc245d; +} + +.p-bubblegum-900-color { + color: #910e38; +} + +.p-cocoa-color { + color: #715344; +} + +.p-cocoa-100-color { + color: #a3907c; +} + +.p-cocoa-300-color { + color: #8a715e; +} + +.p-cocoa-500-color { + color: #715344; +} + +.p-cocoa-700-color { + color: #57392d; +} + +.p-cocoa-900-color { + color: #3d211b; +} + +.p-silver-color { + color: #abacae; +} + +.p-silver-100-color { + color: #fafafa; +} + +.p-silver-300-color { + color: #d4d4d4; +} + +.p-silver-500-color { + color: #abacae; +} + +.p-silver-700-color { + color: #7e8087; +} + +.p-silver-900-color { + color: #555761; +} + +.p-slate-color { + color: #485a6c; +} + +.p-slate-100-color { + color: #95a3ab; +} + +.p-slate-300-color { + color: #667885; +} + +.p-slate-500-color { + color: #485a6c; +} + +.p-slate-700-color { + color: #273445; +} + +.p-slate-900-color { + color: #0e141f; +} + +.p-dark-color { + color: #333; +} + +.p-dark-100-color { + color: #666; + /* hehe */ +} + +.p-dark-300-color { + color: #4d4d4d; +} + +.p-dark-500-color { + color: #333; +} + +.p-dark-700-color { + color: #1a1a1a; +} + +.p-dark-900-color { + color: #000; +} + +.p-white-color{ + color: #fff; +} diff --git a/v3/examples/ios/frontend/public/puppertino/css/dark_mode.css b/v3/examples/ios/frontend/public/puppertino/css/dark_mode.css new file mode 100644 index 000000000..3c5a03e80 --- /dev/null +++ b/v3/examples/ios/frontend/public/puppertino/css/dark_mode.css @@ -0,0 +1 @@ +/* Puppertino dark_mode placeholder - local vendored */ diff --git a/v3/examples/ios/frontend/public/puppertino/css/forms.css b/v3/examples/ios/frontend/public/puppertino/css/forms.css new file mode 100644 index 000000000..f2320ab1b --- /dev/null +++ b/v3/examples/ios/frontend/public/puppertino/css/forms.css @@ -0,0 +1,509 @@ +:root { + --primary-col:linear-gradient(to bottom, #4fc5fa 0%,#0f75f5 100%); + --primary-col-ac:#0f75f5; + --bg-color-input:#fff; + + --p-checkbox-gradient: linear-gradient(180deg, #4B91F7 0%, #367AF6 100%); + --p-checkbox-border: rgba(0, 0, 0, 0.2); + --p-checkbox-border-active: rgba(0, 0, 0, 0.12); + --p-checkbox-bg: transparent; + --p-checkbox-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.15), inset 0px 0px 2px rgba(0, 0, 0, 0.10); + + --p-input-bg:#fff; + --p-input-color: rgba(0,0,0,.85); + --p-input-color-plac:rgba(0,0,0,0.25); + + --p-input-color:#808080; + --p-input-bd:rgba(0,0,0,0.15); + --bg-hover-color:#f9f9f9; + --bg-front-col:#000; + --invalid-color:#d6513c; + --valid-color:#94d63c; +} + +.p-dark-mode{ + --p-checkbox-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.13) 100%); + --p-checkbox-shadow: 0px 0px 1px rgba(0, 0, 0, 0.25), inset 0px 0.5px 0px rgba(255, 255, 255, 0.15); + --p-checkbox-border: rgba(0, 0, 0, 0); + + --p-checkbox-gradient: linear-gradient(180deg, #3168DD 0%, #2C5FC8 100%); + --p-checkbox-border-active: rgba(0, 0, 0, 0); +} + +.p-form-select { + border-radius: 5px; + display: inline-block; + font-family: -apple-system, "Inter", sans-serif; + margin: 10px; + position: relative; +} + +.p-form-select > select:focus{ + outline: 2px solid #64baff; +} + +.p-form-select::after { + background: url("data:image/svg+xml,%3Csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 12'%3E%3Cpath d='M.288 4.117 3.16 1.18c.168-.168.336-.246.54-.246a.731.731 0 0 1 .538.246L7.108 4.12c.125.121.184.27.184.45 0 .359-.293.656-.648.656a.655.655 0 0 1-.48-.211L3.701 2.465l-2.469 2.55a.664.664 0 0 1-.48.212.656.656 0 0 1-.465-1.11Zm3.41 7.324a.73.73 0 0 0 .54-.246l2.87-2.941a.601.601 0 0 0 .184-.45.656.656 0 0 0-.648-.656.677.677 0 0 0-.48.211L3.701 9.91 1.233 7.36a.68.68 0 0 0-.48-.212.656.656 0 0 0-.465 1.11l2.871 2.937c.172.168.336.246.54.246Z' fill='white' style='mix-blend-mode:luminosity'/%3E%3C/svg%3E"), #017AFF; + background-size: 100% 75%; + background-position: center; + background-repeat: no-repeat; + border-radius: 5px; + bottom: 0; + content: ""; + display: block; + height: 80%; + pointer-events: none; + position: absolute; + right: 3%; + top: 10%; + width: 20px; +} + +.p-form-select > select { + -webkit-appearance: none; + appearance: none; + background: var(--p-input-bg); + border: 1px solid var(--p-input-bd); + border-radius: 5px; + font-size: 14px; + margin: 0; + outline: none; + padding: 5px 35px 5px 10px; + position: relative; + width: 100%; + color: var(--p-input-color); +} + +.p-form-text:invalid, +.p-form-text-alt:invalid{ + border-color: var(--invalid-color); +} + +.p-form-text:valid, +.p-form-text-alt:valid{ + border-color: var(--valid-color); +} + +.p-form-text:placeholder-shown, +.p-form-text-alt:placeholder-shown{ + border-color: var(--p-input-bd); +} + +.p-form-text { + color: var(--p-input-color); + -webkit-appearance: none; + appearance: none; + background: var(--p-input-bg); + border: 1px solid var(--p-input-bd); + border-radius: 5px; + font-family: -apple-system, "Inter", sans-serif; + font-size: 13px; + margin: 10px; + outline: 0; + padding: 3px 7px; + resize: none; + transition: border-color 200ms; + box-shadow: 0px 0.5px 2.5px rgba(0,0,0,.3), 0px 0px 0px rgba(0,0,0,.1); +} + +.p-form-text-alt { + color: var(--p-input-color); + -webkit-appearance: none; + appearance: none; + box-shadow: none; + background: var(--p-input-bg); + border: 0px; + border-bottom: 2px solid var(--p-input-bd); + padding: 10px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + margin: 10px; +} + + + +.p-form-text-alt::placeholder, +.p-form-text::placeholder +{ + color: var(--p-input-color-plac); +} + +.p-form-text:active, +.p-form-text:focus +{ + outline: 3px solid rgb(0 122 255 / 50%); +} + +.p-form-text-alt:focus { + outline: 0; + outline: 3px solid rgb(0 122 255 / 50%); + border-color: #3689e6; +} + +.p-form-no-validate:valid, +.p-form-no-validate:invalid{ + border-color: var(--p-input-bd); + color: var(--p-input-color)!important; +} + +.p-form-text:focus { + border-color: rgb(0 122 255); +} + +textarea.p-form-text { + -webkit-appearance: none; + appearance: none; + height: 100px; +} + +.p-form-truncated { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.p-form-text[type=password] { + font-family: caption; +} + +.p-form-label, +.p-form-radio-cont, +.p-form-checkbox-cont, +.p-form-label-inline { + font-family: -apple-system, "Inter", sans-serif; +} + +.p-form-label, .p-form-label-inline { + display: inline-block; +} + +.p-form-label{ + font-size: 11px; +} + +.p-form-label-inline { + background: var(--p-input-bg); + padding: 5px; + border-bottom: 2px solid var(--p-input-bd); + color: #656565; + font-weight: 500; + transition: .3s; +} + +.p-form-label-inline:focus-within { + color: #3689e6; + border-color: #3689e6; +} + +.p-form-label-inline > .p-form-text-alt { + border-bottom: 0px; + padding: 0; + outline: 0; + background: var(--p-input-bg); + +} + +.p-form-label-inline > .p-form-text-alt:-webkit-autofill{ + background: var(--p-input-bg); + -webkit-box-shadow: 0 0 0 30px rgba(0,0,0,0) inset !important; +} + +.p-form-label-inline > .p-form-text-alt:invalid { + color: var(--invalid-color); +} + +.p-form-label-inline > .p-form-text-alt:valid { + color: #3689e6; +} + +.p-form-label-inline > .p-form-text-alt:focus{ + color: var(--p-input-color); +} + +.p-form-radio-cont, +.p-form-checkbox-cont { + align-items: center; + display: inline-flex; + cursor: pointer; + margin: 0 10px; + user-select: none; +} + +.p-form-radio-cont > input + span, +.p-form-checkbox-cont > input + span { + background: var(--p-input-bg); + border: 1px solid var(--p-input-bd); + border-radius: 50%; + display: inline-block; + height: 20px; + margin-right: 5px; + position: relative; + transition: 0.2s; + width: 20px; +} + +.p-form-radio-cont > input + span{ + box-shadow: inset 0px 1px 2px rgba(0,0,0,0.10), inset 0px 0px 2px rgba(0,0,0,0.10); +} + +.p-form-radio-cont > input:focus + span, +.p-form-checkbox-cont > input:focus + span{ + outline: 3px solid rgb(0 122 255 / 50%); +} + +.p-form-radio-cont:hover > input + span{ + background: #f9f9f9; +} + +.p-form-radio-cont > input, +.p-form-checkbox-cont > input { + opacity: 0; + pointer-events: none; + position: absolute; +} + +.p-form-radio-cont > input + span::after { + background: #fff; + border-radius: 50%; + content: ""; + display: block; + height: 30%; + left: calc(50% - 15%); + opacity: 0; + position: absolute; + top: calc(50% - 15%); + transform: scale(2); + transition: opacity 0.2s, transform 0.3s; + width: 30%; +} + +.p-form-radio-cont > input:checked + span { + background: #0f75f5; + box-shadow: 0px 1px 2.5px 1px rgba(0, 122, 255, 0.24), inset 0px 0px 0px 0.5px rgba(0, 122, 255, 0.12); +} + +.p-form-radio-cont > input:checked + span::after { + opacity: 1; + transform: scale(1); +} + +.p-form-checkbox-cont > input + span { + border-radius: 5px; + box-shadow: var(--p-checkbox-shadow); + border: 0.5px solid var(--p-checkbox-border); + background: var(--p-checkbox-bg) +} + +.p-form-checkbox-cont > input:checked + span { + background: var(--p-checkbox-gradient); + border: 0.5px solid var(--p-checkbox-border-active); + box-shadow: 0px 1px 2.5px rgba(0, 122, 255, 0.24), 0px 0px 0px 0.5px rgba(0, 122, 255, 0.12); +} + +.p-form-checkbox-cont > input + span::before{ + content: ""; + display: block; + height: 100%; + width: 100%; + position: absolute; + left: 0%; + top: 0%; + opacity: 0; + transition: opacity 0.2s; + background-image: url("data:image/svg+xml,%3Csvg width='10' height='8' viewBox='0 0 10 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.10791 7.81299C3.83545 7.81299 3.6084 7.70459 3.42676 7.48779L1.15918 4.74121C1.08008 4.65039 1.02441 4.5625 0.992188 4.47754C0.959961 4.39258 0.943848 4.30469 0.943848 4.21387C0.943848 4.00586 1.0127 3.83447 1.15039 3.69971C1.29102 3.56201 1.4668 3.49316 1.67773 3.49316C1.91211 3.49316 2.10693 3.58838 2.26221 3.77881L4.10791 6.04297L7.68066 0.368652C7.77148 0.230957 7.86523 0.134277 7.96191 0.0786133C8.06152 0.0200195 8.18311 -0.00927734 8.32666 -0.00927734C8.5376 -0.00927734 8.71191 0.0581055 8.84961 0.192871C8.9873 0.327637 9.05615 0.497559 9.05615 0.702637C9.05615 0.778809 9.04297 0.85791 9.0166 0.939941C8.99023 1.02197 8.94922 1.10693 8.89355 1.19482L4.80225 7.45703C4.64111 7.69434 4.40967 7.81299 4.10791 7.81299Z' fill='white'/%3E%3C/svg%3E%0A"); + background-size: 70%; + background-position: center; + background-repeat: no-repeat; +} + +.p-form-checkbox-cont > input + span::after{ + content: ''; + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + z-index: 9; +} + +.p-form-checkbox-cont > input + span:active::after{ + border-radius: 5px; + backdrop-filter: brightness(1.2); +} + +.p-form-checkbox-cont > input:checked + span::before{ + opacity: 1; +} + + +.p-form-checkbox-cont > input[disabled] + span, +.p-form-radio-cont > input[disabled] ~ span +{ + opacity: .7; + cursor: not-allowed; +} + +.p-form-button { + -webkit-appearance: none; + appearance: none; + background: #fff; + border: 1px solid var(--p-input-bd); + border-radius: 5px; + color: #333230; + display: inline-block; + font-size: 17px; + margin: 10px; + padding: 5px 20px; + text-decoration: none; +} + +.p-form-send { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border: 0; + color: #fff; +} + +.p-form-send:active { + background: #0f75f5; +} + +.p-form-invalid, +.p-form-invalid:placeholder-shown, +.p-form-invalid:valid, +.p-form-invalid:invalid { + border-color: var(--invalid-color); +} + +.p-form-valid, +.p-form-valid:placeholder-shown, +.p-form-valid:valid, +.p-form-valid:invalid { + border-color: var(--valid-color); +} + +.p-form-switch { + --width: 80px; + cursor: pointer; + display: inline-block; +} + +.p-form-switch > input:checked + span::after { + left: calc(100% - calc(var(--width) / 1.8)); +} + +.p-form-switch > input:checked + span { + background: #60c35b; +} + +.p-form-switch > span { + background: #e0e0e0; + border: 1px solid #d3d3d3; + border-radius: 500px; + display: block; + height: calc(var(--width) / 1.6); + position: relative; + transition: all 0.2s; + width: var(--width); +} + +.p-form-switch > span::after { + background: #f9f9f9; + border-radius: 50%; + border: 0.5px solid rgba(0, 0, 0, 0.101987); + box-shadow: 0px 3px 1px rgba(0, 0, 0, 0.1), 0px 1px 1px rgba(0, 0, 0, 0.16), 0px 3px 8px rgba(0, 0, 0, 0.15); + box-sizing: border-box; + content: ""; + height: 84%; + left: 3%; + position: absolute; + top: 6.5%; + transition: all 0.2s; + width: 52.5%; +} + +.p-form-switch > input { + display: none; +} + +.p-chip input{ + opacity: 0; + pointer-events: none; + position: absolute; +} + +.p-chip span{ + padding: .8rem 1rem; + border-radius: 1.6rem; + display:inline-block; + margin:10px; + background: #e4e4e4ca; + color: #3689e6; + transition: .3s; + user-select: none; + cursor:pointer; + font-family: -apple-system, "Inter", sans-serif; + font-size: 1rem; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -moz-tap-highlight-color: rgba(0, 0, 0, 0); + text-align:center; +} + +.p-chip:focus-within span{ + outline: 2px solid #64baff; +} + +.p-chip svg{ + display:block; + margin:auto; +} + + +.p-chip input:checked + span{ + background: #3689e6; + color:#fff; +} + +.p-chip-outline span, .p-chip-outline-to-bg span{ + background: transparent; + color: #3e3e3e; + border: 1px solid currentColor; +} + +.p-chip-outline input:checked + span{ + background: transparent; + color: #3689e6; +} + +.p-chip-radius-b span{ + border-radius: 5px; +} + +.p-chip-dark span{ + color: #3e3e3e; +} + +.p-chip-dark input:checked + span{ + background: #3e3e3e; +} + +.p-chip input:disabled + span, +.p-chip input[disabled] + span{ + opacity: .5; + cursor: not-allowed; +} + +.p-chip-big span{ + font-size: 1.3rem; + padding: 1.5rem; + min-width: 80px; +} + +.p-form-checkbox-cont[disabled], +.p-form-label[disabled], +.p-form-text[disabled], +.p-form-text-alt[disabled], +.p-form-select[disabled], +.p-form-radio-cont[disabled]{ + filter: grayscale(1) opacity(.3); + pointer-events: none; +} diff --git a/v3/examples/ios/frontend/public/puppertino/css/layout.css b/v3/examples/ios/frontend/public/puppertino/css/layout.css new file mode 100644 index 000000000..1f9b36845 --- /dev/null +++ b/v3/examples/ios/frontend/public/puppertino/css/layout.css @@ -0,0 +1,45 @@ +.p-large-title{ + font-size: 2.75rem; +} + +.p-layout h1 { + font-size: 2.25rem; +} + +.p-layout h2 { + font-size: 1.75rem; +} + +.p-layout h3 { + font-size: 1.58rem; +} + +.p-headline { + font-size: 1.34rem; + font-weight: bold; +} + +.p-layout p { + font-size: 1.15rem; +} + +.p-layout .link, +.p-layout input { + font-size: 0.813rem; +} + +.p-callout { + font-size: 1.14rem; +} + +.p-subhead { + font-size: 1.167rem; +} + +.p-footnote { + font-size: 1.07rem; +} + +.p-caption { + font-size: 0.91rem; +} diff --git a/v3/examples/ios/frontend/public/puppertino/css/modals.css b/v3/examples/ios/frontend/public/puppertino/css/modals.css new file mode 100644 index 000000000..4d718c4f7 --- /dev/null +++ b/v3/examples/ios/frontend/public/puppertino/css/modals.css @@ -0,0 +1 @@ +/* Puppertino modals placeholder - local vendored */ diff --git a/v3/examples/ios/frontend/public/puppertino/css/newfull.css b/v3/examples/ios/frontend/public/puppertino/css/newfull.css new file mode 100644 index 000000000..622a2f364 --- /dev/null +++ b/v3/examples/ios/frontend/public/puppertino/css/newfull.css @@ -0,0 +1,11 @@ +@import url('actions.css'); +@import url('buttons.css'); +@import url('layout.css'); +@import url('cards.css'); +@import url('color_palette.css'); +@import url('forms.css'); +@import url('modals.css'); +@import url('segmented-controls.css'); +@import url('shadows.css'); +@import url('tabs.css'); +@import url('dark_mode.css'); diff --git a/v3/examples/ios/frontend/public/puppertino/css/segmented-controls.css b/v3/examples/ios/frontend/public/puppertino/css/segmented-controls.css new file mode 100644 index 000000000..22819fd5f --- /dev/null +++ b/v3/examples/ios/frontend/public/puppertino/css/segmented-controls.css @@ -0,0 +1 @@ +/* Puppertino segmented-controls placeholder - local vendored */ diff --git a/v3/examples/ios/frontend/public/puppertino/css/shadows.css b/v3/examples/ios/frontend/public/puppertino/css/shadows.css new file mode 100644 index 000000000..060a61658 --- /dev/null +++ b/v3/examples/ios/frontend/public/puppertino/css/shadows.css @@ -0,0 +1 @@ +/* Puppertino shadows placeholder - local vendored */ diff --git a/v3/examples/ios/frontend/public/puppertino/css/tabs.css b/v3/examples/ios/frontend/public/puppertino/css/tabs.css new file mode 100644 index 000000000..61d1487ca --- /dev/null +++ b/v3/examples/ios/frontend/public/puppertino/css/tabs.css @@ -0,0 +1 @@ +/* Puppertino tabs placeholder - local vendored */ diff --git a/v3/examples/ios/frontend/public/puppertino/puppertino.css b/v3/examples/ios/frontend/public/puppertino/puppertino.css new file mode 100644 index 000000000..905da220e --- /dev/null +++ b/v3/examples/ios/frontend/public/puppertino/puppertino.css @@ -0,0 +1,1774 @@ +@charset "UTF-8"; +.p-btn { + background: #fff; + border: 1px solid #cacaca; + border-radius: 5px; + color: #333230; + display: inline-block; + font-family: -apple-system, "Inter", sans-serif; + font-size: 17px; + margin: 10px; + padding: 5px 20px; + text-decoration: none; + /* text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); */ +} +.p-btn-mob{ + padding: 10px 40px; + background: #0f75f5; + color: #fff; +} +.p-btn[disabled] { + background: #d3d3d3; + color: #555; + cursor: not-allowed; +} +.p-btn:disabled { + background: #d3d3d3; + color: #555; + cursor: not-allowed; +} +.p-btn-disabled { + background: #d3d3d3; + color: #555; + cursor: not-allowed; +} + +.p-prim-col { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border: 0; + color: #fff; +} + +.p-btn.p-prim-col:active { + background: #0f75f5; +} + +.p-btn-more::after { + content: "..."; +} + +.p-btn-round { + border: 0; + border-radius: 50px; + padding: 10px 30px; +} + +.p-btn-icon { + align-items: center; + background: #fff; + border: 2px solid currentColor; + border-radius: 50%; + box-shadow: 0 3px 10px -8px #000; + color: #0f75f5; + display: inline-flex; + font-weight: 900; + height: 36px; + justify-content: center; + margin: 5px; + text-align: center; + text-decoration: none; + width: 36px; +} + +.p-btn-scope { + background: #8e8e8e; + color: #fff; + margin: 5px; + padding: 2px 20px; +} +.p-btn-scope-unactive { + background: transparent; + border-color: transparent; + color: #212136; + transition: border-color 0.2s; +} +.p-btn-scope-unactive:hover { + border-color: #cacaca; +} +.p-btn-scope-disabled { + background: transparent; + color: #8e8e8e; + cursor: not-allowed; +} +.p-btn-scope-outline { + background: transparent; + color: #212136; +} + +.p-btn-scope-outline { + background: transparent; + color: #212136; +} + +.p-btn-outline { + background: none; + border-color: currentColor; +} + +.p-btn-outline-dash { + background: none; + border-color: currentColor; + border-style: dashed; +} + +.p-btn-direction { + color: #212136; + padding: 5px; + text-decoration: none; +} + +.p-btn-direction.p-btn-d-back::before { + content: "❬"; +} + +.p-btn-direction.p-btn-d-next::after { + content: "❭"; +} + +@media (max-width: 576px) { + .p-btn-big-sm { + border: 0; + border-radius: 0%; + bottom: 0; + font-size: 50px; + left: 0; + margin: 0; + padding: 10px 0; + position: fixed; + text-align: center; + width: 100%; + } +} + +/*END OF BUTTONS*/ + +.p-card { + background: rgba(255, 255, 255, 0.3); + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 3px; + box-shadow: 0 8px 10px -8px rgba(0, 0, 0, 0.1); + color: #000; + display: block; + margin-top: 30px; + text-decoration: none; +} +.p-card-image > img { + border-bottom: 3px solid var(--accent-article); + display: block; + margin: auto; + width: 100%; +} +.p-card-tags { + display: flex; + overflow: hidden; + position: relative; + width: 100%; +} +.p-card-tags::before { + background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 75%, white 100%); + content: ""; + height: 100%; + position: absolute; + right: 0; + top: 0; + width: 30%; +} +.p-card-tags span, +.p-card-tags a { + border: 1px solid #252525; + border-radius: 50px; + color: #252525; + margin: 5px; + padding: 5px 15px; + text-decoration: none; + transition: all 0.2s; +} +.p-card-tags a:hover { + background: #252525; + color: #000; +} +.p-card-title { + font-size: 2rem; + margin-bottom: 15px; + margin-top: 10px; +} +.p-card-content { + padding: 15px; + padding-top: 5px; +} +.p-card-text { + font-size: 17px; + margin-bottom: 10px; + margin-left: 10px; + margin-top: 0; +} + + +/* END OF CARDS*/ + +.p-strawberry { + background: #c6262e; +} + +.p-strawberry-100 { + background: #ff8c82; +} + +.p-strawberry-300 { + background: #ed5353; +} + +.p-strawberry-500 { + background: #c6262e; +} + +.p-strawberry-700 { + background: #a10705; +} + +.p-strawberry-900 { + background: #7a0000; +} + +.p-orange { + background: #f37329; +} + +.p-orange-100 { + background: #ffc27d; +} + +.p-orange-300 { + background: #ffa154; +} + +.p-orange-500 { + background: #f37329; +} + +.p-orange-700 { + background: #cc3b02; +} + +.p-orange-900 { + background: #a62100; +} + +.p-banana { + background: #f9c440; +} + +.p-banana-100 { + background: #fff394; +} + +.p-banana-300 { + background: #ffe16b; +} + +.p-banana-500 { + background: #f9c440; +} + +.p-banana-700 { + background: #d48e15; +} + +.p-banana-900 { + background: #ad5f00; +} + +.p-lime { + background: #68b723; +} + +.p-lime-100 { + background: #d1ff82; +} + +.p-lime-300 { + background: #9bdb4d; +} + +.p-lime-500 { + background: #68b723; +} + +.p-lime-700 { + background: #3a9104; +} + +.p-lime-900 { + background: #206b00; +} + +.p-mint { + background: #28bca3; +} + +.p-mint-100 { + background: #89ffdd; +} + +.p-mint-300 { + background: #43d6b5; +} + +.p-mint-500 { + background: #28bca3; +} + +.p-mint-700 { + background: #0e9a83; +} + +.p-mint-900 { + background: #007367; +} + +.p-blueberry { + background: #3689e6; +} + +.p-blueberry-100 { + background: #8cd5ff; +} + +.p-blueberry-300 { + background: #64baff; +} + +.p-blueberry-500 { + background: #3689e6; +} + +.p-blueberry-700 { + background: #0d52bf; +} + +.p-blueberry-900 { + background: #002e99; +} + +.p-grape { + background: #a56de2; +} + +.p-grape-100 { + background: #e4c6fa; +} + +.p-grape-300 { + background: #cd9ef7; +} + +.p-grape-500 { + background: #a56de2; +} + +.p-grape-700 { + background: #7239b3; +} + +.p-grape-900 { + background: #452981; +} + +.p-bubblegum { + background: #de3e80; +} + +.p-bubblegum-100 { + background: #fe9ab8; +} + +.p-bubblegum-300 { + background: #f4679d; +} + +.p-bubblegum-500 { + background: #de3e80; +} + +.p-bubblegum-700 { + background: #bc245d; +} + +.p-bubblegum-900 { + background: #910e38; +} + +.p-cocoa { + background: #715344; +} + +.p-cocoa-100 { + background: #a3907c; +} + +.p-cocoa-300 { + background: #8a715e; +} + +.p-cocoa-500 { + background: #715344; +} + +.p-cocoa-700 { + background: #57392d; +} + +.p-cocoa-900 { + background: #3d211b; +} + +.p-silver { + background: #abacae; +} + +.p-silver-100 { + background: #fafafa; +} + +.p-silver-300 { + background: #d4d4d4; +} + +.p-silver-500 { + background: #abacae; +} + +.p-silver-700 { + background: #7e8087; +} + +.p-silver-900 { + background: #555761; +} + +.p-slate { + background: #485a6c; +} + +.p-slate-100 { + background: #95a3ab; +} + +.p-slate-300 { + background: #667885; +} + +.p-slate-500 { + background: #485a6c; +} + +.p-slate-700 { + background: #273445; +} + +.p-slate-900 { + background: #0e141f; +} + +.p-dark { + background: #333; +} + +.p-dark-100 { + background: #666; + /* hehe */ +} + +.p-dark-300 { + background: #4d4d4d; +} + +.p-dark-500 { + background: #333; +} + +.p-dark-700 { + background: #1a1a1a; +} + +.p-dark-900 { + background: #000; +} + +.p-strawberry-color { + color: #c6262e; +} + +.p-strawberry-100-color { + color: #ff8c82; +} + +.p-strawberry-300-color { + color: #ed5353; +} + +.p-strawberry-500-color { + color: #c6262e; +} + +.p-strawberry-700-color { + color: #a10705; +} + +.p-strawberry-900-color { + color: #7a0000; +} + +.p-orange-color { + color: #f37329; +} + +.p-orange-100-color { + color: #ffc27d; +} + +.p-orange-300-color { + color: #ffa154; +} + +.p-orange-500-color { + color: #f37329; +} + +.p-orange-700-color { + color: #cc3b02; +} + +.p-orange-900-color { + color: #a62100; +} + +.p-banana-color { + color: #f9c440; +} + +.p-banana-100-color { + color: #fff394; +} + +.p-banana-300-color { + color: #ffe16b; +} + +.p-banana-500-color { + color: #f9c440; +} + +.p-banana-700-color { + color: #d48e15; +} + +.p-banana-900-color { + color: #ad5f00; +} + +.p-lime-color { + color: #68b723; +} + +.p-lime-100-color { + color: #d1ff82; +} + +.p-lime-300-color { + color: #9bdb4d; +} + +.p-lime-500-color { + color: #68b723; +} + +.p-lime-700-color { + color: #3a9104; +} + +.p-lime-900-color { + color: #206b00; +} + +.p-mint-color { + color: #28bca3; +} + +.p-mint-100-color { + color: #89ffdd; +} + +.p-mint-300-color { + color: #43d6b5; +} + +.p-mint-500-color { + color: #28bca3; +} + +.p-mint-700-color { + color: #0e9a83; +} + +.p-mint-900-color { + color: #007367; +} + +.p-blueberry-color { + color: #3689e6; +} + +.p-blueberry-100-color { + color: #8cd5ff; +} + +.p-blueberry-300-color { + color: #64baff; +} + +.p-blueberry-500-color { + color: #3689e6; +} + +.p-blueberry-700-color { + color: #0d52bf; +} + +.p-blueberry-900-color { + color: #002e99; +} + +.p-grape-color { + color: #a56de2; +} + +.p-grape-100-color { + color: #e4c6fa; +} + +.p-grape-300-color { + color: #cd9ef7; +} + +.p-grape-500-color { + color: #a56de2; +} + +.p-grape-700-color { + color: #7239b3; +} + +.p-grape-900-color { + color: #452981; +} + +.p-bubblegum-color { + color: #de3e80; +} + +.p-bubblegum-100-color { + color: #fe9ab8; +} + +.p-bubblegum-300-color { + color: #f4679d; +} + +.p-bubblegum-500-color { + color: #de3e80; +} + +.p-bubblegum-700-color { + color: #bc245d; +} + +.p-bubblegum-900-color { + color: #910e38; +} + +.p-cocoa-color { + color: #715344; +} + +.p-cocoa-100-color { + color: #a3907c; +} + +.p-cocoa-300-color { + color: #8a715e; +} + +.p-cocoa-500-color { + color: #715344; +} + +.p-cocoa-700-color { + color: #57392d; +} + +.p-cocoa-900-color { + color: #3d211b; +} + +.p-silver-color { + color: #abacae; +} + +.p-silver-100-color { + color: #fafafa; +} + +.p-silver-300-color { + color: #d4d4d4; +} + +.p-silver-500-color { + color: #abacae; +} + +.p-silver-700-color { + color: #7e8087; +} + +.p-silver-900-color { + color: #555761; +} + +.p-slate-color { + color: #485a6c; +} + +.p-slate-100-color { + color: #95a3ab; +} + +.p-slate-300-color { + color: #667885; +} + +.p-slate-500-color { + color: #485a6c; +} + +.p-slate-700-color { + color: #273445; +} + +.p-slate-900-color { + color: #0e141f; +} + +.p-dark-color { + color: #333; +} + +.p-dark-100-color { + color: #666; + /* hehe */ +} + +.p-dark-300-color { + color: #4d4d4d; +} + +.p-dark-500-color { + color: #333; +} + +.p-dark-700-color { + color: #1a1a1a; +} + +.p-dark-900-color { + color: #000; +} + +/* END OF COLORS */ + +:root { + --primary-col:linear-gradient(to bottom, #4fc5fa 0%,#0f75f5 100%); + --primary-col-ac:#0f75f5; + --bg-color:#fff; + --bg-hover-color:#f9f9f9; + --bg-front-col:#000; + --invalid-color:#d6513c; + --valid-color:#94d63c; +} + +.p-form-select { + border-radius: 5px; + display: inline-block; + font-family: -apple-system, "Inter", sans-serif; + margin: 10px; + position: relative; +} + +.p-form-select::before { + border-color: #fff transparent transparent; + border-style: solid; + border-width: 5px; + content: ""; + pointer-events: none; + position: absolute; + right: 5px; + top: calc(50% - 3px); + z-index: 3; +} + +.p-form-select::after { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border-bottom-right-radius: 5px; + border-top-right-radius: 5px; + bottom: 0; + content: ""; + display: block; + height: 100%; + pointer-events: none; + position: absolute; + right: 0; + top: 0; + width: 20px; +} + +.p-form-select > select { + -webkit-appearance: none; + background: #fff; + border: 1px solid #cacaca; + border-radius: 5px; + font-size: 14px; + margin: 0; + outline: none; + padding: 5px 30px 5px 10px; + position: relative; + width: 100%; +} + +.p-form-text:invalid, +.p-form-text-alt:invalid, +.p-form-select > select:invalid { + border-color: var(--invalid-color); +} + +.p-form-text:valid, +.p-form-text-alt:valid, +.p-form-select > select:valid { + border-color: var(--valid-color); +} + +.p-form-text:placeholder-shown, +.p-form-text-alt:placeholder-shown, +.p-form-select > select:placeholder-shown { + border-color: #cacaca; +} + +.p-form-text { + -webkit-appearance: none; + box-shadow: none; + background: #fff; + border: 1px solid #cacaca; + border-radius: 5px; + font-family: -apple-system, "Inter", sans-serif; + margin: 10px; + outline: 0; + padding: 5px; + resize: none; + transition: border-color 200ms; +} + +.p-form-text-alt { + -webkit-appearance: none; + box-shadow: none; + background: #fff; + border: 0px; + border-bottom: 2px solid #cacaca; + padding: 10px; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + margin: 10px; +} + +.p-form-text-alt::placeholder { + color: #cacaca; +} + +.p-form-text-alt:focus { + outline: 3px solid #bed8f9; +} + +.p-form-no-validate:valid, +.p-form-no-validate:invalid, +.p-form-no-validate > select:valid, +.p-form-no-validate > select:invalid { + border-color: #cacaca; +} + +.p-form-text:focus { + border-color: #0f75f5; +} + +textarea.p-form-text { + -webkit-appearance: none; + height: 100px; +} + +.p-form-truncated { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.p-form-text[type=password] { + font-family: caption; +} + +.p-form-label, +.p-form-radio-cont, +.p-form-checkbox-cont { + font-family: -apple-system, "Inter", sans-serif; +} + +.p-form-label { + display: block; +} + +.p-form-radio-cont, +.p-form-checkbox-cont { + align-items: center; + display: inline-flex; + margin: 0 10px; +} + +.p-form-radio-cont > input + span, +.p-form-checkbox-cont > input + span { + background: #fff; + border: 1px solid #cacaca; + border-radius: 50%; + cursor: pointer; + display: inline-block; + height: 20px; + margin-right: 5px; + position: relative; + transition: background 0.2s; + width: 20px; +} + +.p-form-radio-cont > input + span:hover { + background: #f9f9f9; +} + +.p-form-radio-cont > input, +.p-form-checkbox-cont > input { + opacity: 0; + pointer-events: none; + position: absolute; +} + +.p-form-radio-cont > input + span::after { + background: #fff; + border-radius: 50%; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + content: ""; + display: block; + height: 30%; + left: calc(50% - 15%); + opacity: 0; + position: absolute; + top: calc(50% - 15%); + transform: scale(2); + transition: opacity 0.2s, transform 0.3s; + width: 30%; +} + +.p-form-radio-cont > input:checked + span { + background: #0f75f5; +} + +.p-form-radio-cont > input:checked + span::after { + opacity: 1; + transform: scale(1); +} + +.p-form-checkbox-cont > input + span { + border-radius: 5px; +} + +.p-form-checkbox-cont > input:checked + span { + background: #0f75f5; +} + +.p-form-checkbox-cont > input + span::before, +.p-form-checkbox-cont > input + span::after { + background: #fff; + border-radius: 20px; + content: ""; + display: block; + height: 8%; + position: absolute; +} + +.p-form-checkbox-cont > input + span::before { + right: 30%; + top: 15%; + transform: rotate(-65deg); + transform-origin: top right; + width: 70%; +} + +.p-form-checkbox-cont > input + span::after { + left: 30%; + top: 43%; + transform: rotate(60deg); + transform-origin: top left; + width: 40%; +} + +.p-form-button { + -webkit-appearance: none; + background: #fff; + border: 1px solid #cacaca; + border-radius: 5px; + color: #333230; + display: inline-block; + font-size: 17px; + margin: 10px; + padding: 5px 20px; + text-decoration: none; + /* text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); */ +} + +.p-form-send { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border: 0; + color: #fff; +} + +.p-form-send:active { + background: #0f75f5; +} + +.p-form-invalid, +.p-form-invalid:placeholder-shown, +.p-form-invalid:valid, +.p-form-invalid:invalid { + border-color: var(--invalid-color); +} + +.p-form-valid, +.p-form-valid:placeholder-shown, +.p-form-valid:valid, +.p-form-valid:invalid { + border-color: var(--valid-color); +} + +.p-form-switch { + --width: 80px; + cursor: pointer; + display: inline-block; +} + +.p-form-switch > input:checked + span::after { + left: calc(100% - calc(var(--width) / 2.1)); +} + +.p-form-switch > input:checked + span { + background: #60c35b; +} + +.p-form-switch > span { + background: #e0e0e0; + border: 1px solid #d3d3d3; + border-radius: 500px; + display: block; + height: calc(var(--width) / 2); + overflow: hidden; + position: relative; + transition: all 0.2s; + width: var(--width); +} + +.p-form-switch > span::after { + background: #f9f9f9; + border-radius: 50%; + box-shadow: 0px 3px 1px rgba(0, 0, 0, 0.1), 0px 1px 1px rgba(0, 0, 0, 0.16), 0px 3px 8px rgba(0, 0, 0, 0.15); + content: ""; + height: 90%; + left: 3%; + position: absolute; + top: 4.5%; + transition: all 0.2s; + width: 45%; +} + +.p-form-switch > input { + display: none; +} + +input[type=range].p-form-range { + width: 100%; + margin: 11.5px 0; + background-color: transparent; + -webkit-appearance: none; +} +input[type=range].p-form-range:focus { + outline: none; +} +input[type=range].p-form-range::-webkit-slider-runnable-track { + background: #cacaca; + border: 0; + width: 100%; + height: 2px; + cursor: pointer; +} +input[type=range].p-form-range::-webkit-slider-thumb { + margin-top: -11.5px; + width: 25px; + height: 25px; + background: #ffffff; + border: 1px solid rgba(115, 115, 115, 0.6); + border-radius: 30px; + cursor: pointer; + box-shadow: 0 3px 1px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.16), 0 3px 8px rgba(0, 0, 0, 0.15); + -webkit-appearance: none; +} +input[type=range].p-form-range:focus::-webkit-slider-runnable-track { + background: #d7d7d7; +} +input[type=range].p-form-range::-moz-range-track { + background: #cacaca; + border: 0; + width: 100%; + height: 2px; + cursor: pointer; +} +input[type=range].p-form-range::-moz-range-thumb { + width: 25px; + height: 25px; + background: #ffffff; + border: 1px solid rgba(115, 115, 115, 0.6); + border-radius: 30px; + box-shadow: 0 3px 1px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.16), 0 3px 8px rgba(0, 0, 0, 0.15); + cursor: pointer; +} +input[type=range].p-form-range::-ms-track { + background: transparent; + border-color: transparent; + border-width: 26.5px 0; + color: transparent; + width: 100%; + height: 2px; + cursor: pointer; +} +input[type=range].p-form-range::-ms-fill-lower { + background: #bdbdbd; + border: 0; +} +input[type=range].p-form-range::-ms-fill-upper { + background: #cacaca; + border: 0; +} +input[type=range].p-form-range::-ms-thumb { + width: 25px; + height: 25px; + background: #ffffff; + border: 1px solid rgba(115, 115, 115, 0.6); + border-radius: 30px; + cursor: pointer; + box-shadow: 0 3px 1px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.16), 0 3px 8px rgba(0, 0, 0, 0.15); + margin-top: 0px; + /*Needed to keep the Edge thumb centred*/ +} +input[type=range].p-form-range:focus::-ms-fill-lower { + background: #cacaca; +} +input[type=range].p-form-range:focus::-ms-fill-upper { + background: #d7d7d7; +} +/*TODO: Use one of the selectors from https://stackoverflow.com/a/20541859/7077589 and figure out +how to remove the virtical space around the range input in IE*/ +@supports (-ms-ime-align:auto) { + /* Pre-Chromium Edge only styles, selector taken from hhttps://stackoverflow.com/a/32202953/7077589 */ + input[type=range].p-form-range { + margin: 0; + /*Edge starts the margin from the thumb, not the track as other browsers do*/ + } +} + + +/* END OF FORMS */ + +.p-layout .p-large-title { + font-size: 2.75rem; +} + +.p-layout h1 { + font-size: 2.25rem; +} + +.p-layout h2 { + font-size: 1.75rem; +} + +.p-layout h3 { + font-size: 1.58rem; +} + +.p-layout .p-headline { + font-size: 1.34rem; + font-weight: bold; +} + +.p-layout p { + font-size: 1.15rem; +} + +.p-layout a, +.p-layout input { + font-size: 1.14rem; +} + +.p-layout .p-callout { + font-size: 1.14rem; +} + +.p-layout .p-subhead { + font-size: 1.167rem; +} + +.p-layout .p-footnote { + font-size: 1.07rem; +} + +.p-layout .p-caption { + font-size: 0.91rem; +} + +/* END OF LAYOUT */ + +:root { + --font: -apple-system, "Inter", sans-serif; + --bg-hover-color: #f9f9f9; + --primary-col-ac: #0f75f5; +} + +.p-modal-opened { + overflow: hidden; +} + +.p-modal-background { + background: rgba(0, 0, 0, 0.3); + height: 100vh; + left: 0; + opacity: 0; + pointer-events: none; + position: fixed; + top: 0; + transition: all 0.3s; + width: 100vw; + z-index: 5; +} + +.p-modal { + background: rgba(255, 255, 255, 0.85); + border-radius: 20px; + top: calc(50% - 20vh); + bottom: unset; + box-shadow: 0 10px 20px -15px; + font-family: var(--font); + left: calc(50% - 20vw); + opacity: 0; + overflow: hidden; + pointer-events: none; + position: fixed; + text-align: center; + transform: scale(1.5); + transition: opacity 0.3s, transform 0.3s; + width: 40vw; + z-index: 9; +} + +.p-modal.active { + backdrop-filter: saturate(180%) blur(10px); + opacity: 1; + pointer-events: auto; + transform: scale(1); +} + +.p-modal-button-container { + border-radius: 20px; + display: flex; +} + +.p-modal-button-container > a { + border-top: 1px solid rgba(0, 0, 0, 0.1); + color: var(--primary-col-ac); + padding: 30px 0%; + text-decoration: none; + width: 100%; +} + +.p-modal-button-container > a:nth-child(2), +.p-modal-button-container > a:nth-child(3) { + border-left: 1px solid rgba(0, 0, 0, 0.1); +} + +.nowactive { + opacity: 1; + pointer-events: auto; +} + +.p-modal p { + padding: 0% 5%; +} + +@supports not (backdrop-filter: blur(5px)) { + .p-modal { + background: #fff; + } +} +@media (max-width: 568px) { + .p-modal { + bottom: 20%; + left: 15%; + top: unset; + width: 70vw; + } + + .p-modal p { + font-size: 15px; + padding: 0% 10%; + } + + .p-modal-button-container { + display: block; + } + + .p-modal-button-container > a { + border-left: 0 !important; + display: block; + padding: 2vh 0%; + } +} + +/* END OF MODALS */ + +.p-segmented-controls { + --color-segmented: #3689e6; + --color-lighter-segment: #d2e3f9; + background: #fff; + border: 1px solid var(--color-segmented); + border-radius: 5px; + display: flex; + flex-wrap: wrap; + font-family: -apple-system, "Inter", sans-serif; + margin-top: 10px; + overflow: hidden; + width: 100%; +} +.p-segmented-controls a { + color: var(--color-segmented); + flex: auto; + padding: 10px; + text-align: center; + text-decoration: none; + transition: all 0.5s; +} +.p-segmented-controls a.active { + background: var(--color-segmented); + color: #fff; +} +.p-segmented-controls a:not(:first-child) { + border-left: 1px solid currentColor; +} + +.p-segmented-radius { + border-radius: 30px; +} + +.p-segmented-internal-radius a, +.p-segmented-internal-radius a:not(:first-child) { + border: 0; + border-radius: 30px; +} + +.p-segmented-controls-alt a:not(:first-child) { + border: 0; +} +.p-segmented-controls-alt a:not(:first-child).active { + background: var(--color-lighter-segment); + color: var(--color-segmented); + font-weight: bold; +} + +.p-segmented-outline { + border: 2px solid var(--color-segmented); +} +.p-segmented-outline a:not(:first-child) { + border-left: 2px solid var(--color-segmented); +} + +.p-segmented-controls-outline-alt a:not(:first-child) { + border: 2px solid transparent; +} + +.p-segmented-controls-outline-alt { + border-radius: 30px; +} +.p-segmented-controls-outline-alt a { + border: 2px solid transparent; + border-radius: 30px; +} +.p-segmented-controls-outline-alt a.active { + background: #fff; + border-color: var(--color-segmented); + border-radius: 30px; + color: var(--color-segmented); + font-weight: bold; +} + +.p-segmented-grey { + --color-segmented: #555761; + --color-lighter-segment: #d4d4d4; +} + +/* END OF SEGMENTED CONTROLS */ + +.p-shadow-1 { + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); +} + +.p-shadow-2 { + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); +} + +.p-shadow-3 { + box-shadow: 0 10px 18px rgba(0, 0, 0, 0.3); +} + +.p-shadow-4 { + box-shadow: 0 25px 30px rgba(0, 0, 0, 0.2); +} + +.p-to-shadow-4, +.p-to-shadow-3, +.p-to-shadow-2, +.p-to-shadow-1 { + transition-timing-function: ease; + transition: box-shadow 0.5s; +} + +.p-to-shadow-1:hover { + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);> +} + +.p-to-shadow-2:hover { + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); +} + +.p-to-shadow-3:hover { + box-shadow: 0 10px 18px rgba(0, 0, 0, 0.3); +} + +.p-to-shadow-4:hover { + box-shadow: 0 25px 30px rgba(0, 0, 0, 0.2); +} + + +/* END OF SHADOWS */ + +.p-tabs-container { + background: #e3e3e3; + border: 1px solid #e0e0e0; + padding: 1em; +} + +.p-tabs-container.p-light { + background: none; + border: none; +} + +.p-tabs-container.p-light .p-panels { + margin-top: 0; + border-radius: 0; + padding: 0; +} + +.p-tabs { + display: flex; + justify-content: center; +} + +.p-tabs > :nth-of-type(1) { + border-radius: 5px 0 0 5px; +} + +.p-tabs > :last-child { + border-radius: 0 5px 5px 0; +} + +.p-tab { + margin: 0; + padding: 5px 35px; + background: #fff; + color: #333230; + text-decoration: none; + /* text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); */ + border: 1px solid #cacaca; + display: inline-block; + font-size: 17px; + font-family: -apple-system, "Inter", sans-serif; + cursor: pointer; +} + +.p-tab:focus { + outline: 0; +} + +.p-is-active { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border: 0; + color: #fff; +} + +.p-panels { + margin-top: 1em; + background: #fff; + border-radius: 3px; + position: relative; + padding: 0.8em; + overflow: hidden; +} + +.p-panel.p-is-active { + opacity: 1; + pointer-events: all; + background: none; + color: inherit; + position: static; +} + +.p-panel { + position: absolute; + opacity: 0; + pointer-events: none; +} + +@media (max-width: 768px) { + .p-tabs { + overflow: auto; + } + .p-tab { + font-size: 0.8em; + padding: 5px 28px; + } + .p-tabs-container { + padding: 0.8em; + } + + .p-panels { + padding: 0.8em; + } +} + +@media screen and (max-width: 496px) { + .p-tab { + text-align: center; + padding: 5px 18px; + } + .p-tabs-container { + padding: 0.5em; + } + + .p-panels { + padding: 0.5em; + margin-top: 0.5em; + } +} + +@media screen and (max-width: 378px) { + .p-tab { + text-align: center; + padding: 5px 10px; + } + .p-tabs-container { + padding: 0.5em; + } + + .p-panels { + padding: 0.5em; + margin-top: 0.5; + } +} + +.p-mobile-tabs { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + padding: 15px 0px; + border-top: 1px solid #949494; + background: rgba(202, 202, 202, 0.8); + backdrop-filter: blur(10px); + display: flex; + font-family: -apple-system, "Inter", sans-serif; +} + +.p-mobile-tabs > div { + flex: auto; + text-align: center; +} + +.p-mobile-tabs a { + text-decoration: none; + color: #555; + transition: color 0.5s; + display: inline-block; + font-size: 0.8rem; +} + +.p-mobile-tabs a.active { + color: #0f75f5; + font-weight: 600; +} + +.p-mobile-tabs svg { + display: block; + margin: auto; + margin-bottom: 0.2rem; +} + +.p-mobile-tabs--content { + display: none; +} + +.p-mobile-tabs--content.active { + display: block; +} + +/* END OF TABS */ + + +.p-action-background{ + background: rgba(0, 0, 0, 0.3); + height: 100vh; + left: 0; + opacity: 0; + pointer-events: none; + position: fixed; + top: 0; + transition: all 0.3s; + width: 100vw; + z-index: 5; +} + +.p-action-background.nowactive { + opacity: 1; + pointer-events: auto; +} + + +.p-action-big-container{ + position:fixed; + width: 100%; + box-sizing: border-box; + padding: 1rem 5vw; + bottom:0; +} + +.p-action-container{ + background: rgba(255, 255, 255, 0.8); + backdrop-filter: blur(10px); + display:block; + margin:auto; + margin-bottom: 10px; + border-radius: 10px; +} + +.p-action-big-container .p-action-container:first-child{ + margin-bottom:10px; +} + +.p-action--intern{ + display:block; + margin:auto; + text-align:center; + padding: 15px 0; + border-bottom: 1px solid #bfbfbf; + font-weight: 500; + color: #0f75f5; + text-decoration:none; +} + +.p-action-destructive{ + color: #c6262e; +} + +.p-action-neutral{ + color: #555761; +} + +.p-action-cancel, .p-action-container a:last-child{ + border-bottom:none; +} + +.p-action-cancel{ + font-weight:bold; +} + +.p-action-icon{ + position:relative; +} +.p-action-icon svg, .p-action-icon img{ + position:absolute; + left:5%; + top:50%; + transform:translateY(-50%); +} + +.p-action-icon-inline{ + text-align: left; + display: flex; + align-items: center; +} + +.p-action-icon-inline svg, .p-action-icon-inline img{ + margin-left: 5%; + margin-right: 3%; +} + +.p-action-title{ + padding: 30px 15px; + border-bottom: 1px solid #bfbfbf; +} + +.p-action-title--intern,.p-action-text{ + margin:0; + color:#555761; +} + +.p-action-title--intern{ + margin-bottom: .3rem; +} + +@supports not (backdrop-filter: blur(10px)) { + .p-action-container { + background: rgba(255,255,255,.95); + } +} + +.p-action-big-container{ + -webkit-transform: translateY(30%); + transform: translateY(30%); + opacity: 0; + transition: opacity 0.4s, transform 0.4s; + transition-timing-function: ease-in-out; +} + +.p-action-big-container.active { +-webkit-transform: translateY(0); + transform: translateY(0); + opacity: 1; +} + + +/* END OF ACTIONS */ diff --git a/v3/examples/ios/frontend/public/style.css b/v3/examples/ios/frontend/public/style.css new file mode 100644 index 000000000..5bf3738a9 --- /dev/null +++ b/v3/examples/ios/frontend/public/style.css @@ -0,0 +1,327 @@ +:root { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, + sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + /* Desktop defaults (mobile overrides below) */ + --bg: #ffffff; + --fg: #213547; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +/* Mobile bottom tabs layout (works on all themes) */ +.mobile-pane { + display: flex; + flex-direction: column; + width: 100%; + max-width: 520px; + height: 70vh; /* fixed-height main screen */ + border-radius: 12px; +} +.mobile-pane .p-mobile-tabs-content { + position: relative; + flex: 1 1 auto; + overflow: hidden; /* contain panels */ + display: flex; + flex-direction: column; +} +.p-mobile-tabs-content .p-mobile-tabs--content { + display: none; + overflow: auto; /* scroll inside content area */ + -webkit-overflow-scrolling: touch; + padding: 8px 8px 16px; +} +.p-mobile-tabs-content .p-mobile-tabs--content.active { display: block; } + +*, *::before, *::after { + box-sizing: border-box; +} + +/* Prefer system fonts on mobile; remove custom font to reduce bundle size */ + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +/* Remove generic button styling to allow Puppertino .btn to control buttons */ + +.result { + height: 20px; + line-height: 20px; +} + +html, +body { + height: 100%; + width: 100%; + overflow-x: hidden; /* prevent horizontal overflow */ + overflow-y: auto; /* allow vertical scroll if needed */ +} + +body { + margin: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-width: 320px; + /* Use small viewport units to avoid iOS Safari URL bar issues */ + min-height: 100svh; + height: auto; /* avoid forcing overflow */ + /* Equal responsive spacing top & bottom */ + padding-block: clamp(8px, 4vh, 48px); + color: var(--fg); + background-color: var(--bg); +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + /* Responsive spacing between elements */ + gap: clamp(8px, 2vh, 24px); + width: 100%; + max-width: 480px; + padding-inline: 16px; +} + +h1 { + /* Responsive heading size */ + font-size: clamp(1.6rem, 6vw, 3.2rem); + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + /* Responsive inner padding: horizontal only, no extra top/bottom */ + padding: 0 clamp(12px, 4vw, 32px); + text-align: center; +} + +.logo { + /* Consistent visual size across images: fix height, auto width */ + height: clamp(80px, 18vh, 140px); + width: auto; + max-width: 80vw; + padding: 0.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +/* Tabs: default hide panels, show one matching the checked radio */ +.tabs .tabs-content .tab-panel { display: none; } +.tabs input#tab-js:checked ~ .tabs-content [data-tab="tab-js"] { display: block; } +.tabs input#tab-go:checked ~ .tabs-content [data-tab="tab-go"] { display: block; } + +/* Sticky tabs header */ +.tabs .tabs-header { + position: sticky; + top: env(safe-area-inset-top); + z-index: 5; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + padding: 8px 0; + background: var(--bg); + backdrop-filter: saturate(1.2) blur(4px); +} + +/* Subtle divider under sticky header */ +.tabs .tabs-header::after { + content: ""; + grid-column: 1 / -1; + height: 1px; + background: rgba(0,0,0,0.08); + margin-top: 8px; +} + +/* Mobile-specific light mode */ +@media (max-width: 768px) and (prefers-color-scheme: light) { + :root { + --fg: #213547; + --bg: #ffffff; + } + + a:hover { + color: #747bff; + } + + /* allow Puppertino to style .btn */ + + .input-box .input { + color: #111827; + background-color: #f3f4f6; + border: 1px solid #e5e7eb; /* show border in light mode */ + border-radius: 8px; + } + + button:hover { + border-color: #d1d5db; /* slate-300 */ + } + + .input-box .input:focus { + border-color: #9ca3af; /* gray-400 */ + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); /* subtle focus ring */ + } +} + +/* let Puppertino handle .btn hover */ + +.input-box .input { + border: 1px solid transparent; /* default; themed in media queries */ + border-radius: 8px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + background-color: rgba(255, 255, 255, 1); + outline: 2px solid transparent; + outline-offset: 2px; +} + +/* Mobile-specific dark mode */ +@media (max-width: 768px) and (prefers-color-scheme: dark) { + :root { + color: rgba(255, 255, 255, 0.88); + --fg: rgba(255, 255, 255, 0.88); + --bg: #0f1115; /* deep dark background */ + } + + a { + color: #8ea2ff; + } + + a:hover { + color: #aab6ff; + } + + /* allow Puppertino to style .btn in dark mode */ + + .input-box .input { + background-color: #111827; /* gray-900 */ + color: #e5e7eb; + caret-color: #ffffff; + border: 1px solid #374151; /* slate-700 */ + } + + .input-box .input:hover, + .input-box .input:focus { + background-color: #0b1220; + border-color: #4b5563; /* slate-600 */ + } + + /* allow Puppertino to handle active state */ +} + +/* Mobile baseline overrides (apply to both light and dark) */ +@media (max-width: 768px) { + /* Prevent iOS zoom on focus */ + input, textarea, select, button { font-size: 16px; } + + /* Make layout vertical and scrollable */ + html, body { + height: auto; + min-height: 100svh; + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + } + + body { + padding-top: env(safe-area-inset-top); + padding-bottom: env(safe-area-inset-bottom); + position: static; + } + + .container { + width: 100%; + max-width: 520px; /* allow a bit wider on phones */ + align-items: stretch; + } + + /* Stack controls vertically with full-width tap targets */ + .input-box { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 10px; + } + + .input-box .input, + .input, + button, + .p-btn { + width: 100%; + min-height: 44px; /* comfortable touch target */ + } + + /* Tabs vertical and full-width */ + .tabs { + display: grid; + grid-template-columns: 1fr; + gap: 8px; + } + .tabs .p-btn { width: 100%; } + + /* Cap device info height for readability */ + #deviceInfo { + max-height: 30vh; + overflow: auto; + padding: 8px; + border-radius: 8px; + background: rgba(0,0,0,0.04); + } +} \ No newline at end of file diff --git a/v3/examples/ios/frontend/public/wails.png b/v3/examples/ios/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/ios/frontend/public/wails.png differ diff --git a/v3/examples/ios/frontend/vite.config.js b/v3/examples/ios/frontend/vite.config.js new file mode 100644 index 000000000..87b093bc3 --- /dev/null +++ b/v3/examples/ios/frontend/vite.config.js @@ -0,0 +1,11 @@ +import { defineConfig } from 'vite'; +import path from 'path'; + +export default defineConfig({ + resolve: { + alias: { + // Use the local repo runtime sources instead of the published package + '@wailsio/runtime': path.resolve(__dirname, '../../../internal/runtime/desktop/@wailsio/runtime/src/index.ts'), + }, + }, +}); diff --git a/v3/examples/ios/go.mod b/v3/examples/ios/go.mod new file mode 100644 index 000000000..d2e86c73a --- /dev/null +++ b/v3/examples/ios/go.mod @@ -0,0 +1,49 @@ +module ios-example + +go 1.25 + +require github.com/wailsapp/wails/v3 v3.0.0-dev + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-git/go-git/v5 v5.16.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/lmittmann/tint v1.1.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.52.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/wailsapp/go-webview2 v1.0.23 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../../../v3 diff --git a/v3/examples/ios/go.sum b/v3/examples/ios/go.sum new file mode 100644 index 000000000..56f1153ea --- /dev/null +++ b/v3/examples/ios/go.sum @@ -0,0 +1,147 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= +github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/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-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/examples/ios/greetservice.go b/v3/examples/ios/greetservice.go new file mode 100644 index 000000000..8972c39cd --- /dev/null +++ b/v3/examples/ios/greetservice.go @@ -0,0 +1,7 @@ +package main + +type GreetService struct{} + +func (g *GreetService) Greet(name string) string { + return "Hello " + name + "!" +} diff --git a/v3/examples/ios/ios_runtime_events_ios.go b/v3/examples/ios/ios_runtime_events_ios.go new file mode 100644 index 000000000..99ccf66d4 --- /dev/null +++ b/v3/examples/ios/ios_runtime_events_ios.go @@ -0,0 +1,62 @@ +//go:build ios + +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +// registerIOSRuntimeEventHandlers registers Go-side event listeners that mutate iOS WKWebView at runtime. +func registerIOSRuntimeEventHandlers(app *application.App) { + // Helper to fetch boolean from event data. Accepts {"enabled":bool} or a bare bool. + getBool := func(data any, key string, def bool) bool { + switch v := data.(type) { + case bool: + return v + case map[string]any: + if raw, ok := v[key]; ok { + if b, ok := raw.(bool); ok { + return b + } + } + } + return def + } + // Helper to fetch string from event data. Accepts {"ua":string} or bare string. + getString := func(data any, key string) string { + switch v := data.(type) { + case string: + return v + case map[string]any: + if raw, ok := v[key]; ok { + if s, ok := raw.(string); ok { + return s + } + } + } + return "" + } + + app.Event.On("ios:setScrollEnabled", func(e *application.CustomEvent) { + application.IOSSetScrollEnabled(getBool(e.Data, "enabled", true)) + }) + app.Event.On("ios:setBounceEnabled", func(e *application.CustomEvent) { + application.IOSSetBounceEnabled(getBool(e.Data, "enabled", true)) + }) + app.Event.On("ios:setScrollIndicatorsEnabled", func(e *application.CustomEvent) { + application.IOSSetScrollIndicatorsEnabled(getBool(e.Data, "enabled", true)) + }) + app.Event.On("ios:setBackForwardGesturesEnabled", func(e *application.CustomEvent) { + application.IOSSetBackForwardGesturesEnabled(getBool(e.Data, "enabled", false)) + }) + app.Event.On("ios:setLinkPreviewEnabled", func(e *application.CustomEvent) { + application.IOSSetLinkPreviewEnabled(getBool(e.Data, "enabled", true)) + }) + app.Event.On("ios:setInspectableEnabled", func(e *application.CustomEvent) { + application.IOSSetInspectableEnabled(getBool(e.Data, "enabled", true)) + }) + app.Event.On("ios:setCustomUserAgent", func(e *application.CustomEvent) { + ua := getString(e.Data, "ua") + application.IOSSetCustomUserAgent(ua) + }) +} diff --git a/v3/examples/ios/ios_runtime_events_stub.go b/v3/examples/ios/ios_runtime_events_stub.go new file mode 100644 index 000000000..4c7376d7d --- /dev/null +++ b/v3/examples/ios/ios_runtime_events_stub.go @@ -0,0 +1,8 @@ +//go:build !ios + +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +// Non-iOS: no-op so examples build on other platforms +func registerIOSRuntimeEventHandlers(app *application.App) {} diff --git a/v3/examples/ios/main.go b/v3/examples/ios/main.go new file mode 100644 index 000000000..f49b73cd8 --- /dev/null +++ b/v3/examples/ios/main.go @@ -0,0 +1,88 @@ +package main + +import ( + "embed" + _ "embed" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// Wails uses Go's `embed` package to embed the frontend files into the binary. +// Any files in the frontend/dist folder will be embedded into the binary and +// made available to the frontend. +// See https://pkg.go.dev/embed for more information. + +//go:embed all:frontend/dist +var assets embed.FS + +// main function serves as the application's entry point. It initializes the application, creates a window, +// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and +// logs any error that might occur. +func main() { + + // Create a new Wails application by providing the necessary options. + // Variables 'Name' and 'Description' are for application metadata. + // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files. + // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances. + // 'Mac' options tailor the application when running an macOS. + app := application.New(application.Options{ + Name: "ios", + Description: "A demo of using raw HTML & CSS", + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + IOS: application.IOSOptions{ + EnableNativeTabs: true, + NativeTabsItems: []application.NativeTabItem{ + {Title: "Home", SystemImage: "house"}, + {Title: "Settings", SystemImage: "gear"}, + }, + }, + }) + + // Register iOS runtime event handlers so the frontend can emit events + // to toggle WKWebView settings at runtime (Go path). + registerIOSRuntimeEventHandlers(app) + + // Create a new window with the necessary options. + // 'Title' is the title of the window. + // 'Mac' options tailor the window when running on macOS. + // 'BackgroundColour' is the background colour of the window. + // 'URL' is the URL that will be loaded into the webview. + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + BackgroundColour: application.NewRGB(27, 38, 54), + URL: "/", + }) + + // Create a goroutine that emits an event containing the current time every second. + // The frontend can listen to this event and update the UI accordingly. + go func() { + for { + now := time.Now().Format(time.RFC1123) + app.Event.Emit("time", now) + time.Sleep(time.Second) + } + }() + + // Run the application. This blocks until the application has been exited. + err := app.Run() + + // If an error occurred while running the application, log it and exit. + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/keybindings/README.md b/v3/examples/keybindings/README.md new file mode 100644 index 000000000..2180cc9d0 --- /dev/null +++ b/v3/examples/keybindings/README.md @@ -0,0 +1,21 @@ +# Keybindings Example + +This simple example demonstrates how to use keybindings in your application. +Run the example and press `Ctrl/CMD+Shift+C` to center the focused window. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | + diff --git a/v3/examples/keybindings/main.go b/v3/examples/keybindings/main.go new file mode 100644 index 000000000..b07bf366b --- /dev/null +++ b/v3/examples/keybindings/main.go @@ -0,0 +1,50 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "log" +) + +func main() { + app := application.New(application.Options{ + Name: "Key Bindings Demo", + Description: "A demo of the Key Bindings Options", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + KeyBindings: map[string]func(window application.Window){ + "shift+ctrl+c": func(window application.Window) { + window.Center() + }, + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "Window 1", + Title: "Window 1", + URL: "https://wails.io", + KeyBindings: map[string]func(window application.Window){ + "F12": func(window application.Window) { + window.OpenDevTools() + }, + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "Window 2", + Title: "Window 2", + URL: "https://google.com", + KeyBindings: map[string]func(window application.Window){ + "F12": func(window application.Window) { + println("Window 2: Toggle Dev Tools") + }, + }, + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/linux_status.org b/v3/examples/linux_status.org new file mode 100644 index 000000000..fdb31ac27 --- /dev/null +++ b/v3/examples/linux_status.org @@ -0,0 +1,28 @@ + +* Status + +| Example | Status | Notes | +|--------------+-----------------+-------------------------------------------------------------------------------| +| binding | works | | +| build | works | removed OS X specific env variables from default target | +| clipboard | works | | +| contextmenus | works (partial) | | +| dev | works | purpose? | +| dialogs | | broken | +| drag-n-drop | works | | +| events | partial | receives WailsEvents - not ApplicationEvents | +| frameless | partial | drag areas do not function | +| hide-window | partial | crash on windowShow - believe this is because window is being destroyed not hidden | +| keybindings | working | | +| menu | working | Lock WebviewWindow Resize isn't correct | +| oauth | failed | Can't type in window - but can paste - redirect failed as well | +| plain | works | | +| plugins | works | Might should provide example commands or something. | +| screen | failed | | +| server | works | | +| systray | works | | +| video | works | binary is named 'frameless' | +| window | partial | Screens related stuff isn't implemented | +| wml | partial | | + + diff --git a/v3/examples/liquid-glass/.gitignore b/v3/examples/liquid-glass/.gitignore new file mode 100644 index 000000000..45d2406b0 --- /dev/null +++ b/v3/examples/liquid-glass/.gitignore @@ -0,0 +1 @@ +liquid-glass-demo diff --git a/v3/examples/liquid-glass/README.md b/v3/examples/liquid-glass/README.md new file mode 100644 index 000000000..33875af54 --- /dev/null +++ b/v3/examples/liquid-glass/README.md @@ -0,0 +1,70 @@ +# Liquid Glass Demo for Wails v3 + +This demo showcases the native Liquid Glass effect available in macOS 15.0+ with fallback to NSVisualEffectView for older systems. + +## Features Demonstrated + +### Window Styles + +1. **Light Glass** - Clean, light appearance with no tint +2. **Dark Glass** - Dark themed glass effect +3. **Vibrant Glass** - Enhanced vibrant effect for maximum transparency +4. **Tinted Glass** - Blue tinted glass with custom RGBA color +5. **Sheet Material** - Using specific NSVisualEffectMaterialSheet +6. **HUD Window** - Ultra-light HUD window material +7. **Content Background** - Content background material with warm tint + +### Customization Options + +- **Style**: `LiquidGlassStyleAutomatic`, `LiquidGlassStyleLight`, `LiquidGlassStyleDark`, `LiquidGlassStyleVibrant` +- **Material**: Direct NSVisualEffectMaterial selection (when NSGlassEffectView is not available) + - `NSVisualEffectMaterialAppearanceBased` + - `NSVisualEffectMaterialLight` + - `NSVisualEffectMaterialDark` + - `NSVisualEffectMaterialSheet` + - `NSVisualEffectMaterialHUDWindow` + - `NSVisualEffectMaterialContentBackground` + - `NSVisualEffectMaterialUnderWindowBackground` + - `NSVisualEffectMaterialUnderPageBackground` + - And more... +- **CornerRadius**: Rounded corners (0 for square corners) +- **TintColor**: Custom RGBA tint overlay +- **GroupID**: For grouping multiple glass windows (future feature) +- **GroupSpacing**: Spacing between grouped windows (future feature) + +### Running the Demo + +```bash +go build -o liquid-glass-demo . +./liquid-glass-demo +``` + +### Requirements + +- macOS 10.14+ (best experience on macOS 26.0+ with native NSGlassEffectView) +- Wails v3 + +### Implementation Details + +The implementation uses: +- Native `NSGlassEffectView` on macOS 26.0+ for authentic glass effect +- Falls back to `NSVisualEffectView` on older systems +- Runtime detection using `NSClassFromString` for compatibility +- Key-Value Coding (KVC) for dynamic property setting + +### Example Usage + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + Backdrop: application.MacBackdropLiquidGlass, + InvisibleTitleBarHeight: 500, // Make window draggable + LiquidGlass: application.MacLiquidGlass{ + Style: application.LiquidGlassStyleLight, + Material: application.NSVisualEffectMaterialHUDWindow, + CornerRadius: 20.0, + TintColor: &application.RGBA{Red: 0, Green: 100, Blue: 200, Alpha: 50}, + }, + }, +}) +``` \ No newline at end of file diff --git a/v3/examples/liquid-glass/index.html b/v3/examples/liquid-glass/index.html new file mode 100644 index 000000000..7f9ff4545 --- /dev/null +++ b/v3/examples/liquid-glass/index.html @@ -0,0 +1,50 @@ + + + + + + Wails Liquid Glass + + + +
        + +

        LIQUID GLASS

        +
        + + \ No newline at end of file diff --git a/v3/examples/liquid-glass/main.go b/v3/examples/liquid-glass/main.go new file mode 100644 index 000000000..61f338b96 --- /dev/null +++ b/v3/examples/liquid-glass/main.go @@ -0,0 +1,237 @@ +package main + +import ( + _ "embed" + "encoding/base64" + "fmt" + "log" + "runtime" + "strings" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed index.html +var indexHTML string + +//go:embed wails-logo.png +var wailsLogo []byte + +func main() { + app := application.New(application.Options{ + Name: "Wails Liquid Glass Demo", + Description: "Demonstrates the native Liquid Glass effect on macOS", + }) + + // Check if running on macOS + if runtime.GOOS != "darwin" { + // Show dialog for non-macOS platforms + app.Dialog.Info(). + SetTitle("macOS Only Demo"). + SetMessage("The Liquid Glass effect is a macOS-specific feature that uses native NSGlassEffectView (macOS 15.0+) or NSVisualEffectView.\n\nThis demo is not available on " + runtime.GOOS + "."). + Show() + fmt.Println("The Liquid Glass effect is a macOS-specific feature. This demo is not available on", runtime.GOOS) + return + } + + // Convert logo to base64 data URI + logoDataURI := "data:image/png;base64," + base64.StdEncoding.EncodeToString(wailsLogo) + + // Create different HTML for each window + lightHTML := strings.Replace(indexHTML, "wails-logo.png", logoDataURI, 1) + lightHTML = strings.Replace(lightHTML, "LIQUID GLASS", "Light Style", 1) + + darkHTML := strings.Replace(indexHTML, "wails-logo.png", logoDataURI, 1) + darkHTML = strings.Replace(darkHTML, "LIQUID GLASS", "Dark Style", 1) + + vibrantHTML := strings.Replace(indexHTML, "wails-logo.png", logoDataURI, 1) + vibrantHTML = strings.Replace(vibrantHTML, "LIQUID GLASS", "Vibrant Style", 1) + + tintedHTML := strings.Replace(indexHTML, "wails-logo.png", logoDataURI, 1) + tintedHTML = strings.Replace(tintedHTML, "LIQUID GLASS", "Blue Tint", 1) + + sheetHTML := strings.Replace(indexHTML, "wails-logo.png", logoDataURI, 1) + sheetHTML = strings.Replace(sheetHTML, "LIQUID GLASS", "Sheet Material", 1) + + hudHTML := strings.Replace(indexHTML, "wails-logo.png", logoDataURI, 1) + hudHTML = strings.Replace(hudHTML, "LIQUID GLASS", "HUD Window", 1) + + contentHTML := strings.Replace(indexHTML, "wails-logo.png", logoDataURI, 1) + contentHTML = strings.Replace(contentHTML, "LIQUID GLASS", "Content Background", 1) + + // Window 1: Light style with no tint + window1 := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Light Glass", + Width: 350, + Height: 280, + X: 100, + Y: 100, + Frameless: true, + EnableFileDrop: false, + HTML: lightHTML, + InitialPosition: application.WindowXY, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropLiquidGlass, + InvisibleTitleBarHeight: 500, + LiquidGlass: application.MacLiquidGlass{ + Style: application.LiquidGlassStyleLight, + Material: application.NSVisualEffectMaterialAuto, + CornerRadius: 20.0, + TintColor: nil, + }, + }, + }) + + // Window 2: Dark style + window2 := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Dark Glass", + Width: 350, + Height: 280, + X: 500, + Y: 100, + Frameless: true, + EnableFileDrop: false, + HTML: darkHTML, + InitialPosition: application.WindowXY, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropLiquidGlass, + InvisibleTitleBarHeight: 500, + LiquidGlass: application.MacLiquidGlass{ + Style: application.LiquidGlassStyleDark, + Material: application.NSVisualEffectMaterialAuto, + CornerRadius: 20.0, + TintColor: nil, + }, + }, + }) + + // Window 3: Vibrant style + window3 := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Vibrant Glass", + Width: 350, + Height: 280, + X: 900, + Y: 100, + Frameless: true, + EnableFileDrop: false, + HTML: vibrantHTML, + InitialPosition: application.WindowXY, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropLiquidGlass, + InvisibleTitleBarHeight: 500, + LiquidGlass: application.MacLiquidGlass{ + Style: application.LiquidGlassStyleVibrant, + Material: application.NSVisualEffectMaterialAuto, + CornerRadius: 20.0, + TintColor: nil, + }, + }, + }) + + // Window 4: Blue tinted glass + window4 := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Tinted Glass", + Width: 350, + Height: 280, + X: 300, + Y: 420, + Frameless: true, + EnableFileDrop: false, + HTML: tintedHTML, + InitialPosition: application.WindowXY, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropLiquidGlass, + InvisibleTitleBarHeight: 500, + LiquidGlass: application.MacLiquidGlass{ + Style: application.LiquidGlassStyleLight, + Material: application.NSVisualEffectMaterialAuto, + CornerRadius: 25.0, // Different corner radius + TintColor: &application.RGBA{Red: 0, Green: 100, Blue: 200, Alpha: 50}, // Blue tint + }, + }, + }) + + // Window 5: Using specific NSVisualEffectMaterial + window5 := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Sheet Material", + Width: 350, + Height: 280, + X: 700, + Y: 420, + Frameless: true, + EnableFileDrop: false, + HTML: sheetHTML, + InitialPosition: application.WindowXY, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropLiquidGlass, + InvisibleTitleBarHeight: 500, + LiquidGlass: application.MacLiquidGlass{ + Style: application.LiquidGlassStyleAutomatic, // Automatic style + Material: application.NSVisualEffectMaterialSheet, // Specific material + CornerRadius: 15.0, // Different corner radius + TintColor: nil, + }, + }, + }) + + // Window 6: HUD Window Material (very light, translucent) + window6 := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "HUD Window", + Width: 350, + Height: 280, + X: 100, + Y: 740, + Frameless: true, + EnableFileDrop: false, + HTML: hudHTML, + InitialPosition: application.WindowXY, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropLiquidGlass, + InvisibleTitleBarHeight: 500, + LiquidGlass: application.MacLiquidGlass{ + Style: application.LiquidGlassStyleAutomatic, + Material: application.NSVisualEffectMaterialHUDWindow, // HUD Window material - very light + CornerRadius: 30.0, // Larger corner radius + TintColor: nil, + }, + }, + }) + + // Window 7: Content Background Material + window7 := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Content Background", + Width: 350, + Height: 280, + X: 500, + Y: 740, + Frameless: true, + EnableFileDrop: false, + HTML: contentHTML, + InitialPosition: application.WindowXY, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropLiquidGlass, + InvisibleTitleBarHeight: 500, + LiquidGlass: application.MacLiquidGlass{ + Style: application.LiquidGlassStyleAutomatic, + Material: application.NSVisualEffectMaterialContentBackground, // Content background + CornerRadius: 10.0, // Smaller corner radius + TintColor: &application.RGBA{Red: 0, Green: 200, Blue: 100, Alpha: 30}, // Warm tint + }, + }, + }) + + // Show all windows + window1.Show() + window2.Show() + window3.Show() + window4.Show() + window5.Show() + window6.Show() + window7.Show() + + // Run the application + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/liquid-glass/wails-logo.png b/v3/examples/liquid-glass/wails-logo.png new file mode 100644 index 000000000..e65c582ff Binary files /dev/null and b/v3/examples/liquid-glass/wails-logo.png differ diff --git a/v3/examples/menu/README.md b/v3/examples/menu/README.md new file mode 100644 index 000000000..cc926df73 --- /dev/null +++ b/v3/examples/menu/README.md @@ -0,0 +1,27 @@ +# Menu Example + +This example is a demonstration of different ways to create applications without using npm. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | + +# Known Issues + +- [Resize cursor still visible when not resizable](https://github.com/orgs/wailsapp/projects/6/views/1?pane=issue&itemId=40962163) + +--- + +Icon attribution: [Click icons created by kusumapotter - Flaticon](https://www.flaticon.com/free-icons/click) \ No newline at end of file diff --git a/v3/examples/menu/icon.png b/v3/examples/menu/icon.png new file mode 100644 index 000000000..e934687ca Binary files /dev/null and b/v3/examples/menu/icon.png differ diff --git a/v3/examples/menu/main.go b/v3/examples/menu/main.go new file mode 100644 index 000000000..b034cbb68 --- /dev/null +++ b/v3/examples/menu/main.go @@ -0,0 +1,161 @@ +package main + +import ( + _ "embed" + "log" + "runtime" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed icon.png +var clickBitmap []byte + +func main() { + + app := application.New(application.Options{ + Name: "Menu Demo", + Description: "A demo of the menu system", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a custom menu + menu := app.NewMenu() + if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) + } + menu.AddRole(application.FileMenu) + menu.AddRole(application.EditMenu) + menu.AddRole(application.WindowMenu) + menu.AddRole(application.HelpMenu) + + // Let's make a "Demo" menu + myMenu := menu.AddSubmenu("Demo") + + // Hidden menu item that can be unhidden + hidden := myMenu.Add("I was hidden").SetHidden(true) + myMenu.Add("Toggle the hidden menu").OnClick(func(ctx *application.Context) { + hidden.SetHidden(!hidden.Hidden()) + }) + + // Disabled menu item + myMenu.Add("Not Enabled").SetEnabled(false) + + // Click callbacks + myMenu.Add("Click Me!").SetAccelerator("CmdOrCtrl+l").OnClick(func(ctx *application.Context) { + switch ctx.ClickedMenuItem().Label() { + case "Click Me!": + ctx.ClickedMenuItem().SetLabel("Thanks mate!") + case "Thanks mate!": + ctx.ClickedMenuItem().SetLabel("Click Me!") + } + }) + + // You can control the current window from the menu + myMenu.Add("Lock WebviewWindow Resize").OnClick(func(ctx *application.Context) { + if app.Window.Current().Resizable() { + app.Window.Current().SetResizable(false) + ctx.ClickedMenuItem().SetLabel("Unlock WebviewWindow Resize") + } else { + app.Window.Current().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") + } + }) + myMenu.Add("Hide the beatles").OnClick(func(ctx *application.Context) { + if beatles.Hidden() { + ctx.ClickedMenuItem().SetLabel("Hide the beatles!") + beatles.SetHidden(false) + } else { + beatles.SetHidden(true) + ctx.ClickedMenuItem().SetLabel("Unhide the beatles!") + } + }) + + myMenu.AddSeparator() + + coffee := myMenu.Add("Request Coffee").OnClick(func(*application.Context) { + println("Coffee dispatched. Productivity +10!") + }) + + myMenu.Add("Toggle coffee availability").OnClick(func(*application.Context) { + if coffee.Enabled() { + coffee.SetEnabled(false) + coffee.SetLabel("Coffee Machine Broken") + println("Alert: Developer morale critically low.") + } else { + coffee.SetEnabled(true) + coffee.SetLabel("Request Coffee") + println("All systems nominal. Coffee restored.") + } + }) + + myMenu.Add("Hide the coffee option").OnClick(func(ctx *application.Context) { + if coffee.Hidden() { + ctx.ClickedMenuItem().SetLabel("Hide the coffee option") + coffee.SetHidden(false) + println("Coffee menu item has been resurrected!") + } else { + coffee.SetHidden(true) + ctx.ClickedMenuItem().SetLabel("Unhide the coffee option") + println("The coffee option has vanished into the void.") + } + }) + + app.Menu.Set(menu) + + // UseApplicationMenu allows Windows/Linux to inherit the app menu + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "menu-example", + Title: "Menu Example", + UseApplicationMenu: true, + }).SetBackgroundColour(application.NewRGB(33, 37, 41)) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/menu/menu_demo b/v3/examples/menu/menu_demo new file mode 100755 index 000000000..78926ad95 Binary files /dev/null and b/v3/examples/menu/menu_demo differ diff --git a/v3/examples/notifications/README.md b/v3/examples/notifications/README.md new file mode 100644 index 000000000..ad12c3f40 --- /dev/null +++ b/v3/examples/notifications/README.md @@ -0,0 +1,59 @@ +# Welcome to Your New Wails3 Project! + +Congratulations on generating your Wails3 application! This README will guide you through the next steps to get your project up and running. + +## Getting Started + +1. Navigate to your project directory in the terminal. + +2. To run your application in development mode, use the following command: + + ``` + wails3 dev + ``` + + This will start your application and enable hot-reloading for both frontend and backend changes. + +3. To build your application for production, use: + + ``` + wails3 build + ``` + + This will create a production-ready executable in the `build` directory. + +## Exploring Wails3 Features + +Now that you have your project set up, it's time to explore the features that Wails3 offers: + +1. **Check out the examples**: The best way to learn is by example. Visit the `examples` directory in the `v3/examples` directory to see various sample applications. + +2. **Run an example**: To run any of the examples, navigate to the example's directory and use: + + ``` + go run . + ``` + + Note: Some examples may be under development during the alpha phase. + +3. **Explore the documentation**: Visit the [Wails3 documentation](https://v3.wails.io/) for in-depth guides and API references. + +4. **Join the community**: Have questions or want to share your progress? Join the [Wails Discord](https://discord.gg/JDdSxwjhGf) or visit the [Wails discussions on GitHub](https://github.com/wailsapp/wails/discussions). + +## Project Structure + +Take a moment to familiarize yourself with your project structure: + +- `frontend/`: Contains your frontend code (HTML, CSS, JavaScript/TypeScript) +- `main.go`: The entry point of your Go backend +- `app.go`: Define your application structure and methods here +- `wails.json`: Configuration file for your Wails project + +## Next Steps + +1. Modify the frontend in the `frontend/` directory to create your desired UI. +2. Add backend functionality in `main.go`. +3. Use `wails3 dev` to see your changes in real-time. +4. When ready, build your application with `wails3 build`. + +Happy coding with Wails3! If you encounter any issues or have questions, don't hesitate to consult the documentation or reach out to the Wails community. diff --git a/v3/examples/notifications/Taskfile.yml b/v3/examples/notifications/Taskfile.yml new file mode 100644 index 000000000..1455cd70c --- /dev/null +++ b/v3/examples/notifications/Taskfile.yml @@ -0,0 +1,34 @@ +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + +vars: + APP_NAME: "Notifications\\ Demo" + BIN_DIR: "bin" + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{OS}}:package" + + run: + summary: Runs the application + cmds: + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} + diff --git a/v3/examples/notifications/build/Taskfile.yml b/v3/examples/notifications/build/Taskfile.yml new file mode 100644 index 000000000..5f3517efc --- /dev/null +++ b/v3/examples/notifications/build/Taskfile.yml @@ -0,0 +1,86 @@ +version: '3' + +tasks: + go:mod:tidy: + summary: Runs `go mod tidy` + internal: true + cmds: + - go mod tidy + + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + label: build:frontend (PRODUCTION={{.PRODUCTION}}) + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/**/* + deps: + - task: install:frontend:deps + - task: generate:bindings + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + cmds: + - npm run {{.BUILD_COMMAND}} -q + env: + PRODUCTION: '{{.PRODUCTION | default "false"}}' + vars: + BUILD_COMMAND: '{{if eq .PRODUCTION "true"}}build{{else}}build:dev{{end}}' + + + generate:bindings: + label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}}) + summary: Generates bindings for the frontend + deps: + - task: go:mod:tidy + sources: + - "**/*.[jt]s" + - exclude: frontend/**/* + - frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output + - "**/*.go" + - go.mod + - go.sum + generates: + - frontend/bindings/**/* + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true -ts + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + sources: + - "appicon.png" + generates: + - "darwin/icons.icns" + - "windows/icon.ico" + cmds: + - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico + + dev:frontend: + summary: Runs the frontend in development mode + dir: frontend + deps: + - task: install:frontend:deps + cmds: + - npm run dev -- --port {{.VITE_PORT}} --strictPort + + update:build-assets: + summary: Updates the build assets + dir: build + cmds: + - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir . diff --git a/v3/examples/notifications/build/appicon.png b/v3/examples/notifications/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/notifications/build/appicon.png differ diff --git a/v3/examples/notifications/build/config.yml b/v3/examples/notifications/build/config.yml new file mode 100644 index 000000000..bc09a6d28 --- /dev/null +++ b/v3/examples/notifications/build/config.yml @@ -0,0 +1,62 @@ +# This file contains the configuration for this project. +# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets. +# Note that this will overwrite any changes you have made to the assets. +version: '3' + +# This information is used to generate the build assets. +info: + companyName: "My Company" # The name of the company + productName: "My Product" # The name of the application + productIdentifier: "com.mycompany.myproduct" # The unique product identifier + description: "A program that does X" # The application description + copyright: "(c) 2025, My Company" # Copyright text + comments: "Some Product Comments" # Comments + version: "v0.0.1" # The application version + +# Dev mode configuration +dev_mode: + root_path: . + log_level: warn + debounce: 1000 + ignore: + dir: + - .git + - node_modules + - frontend + - bin + file: + - .DS_Store + - .gitignore + - .gitkeep + watched_extension: + - "*.go" + git_ignore: true + executes: + - cmd: wails3 task common:install:frontend:deps + type: once + - cmd: wails3 task common:dev:frontend + type: background + - cmd: go mod tidy + type: blocking + - cmd: wails3 task build + type: blocking + - cmd: wails3 task run + type: primary + +# File Associations +# More information at: https://v3.wails.io/noit/done/yet +fileAssociations: +# - ext: wails +# name: Wails +# description: Wails Application File +# iconName: wailsFileIcon +# role: Editor +# - ext: jpg +# name: JPEG +# description: Image File +# iconName: jpegFileIcon +# role: Editor + +# Other data +other: + - name: My Other Data \ No newline at end of file diff --git a/v3/examples/notifications/build/darwin/Info.dev.plist b/v3/examples/notifications/build/darwin/Info.dev.plist new file mode 100644 index 000000000..3a5b9735f --- /dev/null +++ b/v3/examples/notifications/build/darwin/Info.dev.plist @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + Notifications Demo + CFBundleIdentifier + com.wails.notifications-demo + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © now, My Company + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/examples/notifications/build/darwin/Info.plist b/v3/examples/notifications/build/darwin/Info.plist new file mode 100644 index 000000000..464270019 --- /dev/null +++ b/v3/examples/notifications/build/darwin/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + Notifications Demo + CFBundleIdentifier + com.wails.notifications-demo + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © now, My Company + + \ No newline at end of file diff --git a/v3/examples/notifications/build/darwin/Taskfile.yml b/v3/examples/notifications/build/darwin/Taskfile.yml new file mode 100644 index 000000000..3b6a9dc99 --- /dev/null +++ b/v3/examples/notifications/build/darwin/Taskfile.yml @@ -0,0 +1,80 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Creates a production build of the application + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + env: + GOOS: darwin + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + CGO_CFLAGS: "-mmacosx-version-min=10.15" + CGO_LDFLAGS: "-mmacosx-version-min=10.15" + MACOSX_DEPLOYMENT_TARGET: "10.15" + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + build:universal: + summary: Builds darwin universal binary (arm64 + amd64) + deps: + - task: build + vars: + ARCH: amd64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" + - task: build + vars: + ARCH: arm64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + cmds: + - lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + - rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + + package: + summary: Packages a production build of the application into a `.app` bundle + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:app:bundle + + package:universal: + summary: Packages darwin universal binary (arm64 + amd64) + deps: + - task: build:universal + cmds: + - task: create:app:bundle + + + create:app:bundle: + summary: Creates an `.app` bundle + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS + - cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents + + run: + cmds: + - mkdir -p {{.BIN_DIR}}/dev/{{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/dev/{{.APP_NAME}}.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/dev/{{.APP_NAME}}.app/Contents/MacOS + - cp build/darwin/Info.dev.plist {{.BIN_DIR}}/dev/{{.APP_NAME}}.app/Contents/Info.plist + - codesign --force --deep --sign - {{.BIN_DIR}}/dev/{{.APP_NAME}}.app + - '{{.BIN_DIR}}/dev/{{.APP_NAME}}.app/Contents/MacOS/{{.APP_NAME}}' diff --git a/v3/examples/notifications/build/darwin/icons.icns b/v3/examples/notifications/build/darwin/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/notifications/build/darwin/icons.icns differ diff --git a/v3/examples/notifications/build/linux/Taskfile.yml b/v3/examples/notifications/build/linux/Taskfile.yml new file mode 100644 index 000000000..560cc9c92 --- /dev/null +++ b/v3/examples/notifications/build/linux/Taskfile.yml @@ -0,0 +1,119 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Linux + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: linux + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application for Linux + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:appimage + - task: create:deb + - task: create:rpm + - task: create:aur + + create:appimage: + summary: Creates an AppImage + dir: build/linux/appimage + deps: + - task: build + vars: + PRODUCTION: "true" + - task: generate:dotdesktop + cmds: + - cp {{.APP_BINARY}} {{.APP_NAME}} + - cp ../../appicon.png appicon.png + - wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build + vars: + APP_NAME: '{{.APP_NAME}}' + APP_BINARY: '../../../bin/{{.APP_NAME}}' + ICON: '../../appicon.png' + DESKTOP_FILE: '../{{.APP_NAME}}.desktop' + OUTPUT_DIR: '../../../bin' + + create:deb: + summary: Creates a deb package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:deb + + create:rpm: + summary: Creates a rpm package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:rpm + + create:aur: + summary: Creates a arch linux packager package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:aur + + generate:deb: + summary: Creates a deb package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:rpm: + summary: Creates a rpm package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:aur: + summary: Creates a arch linux packager package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:dotdesktop: + summary: Generates a `.desktop` file + dir: build + cmds: + - mkdir -p {{.ROOT_DIR}}/build/linux/appimage + - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}" + vars: + APP_NAME: '{{.APP_NAME}}' + EXEC: '{{.APP_NAME}}' + ICON: 'appicon' + CATEGORIES: 'Development;' + OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}' diff --git a/v3/examples/notifications/build/linux/appimage/build.sh b/v3/examples/notifications/build/linux/appimage/build.sh new file mode 100644 index 000000000..85901c34e --- /dev/null +++ b/v3/examples/notifications/build/linux/appimage/build.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Copyright (c) 2018-Present Lea Anthony +# SPDX-License-Identifier: MIT + +# Fail script on any error +set -euxo pipefail + +# Define variables +APP_DIR="${APP_NAME}.AppDir" + +# Create AppDir structure +mkdir -p "${APP_DIR}/usr/bin" +cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/" +cp "${ICON_PATH}" "${APP_DIR}/" +cp "${DESKTOP_FILE}" "${APP_DIR}/" + +if [[ $(uname -m) == *x86_64* ]]; then + # Download linuxdeploy and make it executable + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + + # Run linuxdeploy to bundle the application + ./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage +else + # Download linuxdeploy and make it executable (arm64) + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage + chmod +x linuxdeploy-aarch64.AppImage + + # Run linuxdeploy to bundle the application (arm64) + ./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage +fi + +# Rename the generated AppImage +mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage" + diff --git a/v3/examples/notifications/build/linux/nfpm/nfpm.yaml b/v3/examples/notifications/build/linux/nfpm/nfpm.yaml new file mode 100644 index 000000000..c2cb7cd81 --- /dev/null +++ b/v3/examples/notifications/build/linux/nfpm/nfpm.yaml @@ -0,0 +1,50 @@ +# Feel free to remove those if you don't want/need to use them. +# Make sure to check the documentation at https://nfpm.goreleaser.com +# +# The lines below are called `modelines`. See `:help modeline` + +name: "notifications" +arch: ${GOARCH} +platform: "linux" +version: "0.1.0" +section: "default" +priority: "extra" +maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> +description: "My Product Description" +vendor: "My Company" +homepage: "https://wails.io" +license: "MIT" +release: "1" + +contents: + - src: "./bin/notifications" + dst: "/usr/local/bin/notifications" + - src: "./build/appicon.png" + dst: "/usr/share/icons/hicolor/128x128/apps/notifications.png" + - src: "./build/linux/notifications.desktop" + dst: "/usr/share/applications/notifications.desktop" + +depends: + - gtk3 + - libwebkit2gtk + +# replaces: +# - foobar +# provides: +# - bar +# depends: +# - gtk3 +# - libwebkit2gtk +# recommends: +# - whatever +# suggests: +# - something-else +# conflicts: +# - not-foo +# - not-bar +# changelog: "changelog.yaml" +# scripts: +# preinstall: ./build/linux/nfpm/scripts/preinstall.sh +# postinstall: ./build/linux/nfpm/scripts/postinstall.sh +# preremove: ./build/linux/nfpm/scripts/preremove.sh +# postremove: ./build/linux/nfpm/scripts/postremove.sh diff --git a/v3/examples/notifications/build/linux/nfpm/scripts/postinstall.sh b/v3/examples/notifications/build/linux/nfpm/scripts/postinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/notifications/build/linux/nfpm/scripts/postinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/notifications/build/linux/nfpm/scripts/postremove.sh b/v3/examples/notifications/build/linux/nfpm/scripts/postremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/notifications/build/linux/nfpm/scripts/postremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/notifications/build/linux/nfpm/scripts/preinstall.sh b/v3/examples/notifications/build/linux/nfpm/scripts/preinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/notifications/build/linux/nfpm/scripts/preinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/notifications/build/linux/nfpm/scripts/preremove.sh b/v3/examples/notifications/build/linux/nfpm/scripts/preremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/notifications/build/linux/nfpm/scripts/preremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/notifications/build/windows/Taskfile.yml b/v3/examples/notifications/build/windows/Taskfile.yml new file mode 100644 index 000000000..be6e4125e --- /dev/null +++ b/v3/examples/notifications/build/windows/Taskfile.yml @@ -0,0 +1,63 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Windows + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - task: generate:syso + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe + - cmd: powershell Remove-item *.syso + platforms: [windows] + - cmd: rm -f *.syso + platforms: [linux, darwin] + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: windows + CGO_ENABLED: 0 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application into a `.exe` bundle + cmds: + - task: create:nsis:installer + + generate:syso: + summary: Generates Windows `.syso` file + dir: build + cmds: + - wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso + vars: + ARCH: '{{.ARCH | default ARCH}}' + + create:nsis:installer: + summary: Creates an NSIS installer + dir: build/windows/nsis + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + # Create the Microsoft WebView2 bootstrapper if it doesn't exist + - wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis" + - makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi + vars: + ARCH: '{{.ARCH | default ARCH}}' + ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}' + + run: + cmds: + - '{{.BIN_DIR}}\\{{.APP_NAME}}.exe' diff --git a/v3/examples/notifications/build/windows/icon.ico b/v3/examples/notifications/build/windows/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/notifications/build/windows/icon.ico differ diff --git a/v3/examples/notifications/build/windows/info.json b/v3/examples/notifications/build/windows/info.json new file mode 100644 index 000000000..850b2b5b0 --- /dev/null +++ b/v3/examples/notifications/build/windows/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "0.1.0" + }, + "info": { + "0000": { + "ProductVersion": "0.1.0", + "CompanyName": "My Company", + "FileDescription": "My Product Description", + "LegalCopyright": "© now, My Company", + "ProductName": "My Product", + "Comments": "This is a comment" + } + } +} \ No newline at end of file diff --git a/v3/examples/notifications/build/windows/nsis/project.nsi b/v3/examples/notifications/build/windows/nsis/project.nsi new file mode 100644 index 000000000..4cb18e04f --- /dev/null +++ b/v3/examples/notifications/build/windows/nsis/project.nsi @@ -0,0 +1,112 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the wails_tools.nsh file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "my-project" # Default "notifications" +## !define INFO_COMPANYNAME "My Company" # Default "My Company" +## !define INFO_PRODUCTNAME "My Product Name" # Default "My Product" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.1.0" +## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "© now, My Company" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/v3/examples/notifications/build/windows/nsis/wails_tools.nsh b/v3/examples/notifications/build/windows/nsis/wails_tools.nsh new file mode 100644 index 000000000..c47c784a4 --- /dev/null +++ b/v3/examples/notifications/build/windows/nsis/wails_tools.nsh @@ -0,0 +1,212 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "notifications" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "My Company" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "My Product" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "0.1.0" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "© now, My Company" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + +!macroend \ No newline at end of file diff --git a/v3/examples/notifications/build/windows/wails.exe.manifest b/v3/examples/notifications/build/windows/wails.exe.manifest new file mode 100644 index 000000000..0299e62ca --- /dev/null +++ b/v3/examples/notifications/build/windows/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/v3/examples/notifications/frontend/Inter Font License.txt b/v3/examples/notifications/frontend/Inter Font License.txt new file mode 100644 index 000000000..b525cbf3a --- /dev/null +++ b/v3/examples/notifications/frontend/Inter Font License.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/index.ts b/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/index.ts new file mode 100644 index 000000000..71eda3bb9 --- /dev/null +++ b/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as NotificationService from "./notificationservice.js"; +export { + NotificationService +}; + +export { + NotificationAction, + NotificationCategory, + NotificationOptions +} from "./models.js"; diff --git a/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/models.ts b/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/models.ts new file mode 100644 index 000000000..d7f48edfe --- /dev/null +++ b/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/models.ts @@ -0,0 +1,107 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +/** + * NotificationAction represents an action button for a notification. + */ +export class NotificationAction { + "id"?: string; + "title"?: string; + + /** + * (macOS-specific) + */ + "destructive"?: boolean; + + /** Creates a new NotificationAction instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new NotificationAction instance from a string or object. + */ + static createFrom($$source: any = {}): NotificationAction { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new NotificationAction($$parsedSource as Partial); + } +} + +/** + * NotificationCategory groups actions for notifications. + */ +export class NotificationCategory { + "id"?: string; + "actions"?: NotificationAction[]; + "hasReplyField"?: boolean; + "replyPlaceholder"?: string; + "replyButtonTitle"?: string; + + /** Creates a new NotificationCategory instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new NotificationCategory instance from a string or object. + */ + static createFrom($$source: any = {}): NotificationCategory { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("actions" in $$parsedSource) { + $$parsedSource["actions"] = $$createField1_0($$parsedSource["actions"]); + } + return new NotificationCategory($$parsedSource as Partial); + } +} + +/** + * NotificationOptions contains configuration for a notification + */ +export class NotificationOptions { + "id": string; + "title": string; + + /** + * (macOS and Linux only) + */ + "subtitle"?: string; + "body"?: string; + "categoryId"?: string; + "data"?: { [_: string]: any }; + + /** Creates a new NotificationOptions instance. */ + constructor($$source: Partial = {}) { + if (!("id" in $$source)) { + this["id"] = ""; + } + if (!("title" in $$source)) { + this["title"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new NotificationOptions instance from a string or object. + */ + static createFrom($$source: any = {}): NotificationOptions { + const $$createField5_0 = $$createType2; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("data" in $$parsedSource) { + $$parsedSource["data"] = $$createField5_0($$parsedSource["data"]); + } + return new NotificationOptions($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = NotificationAction.createFrom; +const $$createType1 = $Create.Array($$createType0); +const $$createType2 = $Create.Map($Create.Any, $Create.Any); diff --git a/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/notificationservice.ts b/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/notificationservice.ts new file mode 100644 index 000000000..859f3570f --- /dev/null +++ b/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/notificationservice.ts @@ -0,0 +1,62 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Service represents the notifications service + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function CheckNotificationAuthorization(): $CancellablePromise { + return $Call.ByID(2216952893); +} + +export function RegisterNotificationCategory(category: $models.NotificationCategory): $CancellablePromise { + return $Call.ByID(2917562919, category); +} + +export function RemoveAllDeliveredNotifications(): $CancellablePromise { + return $Call.ByID(3956282340); +} + +export function RemoveAllPendingNotifications(): $CancellablePromise { + return $Call.ByID(108821341); +} + +export function RemoveDeliveredNotification(identifier: string): $CancellablePromise { + return $Call.ByID(975691940, identifier); +} + +export function RemoveNotification(identifier: string): $CancellablePromise { + return $Call.ByID(3966653866, identifier); +} + +export function RemoveNotificationCategory(categoryID: string): $CancellablePromise { + return $Call.ByID(2032615554, categoryID); +} + +export function RemovePendingNotification(identifier: string): $CancellablePromise { + return $Call.ByID(3729049703, identifier); +} + +/** + * Public methods that delegate to the implementation. + */ +export function RequestNotificationAuthorization(): $CancellablePromise { + return $Call.ByID(3933442950); +} + +export function SendNotification(options: $models.NotificationOptions): $CancellablePromise { + return $Call.ByID(3968228732, options); +} + +export function SendNotificationWithActions(options: $models.NotificationOptions): $CancellablePromise { + return $Call.ByID(1886542847, options); +} diff --git a/v3/examples/notifications/frontend/dist/Inter-Medium.ttf b/v3/examples/notifications/frontend/dist/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/notifications/frontend/dist/Inter-Medium.ttf differ diff --git a/v3/examples/notifications/frontend/dist/assets/index-Dat4utuQ.js b/v3/examples/notifications/frontend/dist/assets/index-Dat4utuQ.js new file mode 100644 index 000000000..b1c054dfb --- /dev/null +++ b/v3/examples/notifications/frontend/dist/assets/index-Dat4utuQ.js @@ -0,0 +1,33 @@ +(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const i of o)if(i.type==="childList")for(const s of i.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&r(s)}).observe(document,{childList:!0,subtree:!0});function n(o){const i={};return o.integrity&&(i.integrity=o.integrity),o.referrerPolicy&&(i.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?i.credentials="include":o.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(o){if(o.ep)return;o.ep=!0;const i=n(o);fetch(o.href,i)}})();const fe="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";function oe(t=21){let e="",n=t|0;for(;n--;)e+=fe[Math.random()*64|0];return e}const pe=window.location.origin+"/wails/runtime",x=Object.freeze({Call:0,Clipboard:1,Application:2,Events:3,ContextMenu:4,Dialog:5,Window:6,Screens:7,System:8,Browser:9,CancelCall:10});let we=oe();function C(t,e=""){return function(n,r=null){return he(t,n,e,r)}}async function he(t,e,n,r){var o,i;let s=new URL(pe);s.searchParams.append("object",t.toString()),s.searchParams.append("method",e.toString()),r&&s.searchParams.append("args",JSON.stringify(r));let a={"x-wails-client-id":we};n&&(a["x-wails-window-name"]=n);let c=await fetch(s,{headers:a});if(!c.ok)throw new Error(await c.text());return((i=(o=c.headers.get("Content-Type"))===null||o===void 0?void 0:o.indexOf("application/json"))!==null&&i!==void 0?i:-1)!==-1?c.json():c.text()}C(x.System);const H=function(){var t,e,n,r,o;try{if(!((e=(t=window.chrome)===null||t===void 0?void 0:t.webview)===null||e===void 0)&&e.postMessage)return window.chrome.webview.postMessage.bind(window.chrome.webview);if(!((o=(r=(n=window.webkit)===null||n===void 0?void 0:n.messageHandlers)===null||r===void 0?void 0:r.external)===null||o===void 0)&&o.postMessage)return window.webkit.messageHandlers.external.postMessage.bind(window.webkit.messageHandlers.external)}catch{}return console.warn(` +%c⚠️ Browser Environment Detected %c + +%cOnly UI previews are available in the browser. For full functionality, please run the application in desktop mode. +More information at: https://v3.wails.io/learn/build/#using-a-browser-for-development +`,"background: #ffffff; color: #000000; font-weight: bold; padding: 4px 8px; border-radius: 4px; border: 2px solid #000000;","background: transparent;","color: #ffffff; font-style: italic; font-weight: bold;"),null}();function T(t){H==null||H(t)}function ie(){return window._wails.environment.OS==="windows"}function me(){return!!window._wails.environment.Debug}function ye(){return new MouseEvent("mousedown").buttons===0}function se(t){var e;return t.target instanceof HTMLElement?t.target:!(t.target instanceof HTMLElement)&&t.target instanceof Node&&(e=t.target.parentElement)!==null&&e!==void 0?e:document.body}document.addEventListener("DOMContentLoaded",()=>{});window.addEventListener("contextmenu",je);const ge=C(x.ContextMenu),be=0;function ve(t,e,n,r){ge(be,{id:t,x:e,y:n,data:r})}function je(t){const e=se(t),n=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu").trim();if(n){t.preventDefault();const r=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu-data");ve(n,t.clientX,t.clientY,r)}else Ee(t,e)}function Ee(t,e){if(me())return;switch(window.getComputedStyle(e).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":t.preventDefault();return}if(e.isContentEditable)return;const n=window.getSelection(),r=n&&n.toString().length>0;if(r)for(let o=0;o{F=t,F||(j=E=!1,u())};window.addEventListener("mousedown",Y,{capture:!0});window.addEventListener("mousemove",Y,{capture:!0});window.addEventListener("mouseup",Y,{capture:!0});for(const t of["click","contextmenu","dblclick"])window.addEventListener(t,ze,{capture:!0});function ze(t){(S||E)&&(t.stopImmediatePropagation(),t.stopPropagation(),t.preventDefault())}const I=0,Se=1,U=2;function Y(t){let e,n=t.buttons;switch(t.type){case"mousedown":e=I,P||(n=y|1<"u"||typeof e=="object"))try{var n=O.call(e);return(n===Ne||n===De||n===He||n===Re)&&e("")==null}catch{}return!1})}function Ue(t){if(V(t))return!0;if(!t||typeof t!="function"&&typeof t!="object")return!1;try{b(t,null,_)}catch(e){if(e!==R)return!1}return!$(t)&&B(t)}function qe(t){if(V(t))return!0;if(!t||typeof t!="function"&&typeof t!="object")return!1;if(Ae)return B(t);if($(t))return!1;var e=O.call(t);return e!==Oe&&e!==Te&&!/^\[object HTML/.test(e)?!1:B(t)}const m=b?Ue:qe;var q;class W extends Error{constructor(e,n){super(e,n),this.name="CancelError"}}class M extends Error{constructor(e,n,r){super((r??"Unhandled rejection in cancelled promise.")+" Reason: "+Fe(n),{cause:n}),this.promise=e,this.name="CancelledRejectionError"}}const p=Symbol("barrier"),G=Symbol("cancelImpl"),K=(q=Symbol.species)!==null&&q!==void 0?q:Symbol("speciesPolyfill");class l extends Promise{constructor(e,n){let r,o;if(super((c,f)=>{r=c,o=f}),this.constructor[K]!==Promise)throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property.");let i={promise:this,resolve:r,reject:o,get oncancelled(){return n??null},set oncancelled(c){n=c??void 0}};const s={get root(){return s},resolving:!1,settled:!1};Object.defineProperties(this,{[p]:{configurable:!1,enumerable:!1,writable:!0,value:null},[G]:{configurable:!1,enumerable:!1,writable:!1,value:ae(i,s)}});const a=ue(i,s);try{e(le(i,s),a)}catch(c){s.resolving?console.log("Unhandled exception in CancellablePromise executor.",c):a(c)}}cancel(e){return new l(n=>{Promise.all([this[G](new W("Promise cancelled.",{cause:e})),_e(this)]).then(()=>n(),()=>n())})}cancelOn(e){return e.aborted?this.cancel(e.reason):e.addEventListener("abort",()=>void this.cancel(e.reason),{capture:!0}),this}then(e,n,r){if(!(this instanceof l))throw new TypeError("CancellablePromise.prototype.then called on an invalid object.");if(m(e)||(e=Q),m(n)||(n=Z),e===Q&&n==Z)return new l(i=>i(this));const o={};return this[p]=o,new l((i,s)=>{super.then(a=>{var c;this[p]===o&&(this[p]=null),(c=o.resolve)===null||c===void 0||c.call(o);try{i(e(a))}catch(f){s(f)}},a=>{var c;this[p]===o&&(this[p]=null),(c=o.resolve)===null||c===void 0||c.call(o);try{i(n(a))}catch(f){s(f)}})},async i=>{try{return r==null?void 0:r(i)}finally{await this.cancel(i)}})}catch(e,n){return this.then(void 0,e,n)}finally(e,n){if(!(this instanceof l))throw new TypeError("CancellablePromise.prototype.finally called on an invalid object.");return m(e)?this.then(r=>l.resolve(e()).then(()=>r),r=>l.resolve(e()).then(()=>{throw r}),n):this.then(e,e,n)}static get[K](){return Promise}static all(e){let n=Array.from(e);const r=n.length===0?l.resolve(n):new l((o,i)=>{Promise.all(n).then(o,i)},o=>L(r,n,o));return r}static allSettled(e){let n=Array.from(e);const r=n.length===0?l.resolve(n):new l((o,i)=>{Promise.allSettled(n).then(o,i)},o=>L(r,n,o));return r}static any(e){let n=Array.from(e);const r=n.length===0?l.resolve(n):new l((o,i)=>{Promise.any(n).then(o,i)},o=>L(r,n,o));return r}static race(e){let n=Array.from(e);const r=new l((o,i)=>{Promise.race(n).then(o,i)},o=>L(r,n,o));return r}static cancel(e){const n=new l(()=>{});return n.cancel(e),n}static timeout(e,n){const r=new l(()=>{});return AbortSignal&&typeof AbortSignal=="function"&&AbortSignal.timeout&&typeof AbortSignal.timeout=="function"?AbortSignal.timeout(e).addEventListener("abort",()=>void r.cancel(n)):setTimeout(()=>void r.cancel(n),e),r}static sleep(e,n){return new l(r=>{setTimeout(()=>r(n),e)})}static reject(e){return new l((n,r)=>r(e))}static resolve(e){return e instanceof l?e:new l(n=>n(e))}static withResolvers(){let e={oncancelled:null};return e.promise=new l((n,r)=>{e.resolve=n,e.reject=r},n=>{var r;(r=e.oncancelled)===null||r===void 0||r.call(e,n)}),e}}function ae(t,e){let n;return r=>{if(e.settled||(e.settled=!0,e.reason=r,t.reject(r),Promise.prototype.then.call(t.promise,void 0,o=>{if(o!==r)throw o})),!(!e.reason||!t.oncancelled))return n=new Promise(o=>{try{o(t.oncancelled(e.reason.cause))}catch(i){Promise.reject(new M(t.promise,i,"Unhandled exception in oncancelled callback."))}}).catch(o=>{Promise.reject(new M(t.promise,o,"Unhandled rejection in oncancelled callback."))}),t.oncancelled=null,n}}function le(t,e){return n=>{if(!e.resolving){if(e.resolving=!0,n===t.promise){if(e.settled)return;e.settled=!0,t.reject(new TypeError("A promise cannot be resolved with itself."));return}if(n!=null&&(typeof n=="object"||typeof n=="function")){let r;try{r=n.then}catch(o){e.settled=!0,t.reject(o);return}if(m(r)){try{let s=n.cancel;if(m(s)){const a=c=>{Reflect.apply(s,n,[c])};e.reason?ae(Object.assign(Object.assign({},t),{oncancelled:a}),e)(e.reason):t.oncancelled=a}}catch{}const o={root:e.root,resolving:!1,get settled(){return this.root.settled},set settled(s){this.root.settled=s},get reason(){return this.root.reason}},i=ue(t,o);try{Reflect.apply(r,n,[le(t,o),i])}catch(s){i(s)}return}}e.settled||(e.settled=!0,t.resolve(n))}}}function ue(t,e){return n=>{if(!e.resolving)if(e.resolving=!0,e.settled){try{if(n instanceof W&&e.reason instanceof W&&Object.is(n.cause,e.reason.cause))return}catch{}Promise.reject(new M(t.promise,n))}else e.settled=!0,t.reject(n)}}function L(t,e,n){const r=[];for(const o of e){let i;try{if(!m(o.then)||(i=o.cancel,!m(i)))continue}catch{continue}let s;try{s=Reflect.apply(i,o,[n])}catch(a){Promise.reject(new M(t,a,"Unhandled exception in cancel method."));continue}s&&r.push((s instanceof Promise?s:Promise.resolve(s)).catch(a=>{Promise.reject(new M(t,a,"Unhandled rejection in cancel method."))}))}return Promise.all(r)}function Q(t){return t}function Z(t){throw t}function Fe(t){try{if(t instanceof Error||typeof t!="object"||t.toString!==Object.prototype.toString)return""+t}catch{}try{return JSON.stringify(t)}catch{}try{return Object.prototype.toString.call(t)}catch{}return""}function _e(t){var e;let n=(e=t[p])!==null&&e!==void 0?e:{};return"promise"in n||Object.assign(n,g()),t[p]==null&&(n.resolve(),t[p]=n),n.promise}let g=Promise.withResolvers;g&&typeof g=="function"?g=g.bind(Promise):g=function(){let t,e;return{promise:new Promise((r,o)=>{t=r,e=o}),resolve:t,reject:e}};window._wails=window._wails||{};window._wails.callResultHandler=Xe;window._wails.callErrorHandler=Je;const Be=C(x.Call),We=C(x.CancelCall),v=new Map,Ye=0,$e=0;class Ve extends Error{constructor(e,n){super(e,n),this.name="RuntimeError"}}function Xe(t,e,n){const r=de(t);if(r)if(!e)r.resolve(void 0);else if(!n)r.resolve(e);else try{r.resolve(JSON.parse(e))}catch(o){r.reject(new TypeError("could not parse result: "+o.message,{cause:o}))}}function Je(t,e,n){const r=de(t);if(r)if(!n)r.reject(new Error(e));else{let o;try{o=JSON.parse(e)}catch(a){r.reject(new TypeError("could not parse error: "+a.message,{cause:a}));return}let i={};o.cause&&(i.cause=o.cause);let s;switch(o.kind){case"ReferenceError":s=new ReferenceError(o.message,i);break;case"TypeError":s=new TypeError(o.message,i);break;case"RuntimeError":s=new Ve(o.message,i);break;default:s=new Error(o.message,i);break}r.reject(s)}}function de(t){const e=v.get(t);return v.delete(t),e}function Ge(){let t;do t=oe();while(v.has(t));return t}function Ke(t){const e=Ge(),n=l.withResolvers();v.set(e,{resolve:n.resolve,reject:n.reject});const r=Be(Ye,Object.assign({"call-id":e},t));let o=!1;r.then(()=>{o=!0},s=>{v.delete(e),n.reject(s)});const i=()=>(v.delete(e),We($e,{"call-id":e}).catch(s=>{console.error("Error while requesting binding call cancellation:",s)}));return n.oncancelled=()=>o?i():r.then(i),n.promise}function k(t,...e){return Ke({methodID:t,args:e})}const w=new Map;class Qe{constructor(e,n,r){this.eventName=e,this.callback=n,this.maxCallbacks=r||-1}dispatch(e){try{this.callback(e)}catch(n){console.error(n)}return this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0)}}function Ze(t){let e=w.get(t.eventName);e&&(e=e.filter(n=>n!==t),e.length===0?w.delete(t.eventName):w.set(t.eventName,e))}window._wails=window._wails||{};window._wails.dispatchWailsEvent=tt;C(x.Events);class et{constructor(e,n=null){this.name=e,this.data=n}}function tt(t){let e=w.get(t.name);if(!e)return;let n=new et(t.name,t.data);"sender"in t&&(n.sender=t.sender),e=e.filter(r=>!r.dispatch(n)),e.length===0?w.delete(t.name):w.set(t.name,e)}function nt(t,e,n){let r=w.get(t)||[];const o=new Qe(t,e,n);return r.push(o),w.set(t,r),()=>Ze(o)}function rt(t,e){return nt(t,e,-1)}window._wails=window._wails||{};window._wails.invoke=T;T("wails:runtime:ready");function X(){return k(2216952893)}function ot(t){return k(2917562919,t)}function it(){return k(3933442950)}function st(t){return k(3968228732,t)}function ct(t){return k(1886542847,t)}const d=document.querySelector("#response");var ee;(ee=document.querySelector("#request"))==null||ee.addEventListener("click",async()=>{try{await it()?(d&&(d.innerHTML="

        Notifications are now authorized.

        "),console.info("Notifications are now authorized.")):(d&&(d.innerHTML="

        Notifications are not authorized. You can attempt to request again or let the user know in the UI.

        "),console.warn(`Notifications are not authorized. + You can attempt to request again or let the user know in the UI. +`))}catch(t){console.error(t)}});var te;(te=document.querySelector("#check"))==null||te.addEventListener("click",async()=>{try{await X()?(d&&(d.innerHTML="

        Notifications are authorized.

        "),console.info("Notifications are authorized.")):(d&&(d.innerHTML="

        Notifications are not authorized. You can attempt to request again or let the user know in the UI.

        "),console.warn(`Notifications are not authorized. + You can attempt to request again or let the user know in the UI. +`))}catch(t){console.error(t)}});var ne;(ne=document.querySelector("#basic"))==null||ne.addEventListener("click",async()=>{try{await X()?await st({id:crypto.randomUUID(),title:"Notification Title",subtitle:"Subtitle on macOS and Linux",body:"Body text of notification.",data:{"user-id":"user-123","message-id":"msg-123",timestamp:Date.now()}}):(d&&(d.innerHTML="

        Notifications are not authorized. You can attempt to request again or let the user know in the UI.

        "),console.warn(`Notifications are not authorized. + You can attempt to request again or let the user know in the UI. +`))}catch(t){console.error(t)}});var re;(re=document.querySelector("#complex"))==null||re.addEventListener("click",async()=>{try{if(await X()){const e="frontend-notification-id";await ot({id:e,actions:[{id:"VIEW",title:"View"},{id:"MARK_READ",title:"Mark as read"},{id:"DELETE",title:"Delete",destructive:!0}],hasReplyField:!0,replyPlaceholder:"Message...",replyButtonTitle:"Reply"}),await ct({id:crypto.randomUUID(),title:"Notification Title",subtitle:"Subtitle on macOS and Linux",body:"Body text of notification.",categoryId:e,data:{"user-id":"user-123","message-id":"msg-123",timestamp:Date.now()}})}else d&&(d.innerHTML="

        Notifications are not authorized. You can attempt to request again or let the user know in the UI.

        "),console.warn(`Notifications are not authorized. + You can attempt to request again or let the user know in the UI. +`)}catch(t){console.error(t)}});const at=rt("notification:action",t=>{console.info(`Recieved a ${t.name} event`);const{userInfo:e,...n}=t.data[0];console.info("Notification Response:"),console.table(n),console.info("Notification Response Metadata:"),console.table(e);const r=` +
        Notification Response
        + + + ${Object.keys(n).map(i=>``).join("")} + + + ${Object.values(n).map(i=>``).join("")} + +
        ${i}
        ${i}
        +
        Notification Metadata
        + + + ${Object.keys(e).map(i=>``).join("")} + + + ${Object.values(e).map(i=>``).join("")} + +
        ${i}
        ${i}
        + `,o=document.querySelector("#response");o&&(o.innerHTML=r)});window.onbeforeunload=()=>at(); diff --git a/v3/examples/notifications/frontend/dist/index.html b/v3/examples/notifications/frontend/dist/index.html new file mode 100644 index 000000000..b7794f4d8 --- /dev/null +++ b/v3/examples/notifications/frontend/dist/index.html @@ -0,0 +1,32 @@ + + + + + + + + Wails App + + + +
        + +

        Wails + Typescript + Desktop Notifications

        +

        Send notifications 👇

        +
        + + + + +
        + +
        + + diff --git a/v3/examples/notifications/frontend/dist/style.css b/v3/examples/notifications/frontend/dist/style.css new file mode 100644 index 000000000..074717bca --- /dev/null +++ b/v3/examples/notifications/frontend/dist/style.css @@ -0,0 +1,131 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +* { + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +.controls { + display: flex; + gap: 1em; +} + +button { + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 3em; +} + +h1, h3 { + line-height: 1.1; + text-align: center; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +.footer table { + font-size: 12px; + border-collapse: collapse; + margin: 0 auto; +} + +.footer table, th, td { + border: 1px solid #ddd; + padding: 0.5em; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} \ No newline at end of file diff --git a/v3/examples/notifications/frontend/dist/typescript.svg b/v3/examples/notifications/frontend/dist/typescript.svg new file mode 100644 index 000000000..d91c910cc --- /dev/null +++ b/v3/examples/notifications/frontend/dist/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/notifications/frontend/dist/wails.png b/v3/examples/notifications/frontend/dist/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/notifications/frontend/dist/wails.png differ diff --git a/v3/examples/notifications/frontend/index.html b/v3/examples/notifications/frontend/index.html new file mode 100644 index 000000000..b873cd4f3 --- /dev/null +++ b/v3/examples/notifications/frontend/index.html @@ -0,0 +1,32 @@ + + + + + + + + Wails App + + +
        + +

        Wails + Typescript + Desktop Notifications

        +

        Send notifications 👇

        +
        + + + + +
        + +
        + + + diff --git a/v3/examples/notifications/frontend/package.json b/v3/examples/notifications/frontend/package.json new file mode 100644 index 000000000..b39da7ece --- /dev/null +++ b/v3/examples/notifications/frontend/package.json @@ -0,0 +1,19 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "tsc && vite build --minify false --mode development", + "build": "tsc && vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "typescript": "^4.9.3", + "vite": "^5.0.0" + } +} diff --git a/v3/examples/notifications/frontend/public/Inter-Medium.ttf b/v3/examples/notifications/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/notifications/frontend/public/Inter-Medium.ttf differ diff --git a/v3/examples/notifications/frontend/public/style.css b/v3/examples/notifications/frontend/public/style.css new file mode 100644 index 000000000..074717bca --- /dev/null +++ b/v3/examples/notifications/frontend/public/style.css @@ -0,0 +1,131 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +* { + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +.controls { + display: flex; + gap: 1em; +} + +button { + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 3em; +} + +h1, h3 { + line-height: 1.1; + text-align: center; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +.footer table { + font-size: 12px; + border-collapse: collapse; + margin: 0 auto; +} + +.footer table, th, td { + border: 1px solid #ddd; + padding: 0.5em; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} \ No newline at end of file diff --git a/v3/examples/notifications/frontend/public/typescript.svg b/v3/examples/notifications/frontend/public/typescript.svg new file mode 100644 index 000000000..d91c910cc --- /dev/null +++ b/v3/examples/notifications/frontend/public/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/notifications/frontend/public/wails.png b/v3/examples/notifications/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/notifications/frontend/public/wails.png differ diff --git a/v3/examples/notifications/frontend/src/main.ts b/v3/examples/notifications/frontend/src/main.ts new file mode 100644 index 000000000..94bbb7df3 --- /dev/null +++ b/v3/examples/notifications/frontend/src/main.ts @@ -0,0 +1,129 @@ +import { Events } from "@wailsio/runtime"; +import { NotificationService } from "../bindings/github.com/wailsapp/wails/v3/pkg/services/notifications"; + +const footer = document.querySelector("#response"); + +document.querySelector("#request")?.addEventListener("click", async () => { + try { + const authorized = await NotificationService.RequestNotificationAuthorization(); + if (authorized) { + if (footer) footer.innerHTML = "

        Notifications are now authorized.

        "; + console.info("Notifications are now authorized."); + } else { + if (footer) footer.innerHTML = "

        Notifications are not authorized. You can attempt to request again or let the user know in the UI.

        "; + console.warn("Notifications are not authorized.\n You can attempt to request again or let the user know in the UI.\n"); + } + } catch (error) { + console.error(error); + } +}); + +document.querySelector("#check")?.addEventListener("click", async () => { + try { + const authorized = await NotificationService.CheckNotificationAuthorization(); + if (authorized) { + if (footer) footer.innerHTML = "

        Notifications are authorized.

        "; + console.info("Notifications are authorized."); + } else { + if (footer) footer.innerHTML = "

        Notifications are not authorized. You can attempt to request again or let the user know in the UI.

        "; + console.warn("Notifications are not authorized.\n You can attempt to request again or let the user know in the UI.\n"); + } + } catch (error) { + console.error(error); + } +}); + +document.querySelector("#basic")?.addEventListener("click", async () => { + try { + const authorized = await NotificationService.CheckNotificationAuthorization(); + if (authorized) { + await NotificationService.SendNotification({ + id: crypto.randomUUID(), + title: "Notification Title", + subtitle: "Subtitle on macOS and Linux", + body: "Body text of notification.", + data: { + "user-id": "user-123", + "message-id": "msg-123", + "timestamp": Date.now(), + }, + }); + } else { + if (footer) footer.innerHTML = "

        Notifications are not authorized. You can attempt to request again or let the user know in the UI.

        "; + console.warn("Notifications are not authorized.\n You can attempt to request again or let the user know in the UI.\n"); + } + } catch (error) { + console.error(error); + } +}); +document.querySelector("#complex")?.addEventListener("click", async () => { + try { + const authorized = await NotificationService.CheckNotificationAuthorization(); + if (authorized) { + const CategoryID = "frontend-notification-id"; + + await NotificationService.RegisterNotificationCategory({ + id: CategoryID, + actions: [ + { id: "VIEW", title: "View" }, + { id: "MARK_READ", title: "Mark as read" }, + { id: "DELETE", title: "Delete", destructive: true }, + ], + hasReplyField: true, + replyPlaceholder: "Message...", + replyButtonTitle: "Reply", + }); + + await NotificationService.SendNotificationWithActions({ + id: crypto.randomUUID(), + title: "Notification Title", + subtitle: "Subtitle on macOS and Linux", + body: "Body text of notification.", + categoryId: CategoryID, + data: { + "user-id": "user-123", + "message-id": "msg-123", + "timestamp": Date.now(), + }, + }); + } else { + if (footer) footer.innerHTML = "

        Notifications are not authorized. You can attempt to request again or let the user know in the UI.

        "; + console.warn("Notifications are not authorized.\n You can attempt to request again or let the user know in the UI.\n"); + } + } catch (error) { + console.error(error); + } +}); + +const unlisten = Events.On("notification:action", (response) => { + console.info(`Recieved a ${response.name} event`); + const { userInfo, ...base } = response.data[0]; + console.info("Notification Response:"); + console.table(base); + console.info("Notification Response Metadata:"); + console.table(userInfo); + const table = ` +
        Notification Response
        + + + ${Object.keys(base).map(key => ``).join("")} + + + ${Object.values(base).map(value => ``).join("")} + +
        ${key}
        ${value}
        +
        Notification Metadata
        + + + ${Object.keys(userInfo).map(key => ``).join("")} + + + ${Object.values(userInfo).map(value => ``).join("")} + +
        ${key}
        ${value}
        + `; + const footer = document.querySelector("#response"); + if (footer) footer.innerHTML = table; +}); + +window.onbeforeunload = () => unlisten(); \ No newline at end of file diff --git a/v3/examples/notifications/frontend/src/vite-env.d.ts b/v3/examples/notifications/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/examples/notifications/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/examples/notifications/frontend/tsconfig.json b/v3/examples/notifications/frontend/tsconfig.json new file mode 100644 index 000000000..c267ecf24 --- /dev/null +++ b/v3/examples/notifications/frontend/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "noImplicitReturns": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/v3/examples/notifications/go.mod b/v3/examples/notifications/go.mod new file mode 100644 index 000000000..af22c8e53 --- /dev/null +++ b/v3/examples/notifications/go.mod @@ -0,0 +1,50 @@ +module notifications + +go 1.25 + +require github.com/wailsapp/wails/v3 v3.0.0-dev + +require ( + dario.cat/mergo v1.0.2 // indirect + git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-git/go-git/v5 v5.16.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/lmittmann/tint v1.1.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.52.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/wailsapp/go-webview2 v1.0.23 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/notifications/go.sum b/v3/examples/notifications/go.sum new file mode 100644 index 000000000..85f7a05c3 --- /dev/null +++ b/v3/examples/notifications/go.sum @@ -0,0 +1,149 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA= +git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= +github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/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-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/examples/notifications/main.go b/v3/examples/notifications/main.go new file mode 100644 index 000000000..c0e006652 --- /dev/null +++ b/v3/examples/notifications/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "embed" + _ "embed" + "fmt" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/services/notifications" +) + +// Wails uses Go's `embed` package to embed the frontend files into the binary. +// Any files in the frontend/dist folder will be embedded into the binary and +// made available to the frontend. +// See https://pkg.go.dev/embed for more information. + +//go:embed all:frontend/dist +var assets embed.FS + +// main function serves as the application's entry point. It initializes the application, creates a window, +// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and +// logs any error that might occur. +func main() { + // Create a new Notification Service + ns := notifications.New() + + // Create a new Wails application by providing the necessary options. + // Variables 'Name' and 'Description' are for application metadata. + // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files. + // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances. + // 'Mac' options tailor the application when running an macOS. + app := application.New(application.Options{ + Name: "Notifications Demo", + Description: "A demo of using desktop notifications with Wails", + Services: []application.Service{ + application.NewService(ns), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a new window with the necessary options. + // 'Title' is the title of the window. + // 'Mac' options tailor the window when running on macOS. + // 'BackgroundColour' is the background colour of the window. + // 'URL' is the URL that will be loaded into the webview. + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Name: "main", + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + BackgroundColour: application.NewRGB(27, 38, 54), + URL: "/", + }) + + // Pass a notification callback that will be called when a notification is actioned. + ns.OnNotificationResponse(func(result notifications.NotificationResult) { + if result.Error != nil { + println(fmt.Errorf("parsing notification result failed: %s", result.Error)) + } else { + fmt.Printf("Response: %+v\n", result.Response) + println("Sending response to frontend...") + app.Event.Emit("notification:action", result.Response) + } + }) + + // Run the application. This blocks until the application has been exited. + err := app.Run() + + // If an error occurred while running the application, log it and exit. + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/panic-handling/README.md b/v3/examples/panic-handling/README.md new file mode 100644 index 000000000..99068495f --- /dev/null +++ b/v3/examples/panic-handling/README.md @@ -0,0 +1,11 @@ +# Panic Handling Example + +This example is a demonstration of how to handle panics in your application. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` diff --git a/v3/examples/panic-handling/assets/index.html b/v3/examples/panic-handling/assets/index.html new file mode 100644 index 000000000..f4b5fe886 --- /dev/null +++ b/v3/examples/panic-handling/assets/index.html @@ -0,0 +1,27 @@ + + + + + + Window Call Demo + + + + + + + + + \ No newline at end of file diff --git a/v3/examples/panic-handling/main.go b/v3/examples/panic-handling/main.go new file mode 100644 index 000000000..3bf02136c --- /dev/null +++ b/v3/examples/panic-handling/main.go @@ -0,0 +1,62 @@ +package main + +import ( + "embed" + "fmt" + "github.com/wailsapp/wails/v3/pkg/application" + "log" +) + +//go:embed assets/* +var assets embed.FS + +var app *application.App + +type WindowService struct{} + +func (s *WindowService) GeneratePanic() { + s.call1() +} + +func (s *WindowService) call1() { + s.call2() +} + +func (s *WindowService) call2() { + panic("oh no! something went wrong deep in my service! :(") +} + +// ============================================== + +func main() { + app = application.New(application.Options{ + Name: "Panic Handler Demo", + Description: "A demo of Handling Panics", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, + Services: []application.Service{ + application.NewService(&WindowService{}), + }, + PanicHandler: func(panicDetails *application.PanicDetails) { + fmt.Printf("*** There was a panic! ***\n") + fmt.Printf("Time: %s\n", panicDetails.Time) + fmt.Printf("Error: %s\n", panicDetails.Error) + fmt.Printf("Stacktrace: %s\n", panicDetails.StackTrace) + app.Dialog.Info().SetMessage("There was a panic!").Show() + }, + }) + + app.Window.New(). + SetTitle("WebviewWindow 1"). + Show() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/plain/README.md b/v3/examples/plain/README.md new file mode 100644 index 000000000..9d44f5119 --- /dev/null +++ b/v3/examples/plain/README.md @@ -0,0 +1,19 @@ +# Plain Example + +This example is a demonstration of different ways to create applications without using npm. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/plain/main.go b/v3/examples/plain/main.go new file mode 100644 index 000000000..3132d65b8 --- /dev/null +++ b/v3/examples/plain/main.go @@ -0,0 +1,84 @@ +package main + +import ( + _ "embed" + "log" + "net/http" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Plain", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(`Plain Bundle

        Plain Bundle

        This is a plain bundle. It has no frontend code but this was Served by the AssetServer's Handler.



        Clicking this paragraph emits an event...

        `)) + }), + }, + }) + // Create window - Note: In future versions, window creation may return errors + // that should be checked. For now, window creation is deferred until app.Run() + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgb(255, 255, 255); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; user-select: none; -ms-user-select: none; -webkit-user-select: none; } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + }, + URL: "/", + }) + + // Create second window with direct HTML content + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "HTML TEST", + HTML: "

        AWESOME!

        ", + CSS: `body { background-color: rgb(255, 0, 0); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; user-select: none; -ms-user-select: none; -webkit-user-select: none; } .main { color: white; margin: 20%; }`, + JS: `window.iamhere = function() { console.log("Hello World!"); }`, + }) + + // Store the cleanup function to remove event listener when needed + removeClickHandler := app.Event.On("clicked", func(_ *application.CustomEvent) { + println("clicked") + }) + // Note: In a real application, you would call removeClickHandler() when appropriate + _ = removeClickHandler // Acknowledge we're storing the cleanup function + + // Use context-aware goroutine for graceful shutdown + go func() { + // Use a ticker instead of sleep to allow for cancellation + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + select { + case <-ticker.C: + // Create window after delay - in production, you should handle potential errors + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle new Window from GoRoutine", + Width: 500, + Height: 500, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + case <-app.Context().Done(): + // Application is shutting down, cancel the goroutine + return + } + }() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/print/.gitignore b/v3/examples/print/.gitignore new file mode 100644 index 000000000..bd7887dea --- /dev/null +++ b/v3/examples/print/.gitignore @@ -0,0 +1,2 @@ +bindings/ +build/ diff --git a/v3/examples/print/assets/index.html b/v3/examples/print/assets/index.html new file mode 100644 index 000000000..30ec4e0ab --- /dev/null +++ b/v3/examples/print/assets/index.html @@ -0,0 +1,143 @@ + + + + + + Print Dialog Test + + + +

        Print Dialog Test

        +

        Issue #4290: macOS Print dialog does not open

        + +
        +

        Test Instructions

        +
          +
        1. Click the "Print via Go API" button below, OR
        2. +
        3. Use the menu: File > Print, OR
        4. +
        5. Press Cmd+P
        6. +
        +

        Expected: A print dialog should appear as a sheet attached to this window.

        +

        Bug: Before the fix, nothing happens when trying to print.

        +
        + +
        +

        Test Actions

        + + +
        Ready to test...
        +
        + +
        +

        Sample Content to Print

        +

        This is sample content that should appear in the print preview.

        +
          +
        • Item 1: Testing print functionality
        • +
        • Item 2: Verifying dialog appearance
        • +
        • Item 3: Checking modal behavior
        • +
        +
        + +
        + Technical Details: +

        The bug was caused by passing a raw void* pointer instead of the properly cast NSWindow* to runOperationModalForWindow:.

        +

        Note: window.print() may not be natively supported in WKWebView. The Go API uses NSPrintOperation instead.

        +
        + + + + diff --git a/v3/examples/print/go.mod b/v3/examples/print/go.mod new file mode 100644 index 000000000..183b2bc09 --- /dev/null +++ b/v3/examples/print/go.mod @@ -0,0 +1,49 @@ +module print + +go 1.25 + +replace github.com/wailsapp/wails/v3 => ../../ + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-git/go-git/v5 v5.16.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/lmittmann/tint v1.1.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.52.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/wailsapp/go-webview2 v1.0.23 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) diff --git a/v3/examples/print/go.sum b/v3/examples/print/go.sum new file mode 100644 index 000000000..56f1153ea --- /dev/null +++ b/v3/examples/print/go.sum @@ -0,0 +1,147 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= +github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/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-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/examples/print/main.go b/v3/examples/print/main.go new file mode 100644 index 000000000..8de642cbc --- /dev/null +++ b/v3/examples/print/main.go @@ -0,0 +1,90 @@ +//go:build darwin + +package main + +import ( + "embed" + "log" + "runtime" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +// PrintService provides print functionality to the frontend +type PrintService struct { + app *application.App +} + +func (p *PrintService) Print() error { + if w := p.app.Window.Current(); w != nil { + log.Println("PrintService.Print() called") + return w.Print() + } + return nil +} + +func main() { + // Only run on macOS + if runtime.GOOS != "darwin" { + log.Fatal("This test is only for macOS") + } + + printService := &PrintService{} + + app := application.New(application.Options{ + Name: "Print Dialog Test", + Description: "Test for macOS print dialog (Issue #4290)", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Services: []application.Service{ + application.NewService(printService), + }, + }) + + printService.app = app + + // Create application menu + menu := app.NewMenu() + + // File menu + fileMenu := menu.AddSubmenu("File") + fileMenu.Add("Print..."). + SetAccelerator("CmdOrCtrl+P"). + OnClick(func(ctx *application.Context) { + if w := app.Window.Current(); w != nil { + log.Println("Attempting to print...") + if err := w.Print(); err != nil { + log.Printf("Print error: %v", err) + } else { + log.Println("Print completed (or dialog dismissed)") + } + } + }) + fileMenu.AddSeparator() + fileMenu.Add("Quit"). + SetAccelerator("CmdOrCtrl+Q"). + OnClick(func(ctx *application.Context) { + app.Quit() + }) + + app.Menu.Set(menu) + + // Create main window + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Print Dialog Test - Issue #4290", + Width: 800, + Height: 600, + URL: "/index.html", + }) + + log.Println("Starting application. Use File > Print or Cmd+P to test print dialog.") + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/raw-message/README.md b/v3/examples/raw-message/README.md new file mode 100644 index 000000000..8d28d4f8a --- /dev/null +++ b/v3/examples/raw-message/README.md @@ -0,0 +1,11 @@ +# Raw Message Example + +This example is a demonstration of sending raw messages from JS to Go. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run main.go +``` diff --git a/v3/examples/raw-message/assets/index.html b/v3/examples/raw-message/assets/index.html new file mode 100644 index 000000000..52f174336 --- /dev/null +++ b/v3/examples/raw-message/assets/index.html @@ -0,0 +1,25 @@ + + + + + Title + + + + +

        Raw Message Demo

        +
        +To send a raw message from this window, enter some text and click the button: +
        +
        + + + + diff --git a/v3/examples/raw-message/main.go b/v3/examples/raw-message/main.go new file mode 100644 index 000000000..697773373 --- /dev/null +++ b/v3/examples/raw-message/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "embed" + _ "embed" + "fmt" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Raw Message Demo", + Description: "A demo of sending raw messages from the frontend", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) { + println(fmt.Sprintf("Raw message received from Window %s with message: %s, origin %s, topOrigin %s, isMainFrame %t", window.Name(), message, originInfo.Origin, originInfo.TopOrigin, originInfo.IsMainFrame)) + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Name: "Window 1", + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/screen/README.md b/v3/examples/screen/README.md new file mode 100644 index 000000000..1f922f341 --- /dev/null +++ b/v3/examples/screen/README.md @@ -0,0 +1,19 @@ +# Screen Example + +This example will detect all attached screens and display their details. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/screen/assets/examples.js b/v3/examples/screen/assets/examples.js new file mode 100644 index 000000000..8abe3f0ca --- /dev/null +++ b/v3/examples/screen/assets/examples.js @@ -0,0 +1,206 @@ +window.examples = [ + [ + // Normal examples (demonstrate real life scenarios) + { + name: "Single 4k monitor", + screens: [ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + ] + }, + { + name: "Two monitors", + screens: [ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + {id: 2, w: 1920, h: 1080, s: 1, parent: {id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI`}, + ] + }, + { + name: "Two monitors (2)", + screens: [ + {id: 1, w: 1920, h: 1080, s: 1, name: `23" FHD 96DPI`}, + {id: 2, w: 1920, h: 1080, s: 1.25, parent: {id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI (125%)`}, + ] + }, + { + name: "Three monitors", + screens: [ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + {id: 2, w: 1920, h: 1080, s: 1, parent: {id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI`}, + {id: 3, w: 1920, h: 1080, s: 1.25, parent: {id: 1, align: "l", offset: 0}, name: `23" FHD 96DPI (125%)`}, + ] + }, + { + name: "Four monitors", + screens: [ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + {id: 2, w: 1920, h: 1080, s: 1, parent: {id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI`}, + {id: 3, w: 1920, h: 1080, s: 1.25, parent: {id: 2, align: "b", offset: 0}, name: `23" FHD 96DPI (125%)`}, + {id: 4, w: 1080, h: 1920, s: 1, parent: {id: 1, align: "l", offset: 0}, name: `23" FHD (90deg)`}, + ] + }, + ], + [ + // Test cases examples (demonstrate the algorithm basics) + { + name: "Child scaled, Start offset", + screens: [ + {id: 1, w: 1200, h: 1200, s: 1, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1.5, parent: {id: 1, align: "r", offset: 600}, name: "Child"}, + ] + }, + { + name: "Child scaled, End offset", + screens: [ + {id: 1, w: 1200, h: 1200, s: 1, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1.5, parent: {id: 1, align: "r", offset: -600}, name: "Child"}, + ] + }, + { + name: "Parent scaled, Start offset percent", + screens: [ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1, parent: {id: 1, align: "r", offset: 600}, name: "Child"}, + ] + }, + { + name: "Parent scaled, End offset percent", + screens: [ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1, parent: {id: 1, align: "r", offset: -600}, name: "Child"}, + ] + }, + { + name: "Parent scaled, Start align", + screens: [ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1100, s: 1, parent: {id: 1, align: "r", offset: 0}, name: "Child"}, + ] + }, + { + name: "Parent scaled, End align", + screens: [ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1, parent: {id: 1, align: "r", offset: 0}, name: "Child"}, + ] + }, + { + name: "Parent scaled, in-between", + screens: [ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1500, s: 1, parent: {id: 1, align: "r", offset: -200}, name: "Child"}, + ] + }, + ], + [ + // Edge cases examples + { + name: "Parent order (5 is parent of 4)", + screens: [ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 600, s: 1.25, parent: {id: 1, align: "r", offset: -200}}, + {id: 3, w: 800, h: 800, s: 1.25, parent: {id: 2, align: "b", offset: 0}}, + {id: 4, w: 800, h: 1080, s: 1.5, parent: {id: 2, align: "re", offset: 100}}, + {id: 5, w: 600, h: 600, s: 1, parent: {id: 3, align: "r", offset: 100}}, + ] + }, + { + name: "de-intersection reparent", + screens: [ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1680, h: 1050, s: 1.25, parent: {id: 1, align: "r", offset: 10}}, + {id: 3, w: 1440, h: 900, s: 1.5, parent: {id: 1, align: "le", offset: 150}}, + {id: 4, w: 1024, h: 768, s: 1, parent: {id: 3, align: "bc", offset: -200}}, + {id: 5, w: 1024, h: 768, s: 1.25, parent: {id: 4, align: "r", offset: 400}}, + ] + }, + { + name: "de-intersection (unattached child)", + screens: [ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 768, s: 1.5, parent: {id: 1, align: "le", offset: 10}}, + {id: 3, w: 1024, h: 768, s: 1.25, parent: {id: 2, align: "b", offset: 100}}, + {id: 4, w: 1024, h: 768, s: 1, parent: {id: 3, align: "r", offset: 500}}, + ] + }, + { + name: "Multiple de-intersection", + screens: [ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 768, s: 1, parent: {id: 1, align: "be", offset: 0}}, + {id: 3, w: 1024, h: 768, s: 1, parent: {id: 2, align: "b", offset: 300}}, + {id: 4, w: 1024, h: 768, s: 1.5, parent: {id: 2, align: "le", offset: 100}}, + {id: 5, w: 1024, h: 768, s: 1, parent: {id: 4, align: "be", offset: 100}}, + ] + }, + { + name: "Multiple de-intersection (left-side)", + screens: [ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 768, s: 1, parent: {id: 1, align: "le", offset: 0}}, + {id: 3, w: 1024, h: 768, s: 1, parent: {id: 2, align: "b", offset: 300}}, + {id: 4, w: 1024, h: 768, s: 1.5, parent: {id: 2, align: "le", offset: 100}}, + {id: 5, w: 1024, h: 768, s: 1, parent: {id: 4, align: "be", offset: 100}}, + ] + }, + { + name: "Parent de-intersection child offset", + screens: [ + {id: 1, w: 1600, h: 1600, s: 1.5}, + {id: 2, w: 800, h: 800, s: 1, parent: {id: 1, align: "r", offset: 0}}, + {id: 3, w: 800, h: 800, s: 1, parent: {id: 1, align: "r", offset: 800}}, + {id: 4, w: 800, h: 1600, s: 1, parent: {id: 2, align: "r", offset: 0}}, + ] + }, + ], +].map(sections => sections.map(layout => { + return parseLayout(layout) +})) + +function parseLayout(layout) { + const screens = [] + + for (const screen of layout.screens) { + let x = 0, y = 0 + const {w, h} = screen + + if (screen.parent) { + const parent = screens.find(s => s.ID == screen.parent.id).Bounds + const offset = screen.parent.offset + let align = screen.parent.align + let align2 = "" + + if (align.length == 2) { + align2 = align.charAt(1) + align = align.charAt(0) + } + + x = parent.X + y = parent.Y + // t: top, b: bottom, l: left, r: right, e: edge, c: corner + if (align == "t" || align == "b") { + x += offset + (align2 == "e" || align2 == "c" ? parent.Width : 0) - (align2 == "e" ? w : 0) + y += (align == "t" ? -h : parent.Height) + } else { + y += offset + (align2 == "e" || align2 == "c" ? parent.Height : 0) - (align2 == "e" ? h : 0) + x += (align == "l" ? -w : parent.Width) + } + } + + screens.push({ + ID: `${screen.id}`, + Name: screen.name ?? `Display${screen.id}`, + ScaleFactor: Math.round(screen.s * 100) / 100, + X: x, + Y: y, + Size: {Width: w, Height: h}, + Bounds: {X: x, Y: y, Width: w, Height: h}, + PhysicalBounds: {X: x, Y: y, Width: w, Height: h}, + WorkArea: {X: x, Y: y, Width: w, Height: h-Math.round(40*screen.s)}, + PhysicalWorkArea: {X: x, Y: y, Width: w, Height: h-Math.round(40*screen.s)}, + IsPrimary: screen.id == 1, + Rotation: 0 + }) + } + + return {name: layout.name, screens} +} diff --git a/v3/examples/screen/assets/index.html b/v3/examples/screen/assets/index.html new file mode 100644 index 000000000..358624411 --- /dev/null +++ b/v3/examples/screen/assets/index.html @@ -0,0 +1,141 @@ + + + + + Screens Demo + + + +
        + + +
        + +  X: + + +  Width: + + +   + + +   + + +   + +   + Layers: +
        +
        +
        +
        + Screens:  + System + +  - Examples + : +   + + +
        +
        + Coordinates:  + Physical (PX) + Logical (DIP) + + +   + + + + + +
        +
        + + + + + + +
        + + + + + + diff --git a/v3/examples/screen/assets/main.js b/v3/examples/screen/assets/main.js new file mode 100644 index 000000000..316c08002 --- /dev/null +++ b/v3/examples/screen/assets/main.js @@ -0,0 +1,406 @@ +setExamplesType(document.getElementById('examples-type').value, 0) + +function setExamplesType(type, autoSelectLayout = 1) { + window.examples_type = parseInt(type) + document.getElementById('examples-list').innerHTML = examples[examples_type].map((layout, i) => { + return `${i + 1}` + }).join("\n") + if (autoSelectLayout != null) setLayout(autoSelectLayout) +} + +async function setLayout(indexOrLayout, physicalCoordinate = true) { + if (typeof indexOrLayout == 'number') { + await radioBtnClick(null, `#layout-selector [data-value="${indexOrLayout}"]`) + } else { + document.querySelectorAll('#layout-selector .active').forEach(el => el.classList.remove('active')) + window.layout = indexOrLayout + window.point = null + window.rect = null + await processLayout() + await draw() + } + + const physical = !parseInt(document.querySelector('#coordinate-selector .active').dataset.value) + if (physical != physicalCoordinate) { + await setCoordinateType(physicalCoordinate) + } +} + +async function setCoordinateType(physicalCoordinate = true) { + await radioBtnClick(null, `#coordinate-selector [data-value="${physicalCoordinate ? 0 : 1}"]`) +} + +async function radioBtnClick(e, selector) { + if (e == null) { + e = new Event("mousedown") + document.querySelector(selector).dispatchEvent(e) + } + if (!e.target.classList.contains('radio-btn')) return + const btnGroup = e.target.closest('.radio-btn-group') + btnGroup.querySelectorAll('.radio-btn.active').forEach(el => el.classList.remove('active')) + e.target.classList.add('active') + + if (btnGroup.id == 'layout-selector') { + window.point = null + window.rect = null + await processLayout() + } + + await draw() +} + +async function processLayout() { + const layoutBtn = document.querySelector('#layout-selector .active') + const i = layoutBtn ? parseInt(layoutBtn.dataset.value) : -1 + if (i == 0) { + // system screens + window.layout = { + name: '', + screens: await callBinding('main.ScreenService.GetSystemScreens'), + } + } else { + if (i > 0) { + // example layouts + window.layout = structuredClone(examples[examples_type][i - 1]) + } + layout.screens = await callBinding('main.ScreenService.ProcessExampleScreens', layout.screens) + } + document.getElementById('example-name').textContent = layout.name +} + +async function draw() { + console.log(layout) + let minX = 0, minY = 0, maxX = 0, maxY = 0; + let html = ''; + + const physical = !parseInt(document.querySelector('#coordinate-selector .active').dataset.value) + const retainViewbox = document.querySelector('#retain-viewbox').checked + + layout.screens.forEach(screen => { + const b = physical ? screen.PhysicalBounds : screen.Bounds + const wa = physical ? screen.PhysicalWorkArea : screen.WorkArea + const vbBounds = retainViewbox ? [screen.Bounds, screen.PhysicalBounds] : [b] + + minX = Math.min(minX, ...vbBounds.map(b => b.X)) + minY = Math.min(minY, ...vbBounds.map(b => b.Y)) + maxX = Math.max(maxX, ...vbBounds.map(b => b.X + b.Width)) + maxY = Math.max(maxY, ...vbBounds.map(b => b.Y + b.Height)) + + html += ` + + + + + + (${b.X}, ${b.Y}) + + ${screen.Name} + ${b.Width} x ${b.Height} + Scale factor: ${screen.ScaleFactor} + + + ` + }) + + const svg = document.getElementById('svg') + svg.innerHTML = ` + ${svg.querySelector('& > defs').outerHTML} + + ${html} + + + ` + + svg.setAttribute('viewBox', `${minX} ${minY} ${maxX - minX} ${maxY - minY}`) + + if (window.point) await probePoint() + if (window.rect) await drawRect() + + svg.onmousedown = async function(e) { + let pt = new DOMPoint(e.clientX, e.clientY) + pt = pt.matrixTransform(svg.getScreenCTM().inverse()) + pt.x = parseInt(pt.x) + pt.y = parseInt(pt.y) + if (e.buttons == 1) { + await probePoint({X: pt.x, Y: pt.y}) + } else if (e.buttons == 2) { + if (e.ctrlKey) { + if (!window.rect) { + window.rect = {X: pt.x, Y: pt.y, Width: 0, Height: 0} + } + if (!window.rectCursor) { + window.rectAnchor = {x: window.rect.X, y: window.rect.Y} + window.rectCursor = {x: window.rectAnchor.x + window.rect.Width, y: window.rectAnchor.y + window.rect.Height} + } + window.rectCursorOffset = { + x: pt.x - window.rectCursor.x, + y: pt.y - window.rectCursor.y, + } + } else { + window.rectAnchor = pt + window.rectCursorOffset = {x: 0, y: 0} + window.probing = true + drawRect({X: pt.x, Y: pt.y, Width: 0, Height: 0}) + window.probing = false + } + } else if (e.buttons == 4) { + drawRect({X: pt.x, Y: pt.y, Width: 50, Height: 50}) + } + } + svg.onmousemove = async function(e) { + if (window.probing) return + window.probing = true + if (e.buttons == 1) { + await svg.onmousedown(e) + } else if (e.buttons == 2) { + let pt = new DOMPoint(e.clientX, e.clientY) + pt = pt.matrixTransform(svg.getScreenCTM().inverse()) + if (e.ctrlKey) { + window.rectAnchor.x += pt.x - rectCursor.x - window.rectCursorOffset.x + window.rectAnchor.y += pt.y - rectCursor.y - window.rectCursorOffset.y + } + window.rectCursor = { + x: pt.x - window.rectCursorOffset.x, + y: pt.y - window.rectCursorOffset.y, + } + await drawRect({ + X: parseInt(Math.min(window.rectAnchor.x, window.rectCursor.x)), + Y: parseInt(Math.min(window.rectAnchor.y, window.rectCursor.y)), + Width: parseInt(Math.abs(window.rectCursor.x - window.rectAnchor.x)), + Height: parseInt(Math.abs(window.rectCursor.y - window.rectAnchor.y)), + }) + } + window.probing = false + } + svg.oncontextmenu = function(e) { + e.preventDefault() + } +} + +async function probePoint(p = null) { + const svg = document.getElementById('svg'); + const physical = !parseInt(document.querySelector('#coordinate-selector .active').dataset.value) + + if (p == null) { + if (window.pointIsPhysical == physical) { + p = window.point + } else { + p = (await callBinding('main.ScreenService.TransformPoint', window.point, window.pointIsPhysical))[0] + } + } + + window.point = p + window.pointIsPhysical = physical + const [ptTransformed, ptDblTransformed] = await callBinding('main.ScreenService.TransformPoint', p, physical) + + svg.getElementById('points').innerHTML = ` + + + + + ` + // await new Promise((resolve) => setTimeout(resolve, 200)) // delay + return ptDblTransformed +} + +async function drawRect(r = null) { + const svg = document.getElementById('svg'); + const physical = !parseInt(document.querySelector('#coordinate-selector .active').dataset.value) + + if (r == null) { + if (window.rectIsPhysical == physical) { + r = window.rect + } else { + r = await callBinding('main.ScreenService.TransformRect', window.rect, window.rectIsPhysical) + } + } + + if (!window.probing) { + window.rectAnchor = null + window.rectCursor = null + } + + document.getElementById('x').value = r.X + document.getElementById('y').value = r.Y + document.getElementById('w').value = r.Width + document.getElementById('h').value = r.Height + + window.rect = r + window.rectIsPhysical = physical + window.rTransformed = await callBinding('main.ScreenService.TransformRect', r, physical) + window.rDblTransformed = await callBinding('main.ScreenService.TransformRect', rTransformed, !physical) + window.rTransformed = rTransformed + + await rectLayers() + return rDblTransformed +} + +async function rectLayers() { + const s = document.getElementById('slider').value + if (window.rect == null) await test1() + + const r = await callBinding('main.ScreenService.TransformRect', rectIsPhysical ? rect : rTransformed, true) + const rShifted = {...r, X: r.X+50} + const rShiftedPhysical = await callBinding('main.ScreenService.TransformRect', rShifted, false) + + svg.getElementById('rects').innerHTML = [ + [window.rect, 'rgb(255 255 255 / 100%)'], // w + [window.rTransformed, 'rgb(0 255 0 / 25%)'], // g + [window.rDblTransformed, 'none'], // none + [rShifted, 'rgb(255 0 0 / 15%)'], // r + [rShiftedPhysical, 'rgb(0 0 255 / 15%)'], // b + ].filter((_,i) => i { + let lines = '' + if (i == 0) { + const center = {X: r.X + (r.Width-1)/2, Y: r.Y + (r.Height-1)/2} + lines += ` + + + ` + } + return `${lines}` + }).join('/n') +} + +async function updateDipRect(x, y=0, w=0, h=0) { + if (rect == null) { + await drawRect({ + X: +document.getElementById('x').value, + Y: +document.getElementById('y').value, + Width: +document.getElementById('w').value, + Height: +document.getElementById('h').value, + }) + } + // Simulate real window by first retrieving the physical bounds then transforming it to dip + // then updating the bounds and transforming it back to physical + let rPhysical = rectIsPhysical ? rect : rTransformed + const r = await callBinding('main.ScreenService.TransformRect', rPhysical, true) + r.X += x + r.Y += y + r.Width += w + r.Height += h + rPhysical = await callBinding('main.ScreenService.TransformRect', r, false) + drawRect(rectIsPhysical ? rPhysical : r) +} + +function arrowMove(e) { + let x = 0, y = 0 + if (e.key == 'ArrowLeft') x = -step.value + if (e.key == 'ArrowRight') x = +step.value + if (e.key == 'ArrowUp') y = -step.value + if (e.key == 'ArrowDown') y = +step.value + if (!(x || y)) return + e.preventDefault() + updateDipRect(x, y) +} + +async function test1() { + // Edge case 1: invalid dip rect: no physical rect can produce it + await setLayout(parseLayout({screens: [ + {id: 1, w: 1200, h: 1200, s: 1}, + {id: 2, w: 1200, h: 1100, s: 1.5, parent: {id: 1, align: "r", offset: 0}}, + ]}), false) + await drawRect({X: 1050, Y: 700, Width: 400, Height: 300}) +} + +async function test2() { + // Edge case 2: physical rect that changes when double transformed (2 physical rects produce the same dip rect) + await setLayout(parseLayout({screens: [ + {id: 1, w: 1200, h: 1200, s: 1.5}, + {id: 2, w: 1200, h: 900, s: 1, parent: {id: 1, align: "r", offset: 0}}, + ]}), true) + await drawRect({X: 1050, Y: 890, Width: 400, Height: 300}) +} + +async function probeLayout(finishup = true) { + const probeButtons = document.getElementById('probe-buttons') + const svg = document.getElementById('svg') + const threshold = 1 + + const physical = !parseInt(document.querySelector('#coordinate-selector .active').dataset.value) + window.cancelProbing = false + probeButtons.classList.add('active') + + const steps = 3 + let failed = false + for (const screen of layout.screens) { + if (window.cancelProbing) break + const b = physical ? screen.PhysicalBounds : screen.Bounds + const xStep = parseInt(b.Width / steps) || 1 + const yStep = parseInt(b.Height / steps) || 1 + let x = b.X, y = b.Y + let xDone = false, yDone = false + + while (!(yDone || window.cancelProbing)) { + if (y >= b.Y + b.Height - 1) { + y = b.Y + b.Height - 1 + yDone = true + } + x = b.X + xDone = false + while (!(xDone || window.cancelProbing)) { + if (x >= b.X + b.Width - 1) { + x = b.X + b.Width - 1 + xDone = true + } + const pt = {X: x, Y: y} + let ptDblTransformed, err + try { + ptDblTransformed = await probePoint(pt) + } catch (e) { + err = e + } + if (err || Math.abs(pt.X - ptDblTransformed.X) > threshold || Math.abs(pt.Y - ptDblTransformed.Y) > threshold) { + failed = true + console.log(pt, ptDblTransformed) + window.cancelProbing = true + setTimeout(() => { + alert(err ?? `**FAILED**\nProbing failed at point: {X: ${pt.X}, Y: ${pt.Y}}\nDouble transformed point: {X: ${ptDblTransformed.X}, Y: ${ptDblTransformed.Y}}\n(Exceeded threshold of ${threshold} pixels)`) + }, 50) + } + x += xStep + } + y += yStep + } + } + + if (finishup || window.cancelProbing) probeButtons.classList.remove('active') + if (!(failed || window.cancelProbing)) { + window.point = null + if (finishup) { + setTimeout(() => { + svg.getElementById('points').innerHTML = '' + alert(`Successfully probed all points!, All within threshold of ${threshold} pixels.`) + }, 50) + } + return true + } +} + +async function probeAllExamples() { + console.time('probeAllExamples') +loop1: + for (let typeI = 0; typeI < examples.length; typeI++) { + document.getElementById('examples-type').value = typeI + setExamplesType(typeI, null) + + for (let layoutI = (typeI ? 0 : -1); layoutI < examples[typeI].length; layoutI++) { + await radioBtnClick(null, `#layout-selector [data-value="${layoutI + 1}"]`) + for (let i = 0; i < 2; i++) { + const lastLayout = (typeI == examples.length - 1 && layoutI == examples[typeI].length - 1 && i == 1) + if (!await probeLayout(lastLayout)) break loop1 + if (i == 0) await setCoordinateType(!pointIsPhysical) + } + } + } + console.timeEnd('probeAllExamples') +} + +async function callBinding(name, ...params) { + return wails.Call.ByName(name, ...params) +} + +function showAdvanced(e) { + e.target.style.display = 'none' + document.querySelectorAll('.advanced').forEach(el => el.style.display = 'initial') +} diff --git a/v3/examples/screen/main.go b/v3/examples/screen/main.go new file mode 100644 index 000000000..75d0c8bd2 --- /dev/null +++ b/v3/examples/screen/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "embed" + "log" + "log/slog" + "net/http" + "os" + "path/filepath" + "runtime" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Screen Demo", + Description: "A demo of the Screen API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Windows: application.WindowsOptions{ + WndProcInterceptor: nil, + DisableQuitOnLastWindowClosed: false, + WebviewUserDataPath: "", + WebviewBrowserPath: "", + }, + Services: []application.Service{ + application.NewService(&ScreenService{}), + }, + LogLevel: slog.LevelError, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + Middleware: func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Disable caching + w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") + w.Header().Set("Pragma", "no-cache") + w.Header().Set("Expires", "0") + + _, filename, _, _ := runtime.Caller(0) + dir := filepath.Dir(filename) + url := r.URL.Path + path := dir + "/assets" + url + + if _, err := os.Stat(path); err == nil { + // Serve file from disk to make testing easy + http.ServeFile(w, r, path) + } else { + // Passthrough to the default asset handler if file not found on disk + next.ServeHTTP(w, r) + } + }) + }, + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Screen Demo", + Width: 800, + Height: 600, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/screen/screens.go b/v3/examples/screen/screens.go new file mode 100644 index 000000000..a19afb14b --- /dev/null +++ b/v3/examples/screen/screens.go @@ -0,0 +1,137 @@ +package main + +import ( + "runtime" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type ScreenService struct { + screenManager application.ScreenManager + isExampleLayout bool +} + +func (s *ScreenService) GetSystemScreens() []*application.Screen { + s.isExampleLayout = false + screens := application.Get().Screen.GetAll() + return screens +} + +func (s *ScreenService) ProcessExampleScreens(rawScreens []interface{}) []*application.Screen { + s.isExampleLayout = true + + parseRect := func(m map[string]interface{}) application.Rect { + return application.Rect{ + X: int(m["X"].(float64)), + Y: int(m["Y"].(float64)), + Width: int(m["Width"].(float64)), + Height: int(m["Height"].(float64)), + } + } + + // Prevent unbounded slice growth by limiting the number of screens + maxScreens := 32 // Reasonable limit for screen configurations + if len(rawScreens) > maxScreens { + rawScreens = rawScreens[:maxScreens] + } + + screens := make([]*application.Screen, 0, len(rawScreens)) + for _, s := range rawScreens { + s := s.(map[string]interface{}) + + bounds := parseRect(s["Bounds"].(map[string]interface{})) + + screens = append(screens, &application.Screen{ + ID: s["ID"].(string), + Name: s["Name"].(string), + X: bounds.X, + Y: bounds.Y, + Size: application.Size{Width: bounds.Width, Height: bounds.Height}, + Bounds: bounds, + PhysicalBounds: parseRect(s["PhysicalBounds"].(map[string]interface{})), + WorkArea: parseRect(s["WorkArea"].(map[string]interface{})), + PhysicalWorkArea: parseRect(s["PhysicalWorkArea"].(map[string]interface{})), + IsPrimary: s["IsPrimary"].(bool), + ScaleFactor: float32(s["ScaleFactor"].(float64)), + Rotation: 0, + }) + } + + s.screenManager.LayoutScreens(screens) + return s.screenManager.GetAll() +} + +func (s *ScreenService) transformPoint(point application.Point, toDIP bool) application.Point { + if s.isExampleLayout { + if toDIP { + return s.screenManager.PhysicalToDipPoint(point) + } else { + return s.screenManager.DipToPhysicalPoint(point) + } + } else { + // ======================= + // TODO: remove this block when DPI is implemented in Linux & Mac + if runtime.GOOS != "windows" { + println("DPI not implemented yet!") + return point + } + // ======================= + if toDIP { + return application.PhysicalToDipPoint(point) + } else { + return application.DipToPhysicalPoint(point) + } + } +} + +func (s *ScreenService) TransformPoint(point map[string]interface{}, toDIP bool) (points [2]application.Point) { + pt := application.Point{ + X: int(point["X"].(float64)), + Y: int(point["Y"].(float64)), + } + + ptTransformed := s.transformPoint(pt, toDIP) + ptDblTransformed := s.transformPoint(ptTransformed, !toDIP) + + // double-transform a limited number of times to catch any double-rounding issues + // Limit iterations to prevent potential performance issues + maxIterations := 3 // Reduced from 10 to limit computational overhead + for i := 0; i < maxIterations; i++ { + ptTransformed = s.transformPoint(ptDblTransformed, toDIP) + ptDblTransformed = s.transformPoint(ptTransformed, !toDIP) + } + + points[0] = ptTransformed + points[1] = ptDblTransformed + return points +} + +func (s *ScreenService) TransformRect(rect map[string]interface{}, toDIP bool) application.Rect { + r := application.Rect{ + X: int(rect["X"].(float64)), + Y: int(rect["Y"].(float64)), + Width: int(rect["Width"].(float64)), + Height: int(rect["Height"].(float64)), + } + + if s.isExampleLayout { + if toDIP { + return s.screenManager.PhysicalToDipRect(r) + } else { + return s.screenManager.DipToPhysicalRect(r) + } + } else { + // ======================= + // TODO: remove this block when DPI is implemented in Linux & Mac + if runtime.GOOS != "windows" { + println("DPI not implemented yet!") + return r + } + // ======================= + if toDIP { + return application.PhysicalToDipRect(r) + } else { + return application.DipToPhysicalRect(r) + } + } +} diff --git a/v3/examples/server/Dockerfile b/v3/examples/server/Dockerfile new file mode 100644 index 000000000..765354108 --- /dev/null +++ b/v3/examples/server/Dockerfile @@ -0,0 +1,55 @@ +# Wails Server Mode Dockerfile +# Multi-stage build for minimal image size +# +# BUILD FROM v3 ROOT (to use local code before server mode is published): +# docker build -t server-example -f examples/server/Dockerfile . +# +# BUILD FROM EXAMPLE DIR (after server mode is published): +# cd examples/server && docker build -t server-example . + +# Build stage +FROM golang:alpine AS builder + +WORKDIR /app + +# Install build dependencies +RUN apk add --no-cache git + +# Copy source code +COPY . . + +# If building from example dir, remove replace directive +# If building from v3 root, this is a no-op +RUN sed -i '/^replace/d' go.mod 2>/dev/null || true + +# Download dependencies +RUN go mod tidy + +# Build the server binary and prepare assets +# When building from v3 root, change to example dir +RUN if [ -d "examples/server" ]; then \ + cd examples/server && go build -tags server -ldflags="-s -w" -o /app/server . && \ + cp -r frontend/dist /app/frontend_dist; \ + else \ + go build -tags server -ldflags="-s -w" -o /app/server . && \ + cp -r frontend/dist /app/frontend_dist; \ + fi + +# Runtime stage - minimal image +FROM gcr.io/distroless/static-debian12 + +# Copy the binary +COPY --from=builder /app/server /server + +# Copy frontend assets +COPY --from=builder /app/frontend_dist /frontend/dist + +# Expose the default port +EXPOSE 8080 + +# Bind to all interfaces (required for Docker) +# Can be overridden at runtime with -e WAILS_SERVER_HOST=... +ENV WAILS_SERVER_HOST=0.0.0.0 + +# Run the server +ENTRYPOINT ["/server"] diff --git a/v3/examples/server/README.md b/v3/examples/server/README.md new file mode 100644 index 000000000..ca9f16514 --- /dev/null +++ b/v3/examples/server/README.md @@ -0,0 +1,134 @@ +# Server Mode Example + +> **Experimental** - This feature is experimental and may change in future releases. + +This example demonstrates running a Wails application in server mode - without a native GUI window. + +## What is Server Mode? + +Server mode allows you to run your Wails application as a pure HTTP server. This enables: + +- **Docker/Container deployments** - Deploy your Wails app without X11/Wayland dependencies +- **Server-side rendering** - Use your Wails app as a web server +- **Web-only access** - Share the same codebase between desktop and web deployments +- **CI/CD testing** - Run integration tests without a display server + +## Building and Running + +The recommended way to build server mode applications is using the Taskfile: + +```bash +# Build for server mode +wails3 task build:server + +# Build and run +wails3 task run:server +``` + +Or using Go directly: + +```bash +# Build with server tag +go build -tags server -o myapp-server . + +# Run +go run -tags server . +``` + +Then open in your browser. + +## Key Differences from Desktop Mode + +1. **No native window** - The app runs as an HTTP server only +2. **Browser access** - Users access the app via their web browser +3. **No CGO required** - Can build without CGO dependencies +4. **Window APIs are no-ops** - Calls to window-related APIs are safely ignored +5. **Browser windows** - Each browser tab is represented as a "window" named `browser-1`, `browser-2`, etc. + +## Events + +Events work bidirectionally in server mode: + +- **Frontend to Backend**: Events emitted from the browser are sent via HTTP and received by your Go event handlers +- **Backend to Frontend**: Events emitted from Go are broadcast to all connected browsers via WebSocket + +```go +// Listen for events from browsers +app.Event.On("user-action", func(event *application.CustomEvent) { + log.Printf("Event from %s: %v", event.Sender, event.Data) + // event.Sender will be "browser-1", "browser-2", etc. +}) + +// Emit events to all browsers +app.Event.Emit("server-update", data) +``` + +## Configuration + +Server mode is enabled by building with the `server` build tag. Configure the HTTP server options: + +```go +app := application.New(application.Options{ + // Configure the HTTP server (used when built with -tags server) + Server: application.ServerOptions{ + Host: "localhost", // Use "0.0.0.0" for all interfaces + Port: 8080, + }, + + // ... other options work the same as desktop mode +}) +``` + +## Health Check + +A health check endpoint is automatically available at `/health`: + +```bash +curl http://localhost:8080/health +# {"status":"ok"} +``` + +## Building for Production + +```bash +# Using Taskfile (recommended) +wails3 task build:server + +# Or using Go directly +go build -tags server -o myapp-server . +``` + +## Docker + +Build and run with Docker using the built-in tasks: + +```bash +# Build Docker image +task build:docker + +# Build and run +task run:docker + +# Run on a different port +task run:docker PORT=3000 +``` + +Or build manually: + +```bash +docker build -t server-example . +docker run --rm -p 8080:8080 server-example +``` + +## Limitations + +Since server mode runs without a native GUI, the following features are not available: + +- Native dialogs (file open/save, message boxes) +- System tray +- Native menus +- Window manipulation (resize, move, minimize, etc.) +- Clipboard access (use browser clipboard APIs instead) +- Screen information + +These APIs are safe to call but will have no effect or return default values. diff --git a/v3/examples/server/Taskfile.yml b/v3/examples/server/Taskfile.yml new file mode 100644 index 000000000..bc9d3acf8 --- /dev/null +++ b/v3/examples/server/Taskfile.yml @@ -0,0 +1,80 @@ +version: '3' + +vars: + APP_NAME: "server-example" + BIN_DIR: "bin" + +tasks: + default: + summary: Shows available tasks + cmds: + - task --list + + build: + summary: Builds the application in server mode + desc: | + Builds the application with the server build tag enabled. + Server mode runs as a pure HTTP server without native GUI dependencies. + deps: + - task: build:frontend + cmds: + - go build -tags server -o {{.BIN_DIR}}/{{.APP_NAME}} . + + build:frontend: + summary: Ensures frontend assets exist (static HTML, no build needed) + dir: frontend + sources: + - dist/index.html + preconditions: + - sh: test -f dist/index.html + msg: "frontend/dist/index.html not found" + + run: + summary: Builds and runs the application in server mode + deps: + - task: build + cmds: + - ./{{.BIN_DIR}}/{{.APP_NAME}} + + dev: + summary: Runs the application in development mode (no build step) + desc: | + Runs the application directly with `go run` for rapid development. + Changes require restarting the command. + cmds: + - go run -tags server . + + clean: + summary: Removes build artifacts + cmds: + - rm -rf {{.BIN_DIR}} + + build:docker: + summary: Builds a Docker image for deployment + desc: | + Builds from v3 root to include local server mode code. + After server mode is published, can build from example dir directly. + dir: ../.. + cmds: + - docker build -t {{.TAG | default "server-example:latest"}} -f examples/server/Dockerfile . + vars: + TAG: '{{.TAG}}' + preconditions: + - sh: docker info > /dev/null 2>&1 + msg: "Docker is required. Please install Docker first." + + run:docker: + summary: Builds and runs the Docker image + deps: + - task: build:docker + cmds: + - docker run --rm -p {{.PORT | default "8080"}}:8080 {{.TAG | default "server-example:latest"}} + vars: + TAG: '{{.TAG}}' + PORT: '{{.PORT}}' + + test: + summary: Runs the server mode tests + dir: ../../pkg/application + cmds: + - go test -tags server -v -run TestServerMode . diff --git a/v3/examples/server/frontend/dist/index.html b/v3/examples/server/frontend/dist/index.html new file mode 100644 index 000000000..207a89bad --- /dev/null +++ b/v3/examples/server/frontend/dist/index.html @@ -0,0 +1,187 @@ + + + + + + Wails Headless Example + + + + +
        +

        Wails Headless

        +

        Running without a native window

        + Server Mode + +
        + + +
        + +
        Enter a name and click Greet
        + +
        + +
        + +
        + Events: +
        +
        + +

        + This Wails application is running in headless mode.
        + It serves content via HTTP without requiring a native GUI. +

        +
        + + + + diff --git a/v3/examples/server/go.mod b/v3/examples/server/go.mod new file mode 100644 index 000000000..ba596d27a --- /dev/null +++ b/v3/examples/server/go.mod @@ -0,0 +1,50 @@ +module github.com/wailsapp/wails/v3/examples/server + +go 1.25 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.62 + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-git/go-git/v5 v5.16.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/lmittmann/tint v1.1.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.52.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/wailsapp/go-webview2 v1.0.23 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +// For local development - remove this line before publishing +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/server/go.sum b/v3/examples/server/go.sum new file mode 100644 index 000000000..56f1153ea --- /dev/null +++ b/v3/examples/server/go.sum @@ -0,0 +1,147 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= +github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/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-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/examples/server/main.go b/v3/examples/server/main.go new file mode 100644 index 000000000..5b9bbdbae --- /dev/null +++ b/v3/examples/server/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "embed" + "log" + "log/slog" + "os" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +// GreetService is a simple service that provides greeting functionality. +type GreetService struct{} + +// Greet returns a greeting message. +func (g *GreetService) Greet(name string) string { + if name == "" { + name = "World" + } + return "Hello, " + name + "!" +} + +func main() { + // Create a logger for better visibility + logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelInfo, + })) + + app := application.New(application.Options{ + Name: "Server Mode Example", + Description: "A Wails application running in server mode", + Logger: logger, + LogLevel: slog.LevelInfo, + + // Server mode is enabled by building with -tags server + // Host/port can be overridden via WAILS_SERVER_HOST and WAILS_SERVER_PORT env vars + Server: application.ServerOptions{ + Host: "localhost", + Port: 8080, + }, + + // Register services (bindings work the same as desktop mode) + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + + // Serve frontend assets + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + }) + + log.Println("Starting Wails application in server mode...") + log.Println("Access at: http://localhost:8080") + log.Println("Health check: http://localhost:8080/health") + log.Println("Press Ctrl+C to stop") + + // Listen for broadcast events from browsers + app.Event.On("broadcast", func(event *application.CustomEvent) { + log.Printf("Received broadcast from %s: %v\n", event.Sender, event.Data) + }) + + // Emit periodic events to test WebSocket broadcasting + go func() { + time.Sleep(2 * time.Second) // Wait for server to start + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + for { + <-ticker.C + app.Event.Emit("server-tick", time.Now().Format(time.RFC3339)) + log.Println("Emitted server-tick event") + } + }() + + if err := app.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/index.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/index.js new file mode 100644 index 000000000..cb6c1ff84 --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Hashes} Hashes + */ diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/models.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/models.js new file mode 100644 index 000000000..a48737a6b --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Create as $Create} from "/wails/runtime.js"; + +/** + * @typedef {Object} Hashes + * @property {string} md5 + * @property {string} sha1 + * @property {string} sha256 + */ diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/service.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/service.js new file mode 100644 index 000000000..f5c01b306 --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/service.js @@ -0,0 +1,19 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create} from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {string} s + * @returns {$CancellablePromise<$models.Hashes>} + */ +export function Generate(s) { + return $Call.ByID(1123907498, s); +} diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/index.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/index.js new file mode 100644 index 000000000..d2bf43c94 --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/keyvaluestore.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/keyvaluestore.js new file mode 100644 index 000000000..e69de29bb diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/service.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/service.js new file mode 100644 index 000000000..b4285097c --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/service.js @@ -0,0 +1,69 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, Create as $Create} from "/wails/runtime.js"; + +/** + * Clear deletes all keys from the store. If AutoSave is true, the store is saved to disk. + * @returns {Promise & { cancel(): void }} + */ +export function Clear() { + let $resultPromise = /** @type {any} */($Call.ByID(816318109)); + return $resultPromise; +} + +/** + * Delete deletes the given key from the store. If AutoSave is true, the store is saved to disk. + * @param {string} key + * @returns {Promise & { cancel(): void }} + */ +export function Delete(key) { + let $resultPromise = /** @type {any} */($Call.ByID(2889946731, key)); + return $resultPromise; +} + +/** + * Get returns the value for the given key. If key is empty, the entire store is returned. + * @param {string} key + * @returns {Promise & { cancel(): void }} + */ +export function Get(key) { + let $resultPromise = /** @type {any} */($Call.ByID(376909388, key)); + return $resultPromise; +} + +/** + * Load loads the store from disk. + * If the store is in-memory, i.e. not associated with a file, Load has no effect. + * If the operation fails, a non-nil error is returned + * and the store's content and state at call time are preserved. + * @returns {Promise & { cancel(): void }} + */ +export function Load() { + let $resultPromise = /** @type {any} */($Call.ByID(1850778156)); + return $resultPromise; +} + +/** + * Save saves the store to disk. + * If the store is in-memory, i.e. not associated with a file, Save has no effect. + * @returns {Promise & { cancel(): void }} + */ +export function Save() { + let $resultPromise = /** @type {any} */($Call.ByID(3572737965)); + return $resultPromise; +} + +/** + * Set sets the value for the given key. If AutoSave is true, the store is saved to disk. + * @param {string} key + * @param {any} value + * @returns {Promise & { cancel(): void }} + */ +export function Set(key, value) { + let $resultPromise = /** @type {any} */($Call.ByID(2491766752, key, value)); + return $resultPromise; +} diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/index.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/index.js new file mode 100644 index 000000000..564a31eeb --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Level +} from "./models.js"; diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/models.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/models.js new file mode 100644 index 000000000..d8579b51a --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/models.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Create as $Create} from "/wails/runtime.js"; + +/** + * A Level is the importance or severity of a log event. + * The higher the level, the more important or severe the event. + * + * Values are arbitrary, but there are four predefined ones. + * @typedef {number} Level + */ + +/** + * Predefined constants for type Level. + * @namespace + */ +export const Level = { + Debug: -4, + Info: 0, + Warning: 4, + Error: 8, +}; diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/service.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/service.js new file mode 100644 index 000000000..e5128d9bc --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/service.js @@ -0,0 +1,106 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, Create as $Create} from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * DebugContext logs at level [Debug]. + * @param {string} message + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ +function DebugContext(message, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(1481024990, message, args)); + return $resultPromise; +} + +/** + * ErrorContext logs at level [Error]. + * @param {string} message + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ +function ErrorContext(message, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(4028761057, message, args)); + return $resultPromise; +} + +/** + * InfoContext logs at level [Info]. + * @param {string} message + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ +function InfoContext(message, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(1400061359, message, args)); + return $resultPromise; +} + +/** + * Log emits a log record with the current time and the given level and message. + * The Record's attributes consist of the Logger's attributes followed by + * the attributes specified by args. + * + * The attribute arguments are processed as follows: + * - If an argument is a string and this is not the last argument, + * the following argument is treated as the value and the two are combined + * into an attribute. + * - Otherwise, the argument is treated as a value with key "!BADKEY". + * + * Log feeds the binding call context into the configured logger, + * so custom handlers may access context values, e.g. the current window. + * @param {$models.Level} level + * @param {string} message + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ +export function Log(level, message, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(4156699346, level, message, args)); + return $resultPromise; +} + +/** + * LogLevel returns the currently configured log level, + * that is either the one configured initially + * or the last value passed to [Service.SetLogLevel]. + * @returns {Promise<$models.Level> & { cancel(): void }} + */ +export function LogLevel() { + let $resultPromise = /** @type {any} */($Call.ByID(4058368160)); + return $resultPromise; +} + +/** + * SetLogLevel changes the current log level. + * @param {$models.Level} level + * @returns {Promise & { cancel(): void }} + */ +export function SetLogLevel(level) { + let $resultPromise = /** @type {any} */($Call.ByID(3988219088, level)); + return $resultPromise; +} + +/** + * WarningContext logs at level [Warn]. + * @param {string} message + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ +function WarningContext(message, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(52282975, message, args)); + return $resultPromise; +} + +export { + DebugContext as Debug, + InfoContext as Info, + WarningContext as Warning, + ErrorContext as Error, +}; diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/index.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/index.js new file mode 100644 index 000000000..0918a51f0 --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/index.js @@ -0,0 +1,22 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +import * as $models from "./models.js"; + +/** + * Row holds a single row in the result of a query. + * It is a key-value map where keys are column names. + * @typedef {$models.Row} Row + */ + +/** + * Rows holds the result of a query + * as an array of key-value maps where keys are column names. + * @typedef {$models.Rows} Rows + */ diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/models.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/models.js new file mode 100644 index 000000000..041151c86 --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/models.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Create as $Create} from "/wails/runtime.js"; + +/** + * Row holds a single row in the result of a query. + * It is a key-value map where keys are column names. + * @typedef {{ [_: string]: any }} Row + */ + +/** + * Rows holds the result of a query + * as an array of key-value maps where keys are column names. + * @typedef {Row[]} Rows + */ + +/** + * Stmt wraps a prepared sql statement pointer. + * It provides the same methods as the [sql.Stmt] type. + * @typedef {string} Stmt + */ diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/service.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/service.js new file mode 100644 index 000000000..9330c1e4d --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/service.js @@ -0,0 +1,167 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, Create as $Create} from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Close closes the current database connection if one is open, otherwise has no effect. + * Additionally, Close closes all open prepared statements associated to the connection. + * + * Even when a non-nil error is returned, + * the database service is left in a consistent state, + * ready for a call to [Service.Open]. + * @returns {Promise & { cancel(): void }} + */ +export function Close() { + let $resultPromise = /** @type {any} */($Call.ByID(1888105376)); + return $resultPromise; +} + +/** + * ClosePrepared closes a prepared statement + * obtained with [Service.Prepare] or [Service.PrepareContext]. + * ClosePrepared is idempotent: + * it has no effect on prepared statements that are already closed. + * @param {$models.Stmt | null} stmt + * @returns {Promise & { cancel(): void }} + */ +function ClosePrepared(stmt) { + let $resultPromise = /** @type {any} */($Call.ByID(2526200629, stmt)); + return $resultPromise; +} + +/** + * ExecContext executes a query without returning any rows. + * It supports early cancellation. + * @param {string} query + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ +function ExecContext(query, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(674944556, query, args)); + return $resultPromise; +} + +/** + * ExecPrepared executes a prepared statement + * obtained with [Service.Prepare] or [Service.PrepareContext] + * without returning any rows. + * It supports early cancellation. + * @param {$models.Stmt | null} stmt + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ +function ExecPrepared(stmt, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(2086877656, stmt, args)); + return $resultPromise; +} + +/** + * Open validates the current configuration, + * closes the current connection if one is present, + * then opens and validates a new connection. + * + * Even when a non-nil error is returned, + * the database service is left in a consistent state, + * ready for a new call to Open. + * @returns {Promise & { cancel(): void }} + */ +export function Open() { + let $resultPromise = /** @type {any} */($Call.ByID(2012175612)); + return $resultPromise; +} + +/** + * PrepareContext creates a prepared statement for later queries or executions. + * Multiple queries or executions may be run concurrently from the returned statement. + * + * The caller must call the statement's Close method when it is no longer needed. + * Statements are closed automatically + * when the connection they are associated with is closed. + * + * PrepareContext supports early cancellation. + * @param {string} query + * @returns {Promise<$models.Stmt | null> & { cancel(): void }} + */ +function PrepareContext(query) { + let $resultPromise = /** @type {any} */($Call.ByID(570941694, query)); + return $resultPromise; +} + +/** + * QueryContext executes a query and returns a slice of key-value records, + * one per row, with column names as keys. + * It supports early cancellation, returning the slice of results fetched so far. + * @param {string} query + * @param {any[]} args + * @returns {Promise<$models.Rows> & { cancel(): void }} + */ +function QueryContext(query, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(4115542347, query, args)); + let $typingPromise = /** @type {any} */($resultPromise.then(($result) => { + return $$createType1($result); + })); + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * QueryPrepared executes a prepared statement + * obtained with [Service.Prepare] or [Service.PrepareContext] + * and returns a slice of key-value records, one per row, with column names as keys. + * It supports early cancellation, returning the slice of results fetched so far. + * @param {$models.Stmt | null} stmt + * @param {any[]} args + * @returns {Promise<$models.Rows> & { cancel(): void }} + */ +function QueryPrepared(stmt, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(3885083725, stmt, args)); + let $typingPromise = /** @type {any} */($resultPromise.then(($result) => { + return $$createType1($result); + })); + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +// Private type creation functions +const $$createType0 = $Create.Map($Create.Any, $Create.Any); +const $$createType1 = $Create.Array($$createType0); + +export { + ExecContext as Execute, + QueryContext as Query +}; + +import { Stmt } from "./stmt.js"; + +/** + * Prepare creates a prepared statement for later queries or executions. + * Multiple queries or executions may be run concurrently from the returned statement. + * + * The caller must call the statement's Close method when it is no longer needed. + * Statements are closed automatically + * when the connection they are associated with is closed. + * + * Prepare supports early cancellation. + * + * @param {string} query + * @returns {Promise & { cancel(): void }} + */ +export function Prepare(query) { + const promise = PrepareContext(query); + const wrapper = /** @type {any} */(promise.then(function (id) { + return id == null ? null : new Stmt( + ClosePrepared.bind(null, id), + ExecPrepared.bind(null, id), + QueryPrepared.bind(null, id)); + })); + wrapper.cancel = promise.cancel; + return wrapper; +} diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/stmt.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/stmt.js new file mode 100644 index 000000000..948b0c3dd --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/stmt.js @@ -0,0 +1,79 @@ +//@ts-check + +//@ts-ignore: Unused imports +import * as $models from "./models.js"; + +const execSymbol = Symbol("exec"), + querySymbol = Symbol("query"), + closeSymbol = Symbol("close"); + +/** + * Stmt represents a prepared statement for later queries or executions. + * Multiple queries or executions may be run concurrently on the same statement. + * + * The caller must call the statement's Close method when it is no longer needed. + * Statements are closed automatically + * when the connection they are associated with is closed. + */ +export class Stmt { + /** + * Constructs a new prepared statement instance. + * @param {(...args: any[]) => Promise} close + * @param {(...args: any[]) => Promise & { cancel(): void }} exec + * @param {(...args: any[]) => Promise<$models.Rows> & { cancel(): void }} query + */ + constructor(close, exec, query) { + /** + * @member + * @private + * @type {typeof close} + */ + this[closeSymbol] = close; + + /** + * @member + * @private + * @type {typeof exec} + */ + this[execSymbol] = exec; + + /** + * @member + * @private + * @type {typeof query} + */ + this[querySymbol] = query; + } + + /** + * Closes the prepared statement. + * It has no effect when the statement is already closed. + * @returns {Promise} + */ + Close() { + return this[closeSymbol](); + } + + /** + * Executes the prepared statement without returning any rows. + * It supports early cancellation. + * + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ + Exec(...args) { + return this[execSymbol](...args); + } + + /** + * Executes the prepared statement + * and returns a slice of key-value records, one per row, with column names as keys. + * It supports early cancellation, returning the array of results fetched so far. + * + * @param {any[]} args + * @returns {Promise<$models.Rows> & { cancel(): void }} + */ + Query(...args) { + return this[querySymbol](...args); + } +} diff --git a/v3/examples/services/assets/index.html b/v3/examples/services/assets/index.html new file mode 100644 index 000000000..c44a0679a --- /dev/null +++ b/v3/examples/services/assets/index.html @@ -0,0 +1,287 @@ + + + + + Wails Alpha + + + + + + +

        Services

        +
        +
        + + + + +
        +
        +

        The sqlite service provides easy integration with sqlite dbs.

        +

        The demo DB has a single table: Users.

        +

        Enter a query below and hit the "Run" button.

        +
        +
        +
        + +
        +
        +
        +
        +
        +
        +

        The hashes service provides hashing functions.

        +
        +
        +
        + +
        +
        +
        +
        +
        +
        +

        The kvstore service provides a means for reading and writing to a json file.

        +

        Enter a key/value pair in the form below to add it to the file.

        +

        A blank value will remove the key.

        +
        +
        + + + +
        +
        +
        +
        +
        +
        +

        The log plugin provides a means for sending frontend logs to a Go logger.

        +

        Enter some text below, press submit and check your console logs.

        +
        + + + +
        +
        +
        + + diff --git a/v3/examples/services/assets/style.css b/v3/examples/services/assets/style.css new file mode 100644 index 000000000..f128a1aa1 --- /dev/null +++ b/v3/examples/services/assets/style.css @@ -0,0 +1,213 @@ +html { + background-color: rgba(33, 37, 43); + text-align: center; + color: white; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -webkit-touch-callout: none; + height: 100vh; + width: 100%; +} + +body { + padding-top: 40px; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + overscroll-behavior: none; + overflow-y: hidden; + background-image: url("/files/images/eryri1.png"); + background-color: rgba(33, 37, 43, 0.85); + background-blend-mode: overlay; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + height: 100vh; + width: 100%; +} + +.logo { + width: 30%; + height: 20%; +} +/* CSS */ +.button-42 { + background-color: initial; + background-image: linear-gradient(-180deg, #555555, #2f2f2f); + border-radius: 6px; + box-shadow: rgba(0, 0, 0, 0.1) 0 2px 4px; + color: #FFFFFF; + cursor: pointer; + display: inline-block; + font-family: Inter,-apple-system,system-ui,Roboto,"Helvetica Neue",Arial,sans-serif; + height: 35px; + line-height: 35px; + outline: 0; + overflow: hidden; + padding: 0 20px; + pointer-events: auto; + position: relative; + text-align: center; + touch-action: manipulation; + user-select: none; + -webkit-user-select: none; + vertical-align: top; + white-space: nowrap; + z-index: 9; + border: 0; + transition: box-shadow .2s; + font-size: medium; +} +p { + font-size: 1rem; +} + +.button-42:hover { + background-image: linear-gradient(-180deg, #cb3939, #610000); +} +html { + background-color: rgba(33, 37, 43); + text-align: center; + color: white; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -webkit-touch-callout: none; +} + +body { + padding-top: 40px; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + overscroll-behavior: none; +} + +.button-42 { + background-color: initial; + background-image: linear-gradient(-180deg, #555555, #2f2f2f); + border-radius: 6px; + box-shadow: rgba(0, 0, 0, 0.1) 0 2px 4px; + color: #FFFFFF; + cursor: pointer; + display: inline-block; + font-family: Inter,-apple-system,system-ui,Roboto,"Helvetica Neue",Arial,sans-serif; + height: 35px; + line-height: 35px; + outline: 0; + overflow: hidden; + padding: 0 20px; + pointer-events: auto; + position: relative; + text-align: center; + touch-action: manipulation; + user-select: none; + -webkit-user-select: none; + vertical-align: top; + white-space: nowrap; + z-index: 9; + border: 0; + transition: box-shadow .2s; + font-size: medium; +} + +p { + font-size: 1rem; +} + +.button-42:hover { + background-image: linear-gradient(-180deg, #cb3939, #610000); +} + +.tab { + overflow: hidden; + background-color: #f1f1f100; + margin-left: 20px; +} + +.tab button { + background-color: transparent; /* Make the background transparent */ + color: white; /* Make the text white */ + float: left; + border: none; + outline: none; + cursor: pointer; + padding: 14px 16px; + transition: 0.3s; + position: relative; /* Added for the underline */ +} + +.tab button::after { /* Added for the underline */ + content: ''; + position: absolute; + width: 0; + height: 2px; + bottom: 0; + left: 0; + background-color: #a40505; + visibility: hidden; + transition: all 0.3s ease-in-out; +} + +.tab button:hover::after, /* Added for the underline */ +.tab button.active::after { /* Added for the underline */ + width: 100%; + visibility: visible; +} + +.tab button.active { + background-color: transparent; /* Make the background transparent */ + color: red; +} +.tabcontent { + display: none; + padding: 6px 12px; + border-top: none; +} +#sqlresults, #hashresults { + font-family: 'Courier New', Courier, monospace; + min-height: 100px; +} +#selectquery { + width: 90%; +} +table { + width: 100%; + border-collapse: collapse; + margin-top: 20px; +} + +th, td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; +} + +th { + background-color: #005467; + color: white; +} + +tr:hover { + background-color: #888; +} + +.error-message { + color: #FF4B2B; /* Bright red color for better visibility in dark mode */ + background-color: #1E1E1E; /* Dark background for the error message */ + border: 1px solid #FF4B2B; /* Border color same as text color */ + padding: 10px; /* Padding around the text */ + margin: 10px 0; /* Margin top and bottom */ + border-radius: 5px; /* Rounded corners */ + text-align: center; /* Center the text */ +} + +.narrowColumn { + width: 1%; /* Adjust as needed */ + white-space: nowrap; +} \ No newline at end of file diff --git a/v3/examples/services/files/images/eryri1.png b/v3/examples/services/files/images/eryri1.png new file mode 100644 index 000000000..224d3b4ac Binary files /dev/null and b/v3/examples/services/files/images/eryri1.png differ diff --git a/v3/examples/services/hashes/hashes.go b/v3/examples/services/hashes/hashes.go new file mode 100644 index 000000000..1e4653dbd --- /dev/null +++ b/v3/examples/services/hashes/hashes.go @@ -0,0 +1,45 @@ +package hashes + +import ( + "context" + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "encoding/hex" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Hashes = struct { + MD5 string `json:"md5"` + SHA1 string `json:"sha1"` + SHA256 string `json:"sha256"` +} + +type Service struct{} + +func (h *Service) Generate(s string) Hashes { + md5Hash := md5.Sum([]byte(s)) + sha1Hash := sha1.Sum([]byte(s)) + sha256Hash := sha256.Sum256([]byte(s)) + + return Hashes{ + MD5: hex.EncodeToString(md5Hash[:]), + SHA1: hex.EncodeToString(sha1Hash[:]), + SHA256: hex.EncodeToString(sha256Hash[:]), + } +} + +func New() *Service { + return &Service{} +} + +func (h *Service) ServiceName() string { + return "Hashes Service" +} + +func (h *Service) ServiceStartup(context.Context, application.ServiceOptions) error { + return nil +} + +func (h *Service) ServiceShutdown() error { return nil } diff --git a/v3/examples/services/main.go b/v3/examples/services/main.go new file mode 100644 index 000000000..c753a79c7 --- /dev/null +++ b/v3/examples/services/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "embed" + "log/slog" + "os" + "path/filepath" + "runtime" + + "github.com/wailsapp/wails/v3/examples/services/hashes" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/services/fileserver" + "github.com/wailsapp/wails/v3/pkg/services/kvstore" + "github.com/wailsapp/wails/v3/pkg/services/log" + "github.com/wailsapp/wails/v3/pkg/services/sqlite" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + + // Get the local directory of this source file + // This isn't needed when running the example with `go run .` + // but is needed when running the example from an IDE + _, thisFile, _, _ := runtime.Caller(0) + localDir := filepath.Dir(thisFile) + + rootPath := filepath.Join(localDir, "files") + app := application.New(application.Options{ + Name: "Services Demo", + Description: "A demo of the services API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + LogLevel: slog.LevelDebug, + Services: []application.Service{ + application.NewService(hashes.New()), + application.NewService(sqlite.NewWithConfig(&sqlite.Config{ + DBSource: "test.db", + })), + application.NewService(kvstore.NewWithConfig(&kvstore.Config{ + Filename: "store.json", + AutoSave: true, + })), + application.NewService(log.New()), + application.NewServiceWithOptions(fileserver.NewWithConfig(&fileserver.Config{ + RootPath: rootPath, + }), application.ServiceOptions{ + Route: "/files", + }), + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Width: 1024, + Height: 768, + }) + + err := app.Run() + + if err != nil { + println(err.Error()) + os.Exit(1) + } +} diff --git a/v3/examples/services/store.json b/v3/examples/services/store.json new file mode 100644 index 000000000..a56bb9842 --- /dev/null +++ b/v3/examples/services/store.json @@ -0,0 +1 @@ +{"q":"www","url2":"https://reddit.com"} \ No newline at end of file diff --git a/v3/examples/services/test.db b/v3/examples/services/test.db new file mode 100644 index 000000000..ced6a916c Binary files /dev/null and b/v3/examples/services/test.db differ diff --git a/v3/examples/show-macos-toolbar/README.md b/v3/examples/show-macos-toolbar/README.md new file mode 100644 index 000000000..21bbeac96 --- /dev/null +++ b/v3/examples/show-macos-toolbar/README.md @@ -0,0 +1,20 @@ +# Show macOS Toolbar Example + +This example is a demonstration of the macOS option `ShowToolbarWhenFullscreen`, which keeps +the system toolbar visible when in fullscreen mode. + +## Running the example + +To run the example (on macOS), simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | N/A | +| Linux | N/A | diff --git a/v3/examples/show-macos-toolbar/main.go b/v3/examples/show-macos-toolbar/main.go new file mode 100644 index 000000000..648432da6 --- /dev/null +++ b/v3/examples/show-macos-toolbar/main.go @@ -0,0 +1,51 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Show macOS Toolbar", + Description: "A demo of the ShowToolbarWhenFullscreen option", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create window + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Toolbar hidden (default behaviour)", + HTML: "

        Switch this window to fullscreen: the toolbar will be hidden

        ", + CSS: `body { background-color: blue; color: white; height: 100vh; display: flex; justify-content: center; align-items: center; }`, + Mac: application.MacWindow{ + TitleBar: application.MacTitleBar{ + UseToolbar: true, + HideToolbarSeparator: true, + }, + }, + }) + + // Create window + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Toolbar visible", + HTML: "

        Switch this window to fullscreen: the toolbar will stay visible

        ", + CSS: `body { background-color: red; color: white; height: 100vh; display: flex; justify-content: center; align-items: center; }`, + Mac: application.MacWindow{ + TitleBar: application.MacTitleBar{ + UseToolbar: true, + HideToolbarSeparator: true, + ShowToolbarWhenFullscreen: true, + }, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/single-instance/README.md b/v3/examples/single-instance/README.md new file mode 100644 index 000000000..5982cb909 --- /dev/null +++ b/v3/examples/single-instance/README.md @@ -0,0 +1,62 @@ +# Single Instance Example + +This example demonstrates the single instance functionality in Wails v3. It shows how to: + +1. Ensure only one instance of your application can run at a time +2. Notify the first instance when a second instance is launched +3. Pass data between instances +4. Handle command line arguments and working directory information from second instances + +## Running the Example + +1. Build and run the application: + ```bash + go build + ./single-instance + ``` + +2. Try launching a second instance of the application. You'll notice: + - The second instance will exit immediately + - The first instance will receive and display: + - Command line arguments from the second instance + - Working directory of the second instance + - Additional data passed from the second instance + +3. Check the application logs to see the information received from second instances. + +## Features Demonstrated + +- Setting up single instance lock with a unique identifier +- Handling second instance launches through callbacks +- Passing custom data between instances +- Displaying instance information in a web UI +- Cross-platform support (Windows, macOS, Linux) + +## Code Overview + +The example consists of: + +- `main.go`: The main application code demonstrating single instance setup +- A simple web UI showing current instance information +- Callback handling for second instance launches + +## Implementation Details + +The application uses the Wails v3 single instance feature: + +```go +app := application.New(&application.Options{ + SingleInstance: &application.SingleInstanceOptions{ + UniqueID: "com.wails.example.single-instance", + OnSecondInstance: func(data application.SecondInstanceData) { + // Handle second instance launch + }, + AdditionalData: map[string]string{ + }, + }, +}) +``` + +The implementation uses platform-specific mechanisms: +- Windows: Named mutex and window messages +- Unix (Linux/macOS): File locking with flock and signals diff --git a/v3/examples/single-instance/assets/index.html b/v3/examples/single-instance/assets/index.html new file mode 100644 index 000000000..330b062ec --- /dev/null +++ b/v3/examples/single-instance/assets/index.html @@ -0,0 +1,141 @@ + + + + + + Single Instance Demo + + + + +
        +

        Single Instance Demo

        + +
        +

        Current Instance Information

        +
        Loading...
        +
        + +
        +

        Instructions

        +

        Try launching another instance of this application. The first instance will:

        +
          +
        • Receive notification of the second instance launch
        • +
        • Get the command line arguments of the second instance
        • +
        • Get the working directory of the second instance
        • +
        • Receive any additional data passed from the second instance
        • +
        +

        Check the application logs to see the information received from second instances.

        +
        +
        + + + + \ No newline at end of file diff --git a/v3/examples/single-instance/main.go b/v3/examples/single-instance/main.go new file mode 100644 index 000000000..51ca48a38 --- /dev/null +++ b/v3/examples/single-instance/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "embed" + "log" + "log/slog" + "os" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/index.html +var assets embed.FS + +type App struct{} + +func (a *App) GetCurrentInstanceInfo() map[string]interface{} { + return map[string]interface{}{ + "args": os.Args, + "workingDir": getCurrentWorkingDir(), + } +} + +var encryptionKey = [32]byte{ + 0x1e, 0x1f, 0x1c, 0x1d, 0x1a, 0x1b, 0x18, 0x19, + 0x16, 0x17, 0x14, 0x15, 0x12, 0x13, 0x10, 0x11, + 0x0e, 0x0f, 0x0c, 0x0d, 0x0a, 0x0b, 0x08, 0x09, + 0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00, 0x01, +} + +func main() { + + var window *application.WebviewWindow + app := application.New(application.Options{ + Name: "Single Instance Example", + LogLevel: slog.LevelDebug, + Description: "An example of single instance functionality in Wails v3", + Services: []application.Service{ + application.NewService(&App{}), + }, + SingleInstance: &application.SingleInstanceOptions{ + UniqueID: "com.wails.example.single-instance", + EncryptionKey: encryptionKey, + OnSecondInstanceLaunch: func(data application.SecondInstanceData) { + if window != nil { + window.EmitEvent("secondInstanceLaunched", data) + window.Restore() + window.Focus() + } + log.Printf("Second instance launched with args: %v\n", data.Args) + log.Printf("Working directory: %s\n", data.WorkingDir) + if data.AdditionalData != nil { + log.Printf("Additional data: %v\n", data.AdditionalData) + } + }, + AdditionalData: map[string]string{ + "launchtime": time.Now().Local().String(), + }, + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + }) + + window = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Single Instance Demo", + Width: 800, + Height: 700, + URL: "/", + }) + + app.Run() +} + +func getCurrentWorkingDir() string { + dir, err := os.Getwd() + if err != nil { + return "" + } + return dir +} diff --git a/v3/examples/spotlight/README.md b/v3/examples/spotlight/README.md new file mode 100644 index 000000000..bda8f464e --- /dev/null +++ b/v3/examples/spotlight/README.md @@ -0,0 +1,66 @@ +# Spotlight Example + +This example demonstrates how to create a Spotlight-like launcher window using the `CollectionBehavior` option on macOS. + +## Features + +- **Appears on all Spaces**: Using `MacWindowCollectionBehaviorCanJoinAllSpaces`, the window is visible across all virtual desktops +- **Overlays fullscreen apps**: Using `MacWindowCollectionBehaviorFullScreenAuxiliary`, the window can appear over fullscreen applications +- **Combined behaviors**: Demonstrates combining multiple behaviors with bitwise OR +- **Floating window**: `MacWindowLevelFloating` keeps the window above other windows +- **Accessory app**: Doesn't appear in the Dock (uses `ActivationPolicyAccessory`) +- **Frameless design**: Clean, borderless appearance with translucent backdrop + +## Running the example + +```bash +go run . +``` + +**Note**: This example is macOS-specific due to the use of `CollectionBehavior`. + +## Combining CollectionBehaviors + +Behaviors can be combined using bitwise OR (`|`): + +```go +CollectionBehavior: application.MacWindowCollectionBehaviorCanJoinAllSpaces | + application.MacWindowCollectionBehaviorFullScreenAuxiliary, +``` + +## CollectionBehavior Options + +These are bitmask values that can be combined: + +**Space behavior:** +| Option | Description | +|--------|-------------| +| `MacWindowCollectionBehaviorDefault` | Uses FullScreenPrimary (default) | +| `MacWindowCollectionBehaviorCanJoinAllSpaces` | Window appears on all Spaces | +| `MacWindowCollectionBehaviorMoveToActiveSpace` | Moves to active Space when shown | +| `MacWindowCollectionBehaviorManaged` | Default managed window behavior | +| `MacWindowCollectionBehaviorTransient` | Temporary/transient window | +| `MacWindowCollectionBehaviorStationary` | Stays stationary during Space switches | + +**Fullscreen behavior:** +| Option | Description | +|--------|-------------| +| `MacWindowCollectionBehaviorFullScreenPrimary` | Can enter fullscreen mode | +| `MacWindowCollectionBehaviorFullScreenAuxiliary` | Can overlay fullscreen apps | +| `MacWindowCollectionBehaviorFullScreenNone` | Disables fullscreen | +| `MacWindowCollectionBehaviorFullScreenAllowsTiling` | Allows side-by-side tiling | + +## Use Cases + +- **Launcher apps** (like Spotlight, Alfred, Raycast) +- **Quick capture tools** (notes, screenshots) +- **System utilities** that need to be accessible anywhere +- **Overlay widgets** that should appear over fullscreen apps + +## Status + +| Platform | Status | +|----------|--------| +| Mac | Working | +| Windows | N/A (macOS-specific feature) | +| Linux | N/A (macOS-specific feature) | diff --git a/v3/examples/spotlight/main.go b/v3/examples/spotlight/main.go new file mode 100644 index 000000000..d961f1f54 --- /dev/null +++ b/v3/examples/spotlight/main.go @@ -0,0 +1,142 @@ +package main + +import ( + "log" + "net/http" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// This example demonstrates how to create a Spotlight-like launcher window +// that appears on all macOS Spaces and can overlay fullscreen applications. +// +// Key features: +// - Window appears on all Spaces (virtual desktops) +// - Can overlay fullscreen applications +// - Floating window level keeps it above other windows +// - Accessory activation policy hides from Dock +// - Frameless design with translucent backdrop + +func main() { + app := application.New(application.Options{ + Name: "Spotlight Example", + Description: "A Spotlight-like launcher demonstrating CollectionBehavior", + Mac: application.MacOptions{ + // Accessory apps don't appear in the Dock + ActivationPolicy: application.ActivationPolicyAccessory, + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(spotlightHTML)) + }), + }, + }) + + // Create a Spotlight-like window + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Spotlight", + Width: 680, + Height: 80, + Frameless: true, + // Center the window + InitialPosition: application.WindowCentered, + // Prevent resizing + DisableResize: true, + Mac: application.MacWindow{ + // Combine multiple behaviors using bitwise OR: + // - CanJoinAllSpaces: window appears on ALL Spaces (virtual desktops) + // - FullScreenAuxiliary: window can overlay fullscreen applications + CollectionBehavior: application.MacWindowCollectionBehaviorCanJoinAllSpaces | + application.MacWindowCollectionBehaviorFullScreenAuxiliary, + // Float above other windows + WindowLevel: application.MacWindowLevelFloating, + // Translucent vibrancy effect + Backdrop: application.MacBackdropTranslucent, + // Hidden title bar for clean look + TitleBar: application.MacTitleBar{ + AppearsTransparent: true, + Hide: true, + }, + }, + URL: "/", + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} + +const spotlightHTML = ` + + + Spotlight + + + + +
        + + + + + +
        + +` diff --git a/v3/examples/systray-basic/README.md b/v3/examples/systray-basic/README.md new file mode 100644 index 000000000..11b84a0bf --- /dev/null +++ b/v3/examples/systray-basic/README.md @@ -0,0 +1,16 @@ +# Systray Basic Example + +This example creates a simple system tray with an attached window. +The window is hidden by default and toggled by left-clicking on the systray icon. +The window will hide automatically when it loses focus. + +On Windows, if the icon is in the notification flyout, +then the window will be shown in the bottom right corner. + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/systray-basic/main.go b/v3/examples/systray-basic/main.go new file mode 100644 index 000000000..026758a02 --- /dev/null +++ b/v3/examples/systray-basic/main.go @@ -0,0 +1,61 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/events" + "log" + "runtime" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/icons" +) + +func main() { + app := application.New(application.Options{ + Name: "Systray Demo", + Description: "A demo of the Systray API", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ActivationPolicy: application.ActivationPolicyAccessory, + }, + }) + + systemTray := app.SystemTray.New() + + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Width: 500, + Height: 500, + Name: "Systray Demo Window", + Frameless: true, + AlwaysOnTop: true, + Hidden: true, + DisableResize: true, + Windows: application.WindowsWindow{ + HiddenOnTaskbar: true, + }, + KeyBindings: map[string]func(window application.Window){ + "F12": func(window application.Window) { + systemTray.OpenMenu() + }, + }, + }) + + // Register a hook to hide the window when the window is closing + window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + // Hide the window + window.Hide() + // Cancel the event so it doesn't get destroyed + e.Cancel() + }) + + if runtime.GOOS == "darwin" { + systemTray.SetTemplateIcon(icons.SystrayMacTemplate) + } + + systemTray.AttachWindow(window).WindowOffset(5) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/systray-clock/main.go b/v3/examples/systray-clock/main.go new file mode 100644 index 000000000..c00d6093d --- /dev/null +++ b/v3/examples/systray-clock/main.go @@ -0,0 +1,59 @@ +package main + +import ( + "log" + "runtime" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/icons" +) + +func main() { + app := application.New(application.Options{ + Name: "Systray Clock", + Description: "System tray clock with live tooltip updates", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ActivationPolicy: application.ActivationPolicyAccessory, + }, + Windows: application.WindowsOptions{ + DisableQuitOnLastWindowClosed: true, + }, + }) + + systemTray := app.SystemTray.New() + + // Use the template icon on macOS so the clock respects light/dark modes. + if runtime.GOOS == "darwin" { + systemTray.SetTemplateIcon(icons.SystrayMacTemplate) + } + + menu := app.NewMenu() + menu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + systemTray.SetMenu(menu) + + updateTooltip := func() { + systemTray.SetTooltip(time.Now().Format("15:04:05")) + } + updateTooltip() + + go func() { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + updateTooltip() + case <-app.Context().Done(): + return + } + } + }() + + if err := app.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/systray-custom/README.md b/v3/examples/systray-custom/README.md new file mode 100644 index 000000000..598d84496 --- /dev/null +++ b/v3/examples/systray-custom/README.md @@ -0,0 +1,16 @@ +# Systray Custom Example + +This example creates a simple system tray and uses hooks to attach a custom window. +The window is hidden by default and toggled by left-clicking on the systray icon. +The window will hide automatically when it loses focus. + +On Windows, if the icon is in the notification flyout, +then the window will be shown in the bottom right corner. + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | | +| Linux | | diff --git a/v3/examples/systray-custom/main.go b/v3/examples/systray-custom/main.go new file mode 100644 index 000000000..f10140792 --- /dev/null +++ b/v3/examples/systray-custom/main.go @@ -0,0 +1,73 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" + "github.com/wailsapp/wails/v3/pkg/icons" + "log" + "runtime" +) + +var windowShowing bool + +func createWindow(app *application.App) { + if windowShowing { + return + } + // Log the time taken to create the window + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Width: 500, + Height: 500, + Name: "Systray Demo Window", + AlwaysOnTop: true, + Hidden: true, + BackgroundColour: application.NewRGB(33, 37, 41), + DisableResize: true, + Windows: application.WindowsWindow{ + HiddenOnTaskbar: true, + }, + }) + windowShowing = true + + window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + windowShowing = false + }) + + window.Show() +} + +func main() { + app := application.New(application.Options{ + Name: "Systray Demo", + Description: "A demo of the Systray API", + Assets: application.AlphaAssets, + Windows: application.WindowsOptions{ + DisableQuitOnLastWindowClosed: true, + }, + Mac: application.MacOptions{ + ActivationPolicy: application.ActivationPolicyAccessory, + }, + }) + + systemTray := app.SystemTray.New() + menu := app.NewMenu() + menu.Add("Quit").OnClick(func(data *application.Context) { + app.Quit() + }) + systemTray.SetMenu(menu) + systemTray.SetTooltip("Systray Demo") + + if runtime.GOOS == "darwin" { + systemTray.SetTemplateIcon(icons.SystrayMacTemplate) + } + + systemTray.OnClick(func() { + createWindow(app) + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/systray-menu/README.md b/v3/examples/systray-menu/README.md new file mode 100644 index 000000000..2de57faea --- /dev/null +++ b/v3/examples/systray-menu/README.md @@ -0,0 +1,17 @@ +# Systray Menu Example + +This example creates a system tray with an attached window and a menu. +The window is hidden by default and toggled by left-clicking on the systray icon. +The window will hide automatically when it loses focus. +Right-clicking on the systray icon will show the menu. + +On Windows, if the icon is in the notification flyout, +then the window will be shown in the bottom right corner. + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/systray-menu/logo-dark-xsmall.png b/v3/examples/systray-menu/logo-dark-xsmall.png new file mode 100644 index 000000000..83a514c74 Binary files /dev/null and b/v3/examples/systray-menu/logo-dark-xsmall.png differ diff --git a/v3/examples/systray-menu/main.go b/v3/examples/systray-menu/main.go new file mode 100644 index 000000000..b342f0fe1 --- /dev/null +++ b/v3/examples/systray-menu/main.go @@ -0,0 +1,114 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/events" + "log" + "runtime" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/icons" +) + +//go:embed logo-dark-xsmall.png +var logo []byte + +func main() { + app := application.New(application.Options{ + Name: "Systray Demo", + Description: "A demo of the Systray API", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ActivationPolicy: application.ActivationPolicyAccessory, + }, + }) + + systemTray := app.SystemTray.New() + + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Width: 500, + Height: 500, + Name: "Systray Demo Window", + Frameless: true, + AlwaysOnTop: true, + Hidden: true, + DisableResize: true, + Windows: application.WindowsWindow{ + HiddenOnTaskbar: true, + }, + KeyBindings: map[string]func(window application.Window){ + "F12": func(window application.Window) { + systemTray.OpenMenu() + }, + }, + }) + + window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + window.Hide() + e.Cancel() + }) + + if runtime.GOOS == "darwin" { + systemTray.SetTemplateIcon(icons.SystrayMacTemplate) + } + + myMenu := app.NewMenu() + myMenu.Add("Wails").SetBitmap(logo).SetEnabled(false) + myMenu.Add("Hidden").SetHidden(true) + + myMenu.Add("Hello World!").OnClick(func(ctx *application.Context) { + println("Hello World!") + q := app.Dialog.Question().SetTitle("Ready?").SetMessage("Are you feeling ready?") + q.AddButton("Yes").OnClick(func() { + println("Awesome!") + }) + q.AddButton("No").SetAsDefault().OnClick(func() { + println("Boo!") + }) + q.Show() + }) + subMenu := myMenu.AddSubmenu("Submenu") + subMenu.Add("Click me!").OnClick(func(ctx *application.Context) { + ctx.ClickedMenuItem().SetLabel("Clicked!") + }) + myMenu.AddSeparator() + myMenu.AddCheckbox("Checked", true).OnClick(func(ctx *application.Context) { + println("Checked: ", ctx.ClickedMenuItem().Checked()) + app.Dialog.Info().SetTitle("Hello World!").SetMessage("Hello World!").Show() + }) + myMenu.Add("Enabled").OnClick(func(ctx *application.Context) { + println("Click me!") + ctx.ClickedMenuItem().SetLabel("Disabled!").SetEnabled(false) + }) + 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) + myMenu.AddSeparator() + myMenu.Add("Hide System tray for 3 seconds...").OnClick(func(ctx *application.Context) { + systemTray.Hide() + time.Sleep(3 * time.Second) + systemTray.Show() + }) + myMenu.AddSeparator() + myMenu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + + systemTray.SetMenu(myMenu) + + systemTray.AttachWindow(window).WindowOffset(2) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/video/README.md b/v3/examples/video/README.md new file mode 100644 index 000000000..012469afb --- /dev/null +++ b/v3/examples/video/README.md @@ -0,0 +1,19 @@ +# Video Example + +This example shows support for HTML5 video. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | | +| Windows | Working | +| Linux | | diff --git a/v3/examples/video/main.go b/v3/examples/video/main.go new file mode 100644 index 000000000..879172c61 --- /dev/null +++ b/v3/examples/video/main.go @@ -0,0 +1,47 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/events" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Video Demo", + Description: "A demo of HTML5 Video API", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Windows: application.WindowsOptions{ + WndProcInterceptor: nil, + DisableQuitOnLastWindowClosed: false, + WebviewUserDataPath: "", + WebviewBrowserPath: "", + }, + }) + app.Event.OnApplicationEvent(events.Mac.ApplicationDidFinishLaunching, func(event *application.ApplicationEvent) { + log.Println("ApplicationDidFinishLaunching") + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + BackgroundColour: application.NewRGB(33, 37, 41), + Mac: application.MacWindow{ + DisableShadow: true, + WebviewPreferences: application.MacWebviewPreferences{ + FullscreenEnabled: application.Enabled, + }, + }, + HTML: "", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/websocket-transport/GreetService.go b/v3/examples/websocket-transport/GreetService.go new file mode 100644 index 000000000..bdb21a277 --- /dev/null +++ b/v3/examples/websocket-transport/GreetService.go @@ -0,0 +1,73 @@ +package main + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is a service that demonstrates bound methods over WebSocket transport +type GreetService struct { + mu sync.Mutex + greetCount int + app *application.App +} + +// ServiceStartup is called when the service is initialized +func (g *GreetService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + g.app = application.Get() + + // Start a timer that emits events every second + // This demonstrates automatic event forwarding to WebSocket transport + go func() { + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case t := <-ticker.C: + // Emit a timer event - automatically forwarded to WebSocket! + g.app.Event.Emit("timer:tick", t.Format("15:04:05")) + } + } + }() + + return nil +} + +// Greet greets a person by name and emits an event +func (g *GreetService) Greet(name string) string { + g.mu.Lock() + g.greetCount++ + count := g.greetCount + g.mu.Unlock() + result := fmt.Sprintf("Hello, %s! (Greeted %d times via WebSocket)", name, count) + + // Emit an event to demonstrate event support over WebSocket + // Events are automatically forwarded to the WebSocket transport! + if g.app != nil { + g.app.Event.Emit("greet:count", count) + } + + return result +} + +// GetTime returns the current server time +func (g *GreetService) GetTime() string { + return time.Now().Format("2006-01-02 15:04:05") +} + +// Echo echoes back the input message +func (g *GreetService) Echo(message string) string { + return "Echo: " + message +} + +// Add adds two numbers together +func (g *GreetService) Add(a, b int) int { + return a + b +} diff --git a/v3/examples/websocket-transport/README.md b/v3/examples/websocket-transport/README.md new file mode 100644 index 000000000..d66170eb1 --- /dev/null +++ b/v3/examples/websocket-transport/README.md @@ -0,0 +1,113 @@ +# WebSocket Transport Example + +This example demonstrates how to use a custom transport like WebSocket for Wails IPC instead of the default HTTP fetch transport. All Wails bindings and features work identically - only the underlying transport layer changes. + +## What This Example Shows + +- How to configure a WebSocket transport on the backend using `NewWebSocketTransport()` +- How to override the runtime transport on the frontend using `setTransport()` and `createWebSocketTransport()` +- Full compatibility with generated bindings - no code generation changes needed +- Real-time connection status monitoring +- Automatic reconnection handling + +## Architecture + +```text +┌─────────────────────────────────────────┐ +│ Frontend (JavaScript) │ +│ - setTransport(wsTransport) │ +│ - All bindings work unchanged │ +└───────────────┬─────────────────────────┘ + │ + │ WebSocket (ws://localhost:9099) + │ +┌───────────────▼─────────────────────────┐ +│ Backend (Go) │ +│ - WebSocketTransport on port 9099 │ +│ - Standard MessageProcessor │ +│ - All Wails features available │ +└─────────────────────────────────────────┘ +``` + +## How to Run + +1. Navigate to this directory: + ```bash + cd v3/examples/websocket-transport + ``` + +2. Run the example: + ```bash + go run . + ``` + +3. The application will start with: + - WebView window displaying the UI + - WebSocket server listening on `ws://localhost:9099/wails/ws` + - Real-time connection status indicator + +## Backend Setup + +The backend configuration is simple - just pass a `WebSocketTransport` to the application options: + +```go +// Create WebSocket transport on port 9099 +wsTransport := NewWebSocketTransport(":9099") + +app := application.New(application.Options{ + Name: "WebSocket Transport Example", + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + // Use WebSocket transport instead of default HTTP + Transport: wsTransport, +}) +``` + +## Frontend Setup + +The frontend uses the WebSocket transport with **generated bindings**: + +```typescript +import { setTransport } from "/wails/runtime.js"; +import { createWebSocketTransport } from "/websocket-transport.js"; +import { GreetService } from "/bindings/github.com/wailsapp/wails/v3/examples/websocket-transport/index.js"; + +// Create and configure WebSocket transport +const wsTransport = createWebSocketTransport('ws://localhost:9099/wails/ws', { + reconnectDelay: 2000, // Reconnect after 2 seconds if disconnected + requestTimeout: 30000 // Request timeout of 30 seconds +}); + +// Set as the active transport +setTransport(wsTransport); + +// Now all generated bindings use WebSocket instead of HTTP fetch! +const result = await GreetService.Greet("World"); +``` + +**Key Point**: The generated bindings (`GreetService.Greet()`, `GreetService.Echo()`, etc.) automatically use whatever transport is configured via `setTransport()`. This proves the custom transport hijack works seamlessly with Wails code generation! + +## Features Demonstrated + +### 1. Generated Bindings with Custom Transport +All generated bindings work identically with WebSocket transport: +- `GreetService.Greet(name)` - Simple string parameter and return +- `GreetService.Echo(message)` - Echo back messages +- `GreetService.Add(a, b)` - Multiple parameters with numeric types +- `GreetService.GetTime()` - No parameters, string return + +### 2. Connection Management +- Automatic connection establishment on startup +- Visual connection status indicator (green = connected, red = disconnected) +- Automatic reconnection with configurable delay +- Graceful handling of connection failures + +### 3. Error Handling +- Request timeouts +- Connection errors +- Backend method errors +- All propagate correctly to the frontend diff --git a/v3/examples/websocket-transport/assets/bindings/github.com/wailsapp/wails/v3/examples/websocket-transport/greetservice.js b/v3/examples/websocket-transport/assets/bindings/github.com/wailsapp/wails/v3/examples/websocket-transport/greetservice.js new file mode 100644 index 000000000..ffdc9c702 --- /dev/null +++ b/v3/examples/websocket-transport/assets/bindings/github.com/wailsapp/wails/v3/examples/websocket-transport/greetservice.js @@ -0,0 +1,48 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is a service that demonstrates bound methods over WebSocket transport + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Add adds two numbers together + * @param {number} a + * @param {number} b + * @returns {$CancellablePromise} + */ +export function Add(a, b) { + return $Call.ByID(1578108007, a, b); +} + +/** + * Echo echoes back the input message + * @param {string} message + * @returns {$CancellablePromise} + */ +export function Echo(message) { + return $Call.ByID(1259920061, message); +} + +/** + * GetTime returns the current server time + * @returns {$CancellablePromise} + */ +export function GetTime() { + return $Call.ByID(570467169); +} + +/** + * Greet greets a person by name + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/examples/websocket-transport/assets/bindings/github.com/wailsapp/wails/v3/examples/websocket-transport/index.js b/v3/examples/websocket-transport/assets/bindings/github.com/wailsapp/wails/v3/examples/websocket-transport/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/examples/websocket-transport/assets/bindings/github.com/wailsapp/wails/v3/examples/websocket-transport/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/examples/websocket-transport/assets/index.html b/v3/examples/websocket-transport/assets/index.html new file mode 100644 index 000000000..5672616f7 --- /dev/null +++ b/v3/examples/websocket-transport/assets/index.html @@ -0,0 +1,353 @@ + + + + + + WebSocket Transport Example + + + +
        +

        🚀 WebSocket Transport Example

        +

        Custom IPC transport using WebSockets instead of HTTP fetch

        + +
        +

        Connection Status

        +
        + Disconnected +
        +
        + ℹ️ Transport Info: + This example uses a custom WebSocket transport running on ws://localhost:9099/wails/ws + instead of the default HTTP fetch-based transport. All Wails generated bindings work identically! +

        + The methods below use GreetService.Greet(), GreetService.Echo(), etc. from the generated bindings, + proving that custom transports work seamlessly with Wails code generation. +
        +
        + +
        +

        🕐 Server Timer (Auto-updating via Events)

        +
        + Waiting for timer... +
        +
        + ✨ Automatic Event Forwarding: + The server emits a timer:tick event every second. Because the WebSocket transport + implements EventTransport, these events are automatically forwarded to + the frontend with zero manual wiring code! +
        +
        + +
        +

        Test Dialog

        +
        + +
        +
        Click "Open" to test the binding...
        +
        + +
        +

        Test Greet Method (with Events)

        +
        + + +
        +
        Click "Greet" to test the binding...
        +
        + 📡 Event Counter: + 0 (Updates via WebSocket events) +
        +
        + +
        +

        Test Echo Method

        +
        + + +
        +
        Click "Echo" to test...
        +
        + +
        +

        Test Add Method

        +
        + + + +
        +
        Click "Add" to calculate...
        +
        + +
        +

        Test GetTime Method

        + +
        Click to get current server time...
        +
        +
        + + + + diff --git a/v3/examples/websocket-transport/frontend/bindings/github.com/wailsapp/wails/v3/examples/websocket-transport/greetservice.js b/v3/examples/websocket-transport/frontend/bindings/github.com/wailsapp/wails/v3/examples/websocket-transport/greetservice.js new file mode 100644 index 000000000..2e837223c --- /dev/null +++ b/v3/examples/websocket-transport/frontend/bindings/github.com/wailsapp/wails/v3/examples/websocket-transport/greetservice.js @@ -0,0 +1,48 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is a service that demonstrates bound methods over WebSocket transport + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Add adds two numbers together + * @param {number} a + * @param {number} b + * @returns {$CancellablePromise} + */ +export function Add(a, b) { + return $Call.ByID(1578108007, a, b); +} + +/** + * Echo echoes back the input message + * @param {string} message + * @returns {$CancellablePromise} + */ +export function Echo(message) { + return $Call.ByID(1259920061, message); +} + +/** + * GetTime returns the current server time + * @returns {$CancellablePromise} + */ +export function GetTime() { + return $Call.ByID(570467169); +} + +/** + * Greet greets a person by name + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/examples/websocket-transport/frontend/bindings/github.com/wailsapp/wails/v3/examples/websocket-transport/index.js b/v3/examples/websocket-transport/frontend/bindings/github.com/wailsapp/wails/v3/examples/websocket-transport/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/examples/websocket-transport/frontend/bindings/github.com/wailsapp/wails/v3/examples/websocket-transport/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/examples/websocket-transport/main.go b/v3/examples/websocket-transport/main.go new file mode 100644 index 000000000..3105d2231 --- /dev/null +++ b/v3/examples/websocket-transport/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + // Create WebSocket transport on port 9099 + wsTransport := NewWebSocketTransport(":9099") + + app := application.New(application.Options{ + Name: "WebSocket Transport Example", + Description: "Example demonstrating custom WebSocket-based IPC transport with event support", + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + // Use WebSocket transport instead of default HTTP + // Events are automatically forwarded to the transport since it implements EventTransport + Transport: wsTransport, + }) + + // ✨ NO MANUAL EVENT WIRING NEEDED! ✨ + // Events are automatically forwarded to the WebSocket transport because it implements + // the EventTransport interface. The following code is no longer necessary: + // + // app.Events.On("greet:count", func(event *application.WailsEvent) { + // wsTransport.SendEvent(event) + // }) + // + // All events emitted via app.Events.Emit() are automatically broadcast to connected clients! + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "WebSocket Transport Example", + URL: "/", + Width: 800, + Height: 600, + DevToolsEnabled: true, + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/websocket-transport/transport_websocket.go b/v3/examples/websocket-transport/transport_websocket.go new file mode 100644 index 000000000..482e7319c --- /dev/null +++ b/v3/examples/websocket-transport/transport_websocket.go @@ -0,0 +1,261 @@ +package main + +import ( + "context" + _ "embed" + "log" + "net/http" + "sync" + + "github.com/gorilla/websocket" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed websocket-transport.js +var jsClient []byte + +// WebSocketTransport is an example implementation of a WebSocket-based transport. +// This demonstrates how to create a custom transport that can replace the default +// HTTP fetch-based IPC while retaining all Wails bindings and event communication. +// +// This implementation is provided as an example and is not production-ready. +// You may need to add error handling, reconnection logic, authentication, etc. +type WebSocketTransport struct { + addr string + server *http.Server + upgrader websocket.Upgrader + clients map[*websocket.Conn]chan *WebSocketMessage + mu sync.RWMutex + handler *application.MessageProcessor +} + +// WebSocketTransportOption is a functional option for configuring WebSocketTransport +type WebSocketTransportOption func(*WebSocketTransport) + +// wsResponse represents the response to a runtime call. +type wsResponse struct { + // StatusCode is the HTTP status code equivalent (200 for success, 422 for error, etc.) + StatusCode int `json:"statusCode"` + + // Data contains the response body (can be struct, string) + Data any `json:"data"` +} + +// WebSocketMessage represents a message sent over the WebSocket transport +type WebSocketMessage struct { + ID string `json:"id"` // Unique message ID for request/response matching + Type string `json:"type"` // "request" or "response" + Request *application.RuntimeRequest `json:"request,omitempty"` + Response *wsResponse `json:"response,omitempty"` + Event *application.CustomEvent `json:"event,omitempty"` +} + +// NewWebSocketTransport creates a new WebSocket transport listening on the specified address. +// Example: NewWebSocketTransport(":9099") +func NewWebSocketTransport(addr string, opts ...WebSocketTransportOption) *WebSocketTransport { + t := &WebSocketTransport{ + addr: addr, + upgrader: websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + // In production, you should validate the origin + return true + }, + }, + clients: make(map[*websocket.Conn]chan *WebSocketMessage), + } + + // Apply options + for _, opt := range opts { + opt(t) + } + + return t +} + +// Start initializes and starts the WebSocket server +func (w *WebSocketTransport) Start(ctx context.Context, handler *application.MessageProcessor) error { + w.handler = handler + + // Create HTTP server but don't start it yet + // We'll set up the handler in ServeAssets() if it's called + w.server = &http.Server{ + Addr: w.addr, + } + + // Handle context cancellation + go func() { + <-ctx.Done() + w.Stop() + }() + + return nil +} + +func (w *WebSocketTransport) JSClient() []byte { + return jsClient +} + +// ServeAssets configures the transport to serve assets alongside WebSocket IPC. +// This implements the AssetServerTransport interface for browser-based deployments. +func (w *WebSocketTransport) ServeAssets(assetHandler http.Handler) error { + mux := http.NewServeMux() + + // Mount WebSocket endpoint for IPC + mux.HandleFunc("/wails/ws", w.handleWebSocket) + + // Mount asset server for all other requests + mux.Handle("/", assetHandler) + + // Set the handler and start the server + w.server.Handler = mux + + // Start server in background + go func() { + log.Printf("WebSocket transport serving assets and IPC on %s", w.addr) + log.Printf(" - Assets: http://%s/", w.addr) + log.Printf(" - WebSocket IPC: ws://%s/wails/ws", w.addr) + if err := w.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Printf("WebSocket server error: %v", err) + } + }() + + return nil +} + +// Stop gracefully shuts down the WebSocket server +func (w *WebSocketTransport) Stop() error { + if w.server == nil { + return nil + } + + w.mu.Lock() + for conn := range w.clients { + conn.Close() + } + w.mu.Unlock() + + return w.server.Shutdown(context.Background()) +} + +// handleWebSocket handles WebSocket connections +func (w *WebSocketTransport) handleWebSocket(rw http.ResponseWriter, r *http.Request) { + conn, err := w.upgrader.Upgrade(rw, r, nil) + if err != nil { + log.Printf("WebSocket upgrade failed: %v", err) + return + } + + w.mu.Lock() + messageChan := make(chan *WebSocketMessage, 100) + w.clients[conn] = messageChan + w.mu.Unlock() + + ctx, cancel := context.WithCancel(r.Context()) + + defer func() { + w.mu.Lock() + cancel() + close(messageChan) + if _, ok := w.clients[conn]; ok { + delete(w.clients, conn) + } + w.mu.Unlock() + conn.Close() + }() + + // write responses in one place, as concurrent writeJSON is not allowed + go func() { + for { + select { + case msg, ok := <-messageChan: + if !ok { + return + } + + w.mu.RLock() + if err := conn.WriteJSON(msg); err != nil { + log.Printf("[WebSocket] Failed to send message: %v", err) + } else { + if msg.Type == "response" { + log.Printf("[WebSocket] Successfully sent response for msgID=%s", msg.ID) + } + } + w.mu.RUnlock() + } + } + }() + + // Read messages from client + for { + var msg WebSocketMessage + err := conn.ReadJSON(&msg) + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { + log.Printf("WebSocket read error: %v", err) + } + break + } + + // Process request + if msg.Type == "request" && msg.Request != nil { + if msg.Request.Args == nil { + msg.Request.Args = &application.Args{} + } + go w.handleRequest(ctx, messageChan, msg.ID, msg.Request) + } + } +} + +// handleRequest processes a runtime call request and sends the response +func (w *WebSocketTransport) handleRequest(ctx context.Context, messageChan chan *WebSocketMessage, msgID string, req *application.RuntimeRequest) { + log.Printf("[WebSocket] Received request: msgID=%s, object=%d, method=%d, args=%s", msgID, req.Object, req.Method, req.Args.String()) + + // Call the Wails runtime handler + response, err := w.handler.HandleRuntimeCallWithIDs(ctx, req) + + w.sendResponse(ctx, messageChan, msgID, response, err) +} + +// sendResponse sends a response message to the client +func (w *WebSocketTransport) sendResponse(ctx context.Context, messageChan chan *WebSocketMessage, msgID string, resp any, err error) { + response := &wsResponse{ + StatusCode: 200, + Data: resp, + } + if err != nil { + response.StatusCode = 422 + response.Data = err.Error() + } + + responseMsg := &WebSocketMessage{ + ID: msgID, + Type: "response", + Response: response, + } + + w.mu.RLock() + defer w.mu.RUnlock() + + select { + case <-ctx.Done(): + log.Println("[WebSocket] Context cancelled before sending response.") + default: + messageChan <- responseMsg + } +} + +// BroadcastEvent sends an event to all connected clients +// This can be used for server-pushed events +func (w *WebSocketTransport) DispatchWailsEvent(event *application.CustomEvent) { + msg := &WebSocketMessage{ + Type: "event", + Event: event, + } + + w.mu.RLock() + defer w.mu.RUnlock() + + for _, channel := range w.clients { + channel <- msg + } +} diff --git a/v3/examples/websocket-transport/websocket-transport.js b/v3/examples/websocket-transport/websocket-transport.js new file mode 100644 index 000000000..ff8d055fd --- /dev/null +++ b/v3/examples/websocket-transport/websocket-transport.js @@ -0,0 +1,253 @@ +/** + * WebSocket Transport Implementation for Wails + * + * This is a custom transport that replaces the default HTTP fetch transport + * with WebSocket-based communication. + * + * VERSION 5 - SIMPLIFIED + */ + +console.log("[WebSocket Transport] Loading VERSION 5 - simplified"); + +import { clientId } from "/wails/runtime.js"; + +/** + * Generate a unique ID (simplified nanoid implementation) + */ +function nanoid(size = 21) { + const alphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; + let id = ""; + let i = size; + while (i--) { + id += alphabet[(Math.random() * 64) | 0]; + } + return id; +} + +/** + * WebSocket Transport class + */ +export class WebSocketTransport { + constructor(url, options = {}) { + this.url = url; + this.ws = null; + this.wsReady = false; + this.pendingRequests = new Map(); + this.messageQueue = []; + this.reconnectTimer = null; + this.reconnectDelay = options.reconnectDelay || 2000; + this.requestTimeout = options.requestTimeout || 30000; + this.maxQueueSize = options.maxQueueSize || 100; + } + + /** + * Connect to the WebSocket server + */ + connect() { + if (this.ws?.readyState === WebSocket.OPEN || this.ws?.readyState === WebSocket.CONNECTING) { + return Promise.resolve(); + } + + return new Promise((resolve, reject) => { + this.ws = new WebSocket(this.url); + + this.ws.onopen = () => { + console.log(`[WebSocket] ✓ Connected to ${this.url}`); + this.wsReady = true; + + // Send queued messages + while (this.messageQueue.length > 0) { + const msg = this.messageQueue.shift(); + this.ws.send(JSON.stringify(msg)); + } + + resolve(); + }; + + this.ws.onmessage = async(event) => { + // Handle both text and binary messages + let data = event.data; + if (data instanceof Blob) { + data = await data.text(); + } + this.handleMessage(data); + }; + + this.ws.onerror = (error) => { + console.error("[WebSocket] Error:", error); + this.wsReady = false; + reject(error); + }; + + this.ws.onclose = () => { + console.log("[WebSocket] Disconnected"); + this.wsReady = false; + + // Reject all pending requests + this.pendingRequests.forEach(({ reject, timeout }) => { + clearTimeout(timeout); + reject(new Error("WebSocket connection closed")); + }); + this.pendingRequests.clear(); + this.messageQueue = []; + + // Attempt to reconnect + if (!this.reconnectTimer) { + this.reconnectTimer = setTimeout(() => { + this.reconnectTimer = null; + console.log("[WebSocket] Attempting to reconnect..."); + this.connect().catch(err => { + console.error("[WebSocket] Reconnection failed:", err); + }); + }, this.reconnectDelay); + } + }; + }); + } + + /** + * Handle incoming WebSocket message + */ + handleMessage(data) { + console.log("[WebSocket] Received raw message:", data); + try { + const msg = JSON.parse(data); + console.log("[WebSocket] Parsed message:", msg); + + if (msg.type === "response" && msg.id) { + const pending = this.pendingRequests.get(msg.id); + if (!pending) { + console.warn("[WebSocket] No pending request for ID:", msg.id); + return; + } + + this.pendingRequests.delete(msg.id); + clearTimeout(pending.timeout); + + const response = msg.response; + if (!response) { + pending.reject(new Error("Invalid response: missing response field")); + return; + } + + console.log("[WebSocket] Response statusCode:", response.statusCode); + + if (response.statusCode === 200) { + let responseData = response.data; + + console.log("[WebSocket] Response data:", responseData); + pending.resolve(responseData ?? undefined); + } else { + let errorData = response.data; + console.error("[WebSocket] Error response:", errorData); + pending.reject(new Error(errorData)); + } + } else if (msg.type === "event") { + console.log("[WebSocket] Received server event:", msg); + // Dispatch to Wails event system + if (msg.event && window._wails?.dispatchWailsEvent) { + window._wails.dispatchWailsEvent(msg.event); + console.log("[WebSocket] Event dispatched to Wails:", msg.event.name); + } + } + } catch (err) { + console.error("[WebSocket] Failed to parse WebSocket message:", err); + console.error("[WebSocket] Raw message that failed:", data); + } + } + + /** + * Send a runtime call over WebSocket + * Implements the RuntimeTransport.call() interface + */ + async call(objectID, method, windowName, args) { + // Ensure WebSocket is connected + if (!this.wsReady) { + await this.connect(); + } + + return new Promise((resolve, reject) => { + const msgID = nanoid(); + + // Set up timeout + const timeout = setTimeout(() => { + if (this.pendingRequests.has(msgID)) { + this.pendingRequests.delete(msgID); + reject(new Error(`Request timeout (${this.requestTimeout}ms)`)); + } + }, this.requestTimeout); + + // Register pending request with the message for later reference + this.pendingRequests.set(msgID, { resolve, reject, timeout, request: { object: objectID, method, args } }); + + // Build message + const message = { + id: msgID, + type: "request", + request: { + object: objectID, + method: method, + args: args, + windowName: windowName || undefined, + clientId: clientId + } + }; + + // Send or queue message + if (this.wsReady && this.ws?.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify(message)); + } else { + if (this.messageQueue.length >= this.maxQueueSize) { + reject(new Error("Message queue full")); + return; + } + this.messageQueue.push(message); + this.connect().catch(reject); + } + }); + } + + /** + * Close the WebSocket connection + */ + close() { + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + + if (this.ws) { + this.ws.close(); + this.ws = null; + } + + this.wsReady = false; + } + + /** + * Get connection status + */ + isConnected() { + return this.wsReady && this.ws?.readyState === WebSocket.OPEN; + } +} + +/** + * Create and configure a WebSocket transport + * + * @param url - WebSocket URL (e.g., 'ws://localhost:9099/wails/ws') + * @param options - Optional configuration + * @returns WebSocketTransport instance + */ +export async function createWebSocketTransport(url, options = {}) { + const transport = new WebSocketTransport(url, options); + await transport.connect(); + return transport; +} + +const wsTransport = await createWebSocketTransport("ws://localhost:9099/wails/ws", { + reconnectDelay: 2000, + requestTimeout: 30000 +}); + +export default wsTransport; diff --git a/v3/examples/window-api/README.md b/v3/examples/window-api/README.md new file mode 100644 index 000000000..02c726062 --- /dev/null +++ b/v3/examples/window-api/README.md @@ -0,0 +1,19 @@ +# Window API Example + +This is an example of how to use the JS Window API + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | | +| Linux | | diff --git a/v3/examples/window-api/assets/index.html b/v3/examples/window-api/assets/index.html new file mode 100644 index 000000000..a0dd5d8b2 --- /dev/null +++ b/v3/examples/window-api/assets/index.html @@ -0,0 +1,159 @@ + + + + + Wails Alpha + + + + + +
        Alpha
        +
        +

        Close the Window?

        +

        Center

        +

        Minimise

        +

        Maximise

        +

        UnMaximise

        +

        Fullscreen

        +

        UnFullscreen

        +

        Restore

        +
        +
        +

        ToggleMaximise

        +

        IsFocused

        +

        IsMaximised

        +

        IsFullscreen

        +
        +
        + + + + + diff --git a/v3/examples/window-api/main.go b/v3/examples/window-api/main.go new file mode 100644 index 000000000..252608641 --- /dev/null +++ b/v3/examples/window-api/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "JS Window API Demo", + Description: "A demo of the JS Window API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "JS Window API Demo", + Width: 1280, + Height: 1024, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/window-call/README.md b/v3/examples/window-call/README.md new file mode 100644 index 000000000..baffad046 --- /dev/null +++ b/v3/examples/window-call/README.md @@ -0,0 +1,11 @@ +# Window Call Example + +This example is a demonstration of how to know which window is calling a service. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` diff --git a/v3/examples/window-call/assets/index.html b/v3/examples/window-call/assets/index.html new file mode 100644 index 000000000..a99293f03 --- /dev/null +++ b/v3/examples/window-call/assets/index.html @@ -0,0 +1,27 @@ + + + + + + Window Call Demo + + + + + + + + + \ No newline at end of file diff --git a/v3/examples/window-call/main.go b/v3/examples/window-call/main.go new file mode 100644 index 000000000..e6bdee23b --- /dev/null +++ b/v3/examples/window-call/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "context" + "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "log" + "math/rand" + "runtime" + "strconv" +) + +//go:embed assets/* +var assets embed.FS + +type WindowService struct{} + +func (s *WindowService) RandomTitle(ctx context.Context) { + callingWindow := ctx.Value(application.WindowKey).(application.Window) + title := "Random Title " + strconv.Itoa(rand.Intn(1000)) + callingWindow.SetTitle(title) +} + +// ============================================== + +func main() { + app := application.New(application.Options{ + Name: "Window call Demo", + Description: "A demo of the WebviewWindow API", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, + Services: []application.Service{ + application.NewService(&WindowService{}), + }, + }) + + app.Window.New(). + SetTitle("WebviewWindow 1"). + Show() + + // Create a custom menu + menu := app.NewMenu() + if runtime.GOOS == "darwin" { + 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.Window.New(). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + Show() + windowCounter++ + }) + + app.Menu.Set(menu) + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/window-menu/README.md b/v3/examples/window-menu/README.md new file mode 100644 index 000000000..0d825364f --- /dev/null +++ b/v3/examples/window-menu/README.md @@ -0,0 +1,24 @@ +# Window Menu Example + +*** Windows Only *** + +This example demonstrates how to create a window with a menu bar that can be toggled using the window.ToggleMenuBar() method. + +## Features + +- Default menu bar with File, Edit, and Help menus +- F1 key to toggle menu bar visibility +- Simple HTML interface with instructions + +## Running the Example + +```bash +cd v3/examples/window-menu +go run . +``` + +## How it Works + +The example creates a window with a default menu and binds the F1 key to toggle the menu bar's visibility. The menu bar will show when F2 is pressed and hide when F3 is released. + +Note: The menu bar toggling functionality only works on Windows. On other platforms, the F1 key binding will have no effect. diff --git a/v3/examples/window-menu/assets/about.html b/v3/examples/window-menu/assets/about.html new file mode 100644 index 000000000..e887a84ce --- /dev/null +++ b/v3/examples/window-menu/assets/about.html @@ -0,0 +1,14 @@ + + + Window Menu Demo + + + +
        +

        About Window Menu Demo

        +

        Press F1 to toggle menu bar visibility

        +

        Press F2 to show menu bar

        +

        Press F3 to hide menu bar

        +
        + + \ No newline at end of file diff --git a/v3/examples/window-menu/assets/index.html b/v3/examples/window-menu/assets/index.html new file mode 100644 index 000000000..b18f601e0 --- /dev/null +++ b/v3/examples/window-menu/assets/index.html @@ -0,0 +1,48 @@ + + + Window Menu Demo + + + +
        +

        Window Menu Demo

        +

        This example demonstrates the menu bar visibility toggle feature.

        +

        Press F1 to toggle the menu bar.

        +

        Press F2 to show the menu bar.

        +

        Press F3 to hide the menu bar.

        +

        The menu includes:

        +
          +
        • File menu with Exit option
        • +
        • MenuBar menu with Hide options
        • +
        • Help menu with About
        • +
        +
        + + \ No newline at end of file diff --git a/v3/examples/window-menu/assets/style.css b/v3/examples/window-menu/assets/style.css new file mode 100644 index 000000000..c7fc71f39 --- /dev/null +++ b/v3/examples/window-menu/assets/style.css @@ -0,0 +1,26 @@ +body { + font-family: system-ui, -apple-system, sans-serif; + margin: 0; + padding: 2rem; + background: #f5f5f5; + color: #333; +} +.container { + max-width: 600px; + margin: 0 auto; + background: white; + padding: 2rem; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} +h1 { + margin-top: 0; + color: #2d2d2d; +} +.key { + background: #e9e9e9; + padding: 2px 8px; + border-radius: 4px; + border: 1px solid #ccc; + font-family: monospace; +} diff --git a/v3/examples/window-menu/main.go b/v3/examples/window-menu/main.go new file mode 100644 index 000000000..107213b44 --- /dev/null +++ b/v3/examples/window-menu/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "embed" + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "log" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "Window MenuBar Demo", + Description: "A demo of menu bar toggling", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + }) + + // Create a menu + menu := app.NewMenu() + fileMenu := menu.AddSubmenu("File") + fileMenu.Add("Exit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + + editMenu := menu.AddSubmenu("MenuBar") + editMenu.Add("Hide MenuBar").OnClick(func(ctx *application.Context) { + app.Window.Current().HideMenuBar() + }) + + helpMenu := menu.AddSubmenu("Help") + helpMenu.Add("About").OnClick(func(ctx *application.Context) { + app.Window.Current().SetURL("/about.html") + }) + + // Create window with menu + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window MenuBar Demo", + Width: 800, + Height: 600, + Windows: application.WindowsWindow{ + Menu: menu, + }, + KeyBindings: map[string]func(window application.Window){ + "F1": func(window application.Window) { + window.ToggleMenuBar() + }, + "F2": func(window application.Window) { + window.ShowMenuBar() + }, + "F3": func(window application.Window) { + window.HideMenuBar() + }, + }, + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/window/README.md b/v3/examples/window/README.md new file mode 100644 index 000000000..2f1c7e810 --- /dev/null +++ b/v3/examples/window/README.md @@ -0,0 +1,19 @@ +# Window Example + +This example is a demonstration of the Windows API. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | | +| Windows | Working | +| Linux | | diff --git a/v3/examples/window/assets/index.html b/v3/examples/window/assets/index.html new file mode 100644 index 000000000..16baa68ff --- /dev/null +++ b/v3/examples/window/assets/index.html @@ -0,0 +1,90 @@ + + + + + + Window Demo + + + + + +
        +
        + +
        +
        +
        + +
        + +  X: + + +  Width: + + +   + + +
        + + + +   + + + +   + +
        + + + + + + \ No newline at end of file diff --git a/v3/examples/window/main.go b/v3/examples/window/main.go new file mode 100644 index 000000000..a70cc1e8a --- /dev/null +++ b/v3/examples/window/main.go @@ -0,0 +1,761 @@ +package main + +import ( + "embed" + "fmt" + "log" + "math/rand" + "runtime" + "strconv" + "time" + + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/pkg/events" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// This is a stub for non-windows platforms +var getExStyle = func() int { + return 0 +} + +//go:embed assets/* +var assets embed.FS + +type WindowService struct{} + +// ============================================== +func (s *WindowService) SetPos(relative bool, x, y float64) { + win := application.Get().Window.Current() + initX, initY := win.Position() + if relative { + x += float64(initX) + y += float64(initY) + } + win.SetPosition(int(x), int(y)) + currentX, currentY := win.Position() + fmt.Printf("SetPos: %d, %d => %d, %d\n", initX, initY, currentX, currentY) +} +func (s *WindowService) SetSize(relative bool, wdt, hgt float64) { + win := application.Get().Window.Current() + initW, initH := win.Size() + if relative { + wdt += float64(initW) + hgt += float64(initH) + } + win.SetSize(int(wdt), int(hgt)) + currentW, currentH := win.Size() + fmt.Printf("SetSize: %d, %d => %d, %d\n", initW, initH, currentW, currentH) +} +func (s *WindowService) SetBounds(x, y, w, h float64) { + win := application.Get().Window.Current() + initR := win.Bounds() + win.SetBounds(application.Rect{ + X: int(x), + Y: int(y), + Width: int(w), + Height: int(h), + }) + currentR := win.Bounds() + fmt.Printf("SetBounds: %+v => %+v\n", initR, currentR) +} +func (s *WindowService) GetBounds() application.Rect { + win := application.Get().Window.Current() + r := win.Bounds() + mid := r.X + (r.Width-1)/2 + fmt.Printf("GetBounds: %+v: mid: %d\n", r, mid) + return r +} + +// ============================================== + +func main() { + app := application.New(application.Options{ + Name: "WebviewWindow Demo", + Description: "A demo of the WebviewWindow API", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, + Services: []application.Service{ + application.NewService(&WindowService{}), + }, + }) + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(event *application.ApplicationEvent) { + log.Println("ApplicationDidFinishLaunching") + }) + + var hiddenWindows []application.Window + + currentWindow := func(fn func(window application.Window)) { + if app.Window.Current() != nil { + fn(app.Window.Current()) + } else { + println("Current Window is nil") + } + } + + // Create a custom menu + menu := app.NewMenu() + if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) + } else { + menu.AddRole(application.FileMenu) + } + 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.Window.New(). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + if runtime.GOOS != "linux" { + myMenu.Add("New WebviewWindow (Content Protection Enabled)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + MinimiseButtonState: application.ButtonDisabled, + ContentProtectionEnabled: true, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Disable Minimise)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + MinimiseButtonState: application.ButtonDisabled, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Disable Maximise)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + MaximiseButtonState: application.ButtonDisabled, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Hide Minimise)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + MinimiseButtonState: application.ButtonHidden, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Always on top)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + AlwaysOnTop: true, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Hide Maximise)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + MaximiseButtonState: application.ButtonHidden, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + + myMenu.Add("New WebviewWindow (Centered)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + MaximiseButtonState: application.ButtonHidden, + InitialPosition: application.WindowCentered, + }). + SetTitle("WebviewWindow " + strconv.Itoa(windowCounter)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + + myMenu.Add("New WebviewWindow (Position 100,100)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + MaximiseButtonState: application.ButtonHidden, + X: 100, + Y: 100, + InitialPosition: application.WindowXY, + }). + SetTitle("WebviewWindow " + strconv.Itoa(windowCounter)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + } + if runtime.GOOS == "darwin" || runtime.GOOS == "windows" { + myMenu.Add("New WebviewWindow (Disable Close)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + CloseButtonState: application.ButtonDisabled, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Hide Close)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + CloseButtonState: application.ButtonHidden, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + + } + + if runtime.GOOS == "windows" { + myMenu.Add("New WebviewWindow (Custom ExStyle)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Windows: application.WindowsWindow{ + ExStyle: getExStyle(), + }, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + } + myMenu.Add("New WebviewWindow (Listen to Move)"). + OnClick(func(ctx *application.Context) { + w := app.Window.NewWithOptions(application.WebviewWindowOptions{}). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + w.OnWindowEvent(events.Common.WindowDidMove, func(event *application.WindowEvent) { + x, y := w.Position() + fmt.Printf("WindowDidMove event triggered. New position: (%d, %d)\n", x, y) + }) + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Listen to Resize)"). + OnClick(func(ctx *application.Context) { + w := app.Window.NewWithOptions(application.WebviewWindowOptions{}). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + w.OnWindowEvent(events.Common.WindowDidResize, func(event *application.WindowEvent) { + width, height := w.Size() + + fmt.Printf("WindowDidResize event triggered. New size: (%d, %d)\n", width, height) + }) + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Hides on Close one time)"). + SetAccelerator("CmdOrCtrl+H"). + OnClick(func(ctx *application.Context) { + var w application.Window = app.Window.New() + + w.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if !lo.Contains(hiddenWindows, w) { + hiddenWindows = append(hiddenWindows, w) + go func() { + time.Sleep(5 * time.Second) + w.Show() + }() + w.Hide() + e.Cancel() + } + // Remove the window from the hiddenWindows list + hiddenWindows = lo.Without(hiddenWindows, w) + }) + + w.SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + + windowCounter++ + + }) + myMenu.Add("New WebviewWindow (Frameless)"). + SetAccelerator("CmdOrCtrl+F"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + X: rand.Intn(1000), + Y: rand.Intn(800), + BackgroundColour: application.NewRGB(33, 37, 41), + Frameless: true, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + }, + }).Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Ignores mouse events)"). + SetAccelerator("CmdOrCtrl+F"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + HTML: "
        ", + X: rand.Intn(1000), + Y: rand.Intn(800), + IgnoreMouseEvents: true, + BackgroundType: application.BackgroundTypeTransparent, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + }, + }).Show() + windowCounter++ + }) + if runtime.GOOS == "darwin" { + myMenu.Add("New WebviewWindow (MacTitleBarHiddenInset)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHiddenInset, + InvisibleTitleBarHeight: 25, + }, + }). + SetBackgroundColour(application.NewRGB(33, 37, 41)). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetHTML("

        A MacTitleBarHiddenInset WebviewWindow example

        "). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (MacTitleBarHiddenInsetUnified)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetHTML("

        A MacTitleBarHiddenInsetUnified WebviewWindow example

        "). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (MacTitleBarHidden)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHidden, + InvisibleTitleBarHeight: 25, + }, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetHTML("

        A MacTitleBarHidden WebviewWindow example

        "). + Show() + windowCounter++ + }) + } + if runtime.GOOS == "windows" { + myMenu.Add("New WebviewWindow (Mica)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "WebviewWindow " + strconv.Itoa(windowCounter), + X: rand.Intn(1000), + Y: rand.Intn(800), + BackgroundType: application.BackgroundTypeTranslucent, + HTML: ` + + + + + +
        +

        This is a Window with a Mica backdrop

        +
        + +`, + Windows: application.WindowsWindow{ + BackdropType: application.Mica, + }, + }).Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Acrylic)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "WebviewWindow " + strconv.Itoa(windowCounter), + X: rand.Intn(1000), + Y: rand.Intn(800), + BackgroundType: application.BackgroundTypeTranslucent, + HTML: ` + + + + + +
        +

        This is a Window with an Acrylic backdrop

        +
        + +`, + Windows: application.WindowsWindow{ + BackdropType: application.Acrylic, + }, + }).Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Tabbed)"). + OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "WebviewWindow " + strconv.Itoa(windowCounter), + X: rand.Intn(1000), + Y: rand.Intn(800), + BackgroundType: application.BackgroundTypeTranslucent, + HTML: ` + + + + + +
        +

        This is a Window with a Tabbed-effect backdrop

        +
        + +`, + Windows: application.WindowsWindow{ + BackdropType: application.Tabbed, + }, + }).Show() + windowCounter++ + }) + } + + sizeMenu := menu.AddSubmenu("Size") + sizeMenu.Add("Set Size (800,600)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetSize(800, 600) + }) + }) + + sizeMenu.Add("Set Size (Random)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + 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.Window) { + w.SetMinSize(200, 200) + }) + }) + sizeMenu.Add("Set Max Size (600,600)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetMaxSize(600, 600) + w.SetMaximiseButtonState(application.ButtonDisabled) + }) + }) + sizeMenu.Add("Get Current WebviewWindow Size").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + width, height := w.Size() + app.Dialog.Info().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.Window) { + w.SetMinSize(0, 0) + }) + }) + sizeMenu.Add("Reset Max Size").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetMaxSize(0, 0) + w.SetMaximiseButtonState(application.ButtonEnabled) + }) + }) + + positionMenu := menu.AddSubmenu("Position") + positionMenu.Add("Set Position (0,0)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetPosition(0, 0) + }) + }) + + positionMenu.Add("Set Position (Random)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetPosition(rand.Intn(1000), rand.Intn(800)) + }) + }) + + positionMenu.Add("Get Position").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + x, y := w.Position() + app.Dialog.Info().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() + }) + }) + + positionMenu.Add("Set Relative Position (0,0)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetRelativePosition(0, 0) + }) + }) + positionMenu.Add("Set Relative Position (Corner)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + screen, _ := w.GetScreen() + w.SetRelativePosition(screen.WorkArea.Width-w.Width(), screen.WorkArea.Height-w.Height()) + }) + }) + positionMenu.Add("Set Relative Position (Random)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetRelativePosition(rand.Intn(1000), rand.Intn(800)) + }) + }) + + positionMenu.Add("Get Relative Position").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + x, y := w.RelativePosition() + app.Dialog.Info().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.Window) { + w.Center() + }) + }) + titleBarMenu := menu.AddSubmenu("Controls") + titleBarMenu.Add("Disable Minimise").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetMinimiseButtonState(application.ButtonDisabled) + }) + }) + titleBarMenu.Add("Enable Minimise").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetMinimiseButtonState(application.ButtonEnabled) + }) + }) + titleBarMenu.Add("Hide Minimise").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetMinimiseButtonState(application.ButtonHidden) + }) + }) + titleBarMenu.Add("Disable Maximise").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetMaximiseButtonState(application.ButtonDisabled) + }) + }) + titleBarMenu.Add("Enable Maximise").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetMaximiseButtonState(application.ButtonEnabled) + }) + }) + titleBarMenu.Add("Hide Maximise").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetMaximiseButtonState(application.ButtonHidden) + }) + }) + titleBarMenu.Add("Disable Close").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetCloseButtonState(application.ButtonDisabled) + }) + }) + titleBarMenu.Add("Enable Close").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetCloseButtonState(application.ButtonEnabled) + }) + }) + titleBarMenu.Add("Hide Close").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetCloseButtonState(application.ButtonHidden) + }) + }) + stateMenu := menu.AddSubmenu("State") + stateMenu.Add("Minimise (for 2 secs)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.Minimise() + time.Sleep(2 * time.Second) + w.Restore() + }) + }) + stateMenu.Add("Maximise").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.Maximise() + }) + }) + stateMenu.Add("Fullscreen").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.Fullscreen() + }) + }) + stateMenu.Add("UnFullscreen").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.UnFullscreen() + }) + }) + stateMenu.Add("Restore").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.Restore() + }) + }) + stateMenu.Add("Hide (for 2 seconds)").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.Hide() + time.Sleep(2 * time.Second) + w.Show() + }) + }) + stateMenu.Add("Always on Top").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetAlwaysOnTop(true) + }) + }) + stateMenu.Add("Not always on Top").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetAlwaysOnTop(false) + }) + }) + stateMenu.Add("Google.com").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetURL("https://google.com") + }) + }) + stateMenu.Add("wails.io").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetURL("https://wails.io") + }) + }) + stateMenu.Add("Get Primary Screen").OnClick(func(ctx *application.Context) { + screen := app.Screen.GetPrimary() + msg := fmt.Sprintf("Screen: %+v", screen) + app.Dialog.Info().SetTitle("Primary Screen").SetMessage(msg).Show() + }) + stateMenu.Add("Get Screens").OnClick(func(ctx *application.Context) { + screens := app.Screen.GetAll() + for _, screen := range screens { + msg := fmt.Sprintf("Screen: %+v", screen) + app.Dialog.Info().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.Window) { + screen, err := w.GetScreen() + if err != nil { + app.Dialog.Error().SetTitle("Error").SetMessage(err.Error()).Show() + return + } + msg := fmt.Sprintf("Screen: %+v", screen) + app.Dialog.Info().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + }) + }) + stateMenu.Add("Disable for 5s").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SetEnabled(false) + time.Sleep(5 * time.Second) + w.SetEnabled(true) + }) + }) + stateMenu.Add("Open Dev Tools").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.OpenDevTools() + }) + }) + + if runtime.GOOS != "darwin" { + stateMenu.Add("Flash for 5s").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + time.Sleep(2 * time.Second) + w.Flash(true) + time.Sleep(5 * time.Second) + w.Flash(false) + }) + }) + } + + if runtime.GOOS == "windows" { + stateMenu.Add("Snap Assist").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + w.SnapAssist() + }) + }) + } + + printMenu := menu.AddSubmenu("Print") + printMenu.Add("Print").OnClick(func(ctx *application.Context) { + currentWindow(func(w application.Window) { + _ = w.Print() + }) + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window Demo", + BackgroundColour: application.NewRGB(33, 37, 41), + Mac: application.MacWindow{ + DisableShadow: true, + }, + Windows: application.WindowsWindow{ + Menu: menu, + }, + }) + + app.Menu.Set(menu) + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/window/windows.go b/v3/examples/window/windows.go new file mode 100644 index 000000000..d66984d18 --- /dev/null +++ b/v3/examples/window/windows.go @@ -0,0 +1,11 @@ +//go:build windows + +package main + +import "github.com/wailsapp/wails/v3/pkg/w32" + +func init() { + getExStyle = func() int { + return w32.WS_EX_TOOLWINDOW | w32.WS_EX_NOREDIRECTIONBITMAP | w32.WS_EX_TOPMOST + } +} diff --git a/v3/examples/wml/README.md b/v3/examples/wml/README.md new file mode 100644 index 000000000..c8ea7850e --- /dev/null +++ b/v3/examples/wml/README.md @@ -0,0 +1,19 @@ +# WML Example + +This is an example of how to use the experimental WML, which provides HTMX style calling of the Wails JS API. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/wml/assets/index.html b/v3/examples/wml/assets/index.html new file mode 100644 index 000000000..466f4c5f0 --- /dev/null +++ b/v3/examples/wml/assets/index.html @@ -0,0 +1,160 @@ + + + + + Wails Alpha + + + + + +
        Alpha
        +
        +

        Documentation

        +

        Feedback

        +
        +

        This application contains no Javascript!

        +

        Emit event

        +

        Delete all the things!

        +

        Close the Window?

        +

        Center

        +

        Minimise

        +

        Maximise

        +

        UnMaximise

        +

        Fullscreen

        +

        UnFullscreen

        +

        Restore

        +

        Open Browser?

        +

        Hover over me

        +
        + + + + + diff --git a/v3/examples/wml/main.go b/v3/examples/wml/main.go new file mode 100644 index 000000000..8d4a55481 --- /dev/null +++ b/v3/examples/wml/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Wails ML Demo", + Description: "A demo of the Wails ML API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Wails ML Demo", + Width: 1280, + Height: 1024, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + app.Event.On("button-pressed", func(_ *application.CustomEvent) { + println("Button Pressed!") + }) + app.Event.On("hover", func(_ *application.CustomEvent) { + println("Hover time!") + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/fix-darwin-ios-constraints.sh b/v3/fix-darwin-ios-constraints.sh new file mode 100644 index 000000000..67211e7b9 --- /dev/null +++ b/v3/fix-darwin-ios-constraints.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# Fix build constraints for Darwin files to exclude iOS +echo "Fixing build constraints to exclude iOS from Darwin builds..." + +# List of files that need updating +files=( + "pkg/application/webview_window_darwin.m" + "pkg/application/webview_window_darwin.h" + "pkg/application/webview_window_darwin.go" + "pkg/application/webview_window_darwin_drag.m" + "pkg/application/webview_window_darwin_drag.h" + "pkg/application/webview_window_close_darwin.go" + "pkg/application/systemtray_darwin.m" + "pkg/application/systemtray_darwin.h" + "pkg/application/systemtray_darwin.go" + "pkg/application/single_instance_darwin.go" + "pkg/application/screen_darwin.go" + "pkg/application/menuitem_selectors_darwin.go" + "pkg/application/menuitem_darwin.m" + "pkg/application/menuitem_darwin.go" + "pkg/application/menu_darwin.go" + "pkg/application/mainthread_darwin.go" + "pkg/application/keys_darwin.go" + "pkg/application/events_common_darwin.go" + "pkg/application/dialogs_darwin_delegate.m" + "pkg/application/dialogs_darwin_delegate.h" + "pkg/application/dialogs_darwin.go" + "pkg/application/clipboard_darwin.go" + "pkg/application/application_darwin_delegate.m" + "pkg/application/application_darwin_delegate.h" + "pkg/application/application_darwin.h" +) + +for file in "${files[@]}"; do + if [ -f "$file" ]; then + # Check if file has the build constraint + if grep -q "^//go:build darwin$" "$file"; then + echo "Updating $file" + sed -i '' 's|^//go:build darwin$|//go:build darwin \&\& !ios|' "$file" + fi + fi +done + +# Also check for other darwin-specific files +echo "Checking for other darwin-specific build constraints..." +find . -name "*_darwin*.go" -o -name "*_darwin*.m" -o -name "*_darwin*.h" | while read -r file; do + if grep -q "^//go:build darwin$" "$file"; then + echo "Also updating: $file" + sed -i '' 's|^//go:build darwin$|//go:build darwin \&\& !ios|' "$file" + fi +done + +echo "Done! Build constraints updated." \ No newline at end of file diff --git a/v3/go.mod b/v3/go.mod new file mode 100644 index 000000000..c5aa9a11e --- /dev/null +++ b/v3/go.mod @@ -0,0 +1,172 @@ +module github.com/wailsapp/wails/v3 + +go 1.25 + +require ( + git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 + github.com/Masterminds/semver v1.5.0 + github.com/adrg/xdg v0.5.3 + github.com/atterpac/refresh v0.8.6 + github.com/bep/debounce v1.2.1 + github.com/charmbracelet/glamour v0.10.0 + github.com/charmbracelet/huh v0.8.0 + github.com/coder/websocket v1.8.14 + github.com/ebitengine/purego v0.9.1 + github.com/go-git/go-git/v5 v5.16.4 + github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e + github.com/go-ole/go-ole v1.3.0 + github.com/godbus/dbus/v5 v5.2.2 + github.com/google/go-cmp v0.7.0 + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 + github.com/google/uuid v1.6.0 + github.com/goreleaser/nfpm/v2 v2.44.1 + github.com/gorilla/websocket v1.5.3 + github.com/jackmordaunt/icns/v2 v2.2.7 + github.com/jaypipes/ghw v0.21.2 + github.com/konoui/lipo v0.10.0 + github.com/leaanthony/clir v1.7.0 + github.com/leaanthony/go-ansi-parser v1.6.1 + github.com/leaanthony/gosod v1.0.4 + github.com/leaanthony/u v1.1.1 + github.com/leaanthony/winicon v1.0.0 + github.com/lmittmann/tint v1.1.2 + github.com/matryer/is v1.4.1 + github.com/mattn/go-colorable v0.1.14 + github.com/mattn/go-isatty v0.0.20 + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c + github.com/pkg/errors v0.9.1 + github.com/pterm/pterm v0.12.82 + github.com/samber/lo v1.52.0 + github.com/stretchr/testify v1.11.1 + github.com/tc-hib/winres v0.3.1 + github.com/wailsapp/go-webview2 v1.0.23 + github.com/wailsapp/task/v3 v3.40.1-patched3 + github.com/zalando/go-keyring v0.2.6 + golang.org/x/sys v0.40.0 + golang.org/x/term v0.39.0 + golang.org/x/tools v0.41.0 + gopkg.in/yaml.v3 v3.0.1 + modernc.org/sqlite v1.44.3 +) + +require ( + al.essio.dev/pkg/shellescape v1.6.0 // indirect + atomicgo.dev/schedule v0.1.0 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/catppuccin/go v0.3.0 // indirect + github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect + github.com/charmbracelet/bubbletea v1.3.10 // indirect + github.com/charmbracelet/colorprofile v0.4.1 // indirect + github.com/charmbracelet/x/cellbuf v0.0.14 // indirect + github.com/charmbracelet/x/exp/slice v0.0.0-20260122224438-b01af16209d9 // indirect + github.com/charmbracelet/x/exp/strings v0.0.0-20260122224438-b01af16209d9 // indirect + github.com/charmbracelet/x/term v0.2.2 // indirect + github.com/clipperhouse/displaywidth v0.7.0 // indirect + github.com/clipperhouse/stringish v0.1.1 // indirect + github.com/clipperhouse/uax29/v2 v2.4.0 // indirect + github.com/danieljoos/wincred v1.2.3 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/konoui/go-qsort v0.1.0 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect +) + +require ( + atomicgo.dev/cursor v0.2.0 // indirect + atomicgo.dev/keyboard v0.2.9 // indirect + dario.cat/mergo v1.0.2 // indirect + github.com/AlekSi/pointer v1.2.0 // indirect + github.com/BurntSushi/toml v1.6.0 // indirect + github.com/Ladicle/tabwriter v1.0.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.3.0 + github.com/alecthomas/chroma/v2 v2.23.1 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect + github.com/cavaliergopher/cpio v1.0.1 // indirect + github.com/chainguard-dev/git-urls v1.0.2 // indirect + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect + github.com/charmbracelet/x/ansi v0.11.4 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/containerd/console v1.0.5 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/dominikbraun/graph v0.23.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/go-task/template v0.2.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/rpmpack v0.7.1 // indirect + github.com/gookit/color v1.6.0 // indirect + github.com/goreleaser/chglog v0.7.4 // indirect + github.com/goreleaser/fileglob v1.4.0 // indirect + github.com/gorilla/css v1.0.1 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/jaypipes/pcidb v1.1.1 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/compress v1.18.3 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/klauspost/pgzip v1.2.6 // indirect + github.com/lithammer/fuzzysearch v1.1.8 // indirect + github.com/lucasb-eyer/go-colorful v1.3.0 // indirect + github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/mattn/go-zglob v0.0.6 // indirect + github.com/microcosm-cc/bluemonday v1.0.27 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/radovskyb/watcher v1.0.7 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rjeczalik/notify v0.9.3 // indirect + github.com/sajari/fuzzy v1.0.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/ulikunitz/xz v0.5.15 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yuin/goldmark v1.7.16 // indirect + github.com/yuin/goldmark-emoji v1.0.6 // indirect + github.com/zeebo/xxh3 v1.1.0 // indirect + gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect + golang.org/x/exp/typeparams v0.0.0-20260112195511-716be5621a96 + golang.org/x/image v0.35.0 + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/text v0.33.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + howett.net/plist v1.0.2-0.20250314012144-ee69052608d9 + modernc.org/libc v1.67.6 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + mvdan.cc/sh/v3 v3.12.0 // indirect +) diff --git a/v3/go.sum b/v3/go.sum new file mode 100644 index 000000000..dd0b0ae81 --- /dev/null +++ b/v3/go.sum @@ -0,0 +1,520 @@ +al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA= +al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= +atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= +atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= +atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= +atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= +atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= +atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA= +git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc= +github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= +github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= +github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg= +github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +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.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= +github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= +github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= +github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s= +github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY= +github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/atterpac/refresh v0.8.6 h1:Q5miKV2qs9jW+USw8WZ/54Zz8/RSh/bOz5U6JvvDZmM= +github.com/atterpac/refresh v0.8.6/go.mod h1:fJpWySLdpbANS8Ej5OvfZVZIVvi/9bmnhTjKS5EjQes= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= +github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8= +github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= +github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY= +github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= +github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= +github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= +github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ= +github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o= +github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws= +github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw= +github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= +github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= +github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk= +github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk= +github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY= +github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk= +github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY= +github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4= +github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= +github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= +github.com/charmbracelet/x/ansi v0.11.4 h1:6G65PLu6HjmE858CnTUQY1LXT3ZUWwfvqEROLF8vqHI= +github.com/charmbracelet/x/ansi v0.11.4/go.mod h1:/5AZ+UfWExW3int5H5ugnsG/PWjNcSQcwYsHBlPFQN4= +github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4= +github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA= +github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U= +github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/exp/slice v0.0.0-20260122224438-b01af16209d9 h1:BBTx26Fy+CW9U3kLiWBuWn9pI9C1NybaS+p/AZeAOkA= +github.com/charmbracelet/x/exp/slice v0.0.0-20260122224438-b01af16209d9/go.mod h1:vqEfX6xzqW1pKKZUUiFOKg0OQ7bCh54Q2vR/tserrRA= +github.com/charmbracelet/x/exp/strings v0.0.0-20260122224438-b01af16209d9 h1:JevRYfkTT0sN9OIXAOncYNC0cTP1Gml/0mCSnsmRkRk= +github.com/charmbracelet/x/exp/strings v0.0.0-20260122224438-b01af16209d9/go.mod h1:/ehtMPNh9K4odGFkqYJKpIYyePhdp1hLBRvyY4bWkH8= +github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= +github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= +github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= +github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= +github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI= +github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4= +github.com/clipperhouse/displaywidth v0.7.0 h1:QNv1GYsnLX9QBrcWUtMlogpTXuM5FVnBwKWp1O5NwmE= +github.com/clipperhouse/displaywidth v0.7.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= +github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= +github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= +github.com/clipperhouse/uax29/v2 v2.4.0 h1:RXqE/l5EiAbA4u97giimKNlmpvkmz+GrBVTelsoXy9g= +github.com/clipperhouse/uax29/v2 v2.4.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= +github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= +github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= +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/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= +github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-task/template v0.2.0 h1:xW7ek0o65FUSTbKcSNeg2Vyf/I7wYXFgLUznptvviBE= +github.com/go-task/template v0.2.0/go.mod h1:dbdoUb6qKnHQi1y6o+IdIrs0J4o/SEhSTA6bbzZmdtc= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/rpmpack v0.7.1 h1:YdWh1IpzOjBz60Wvdw0TU0A5NWP+JTVHA5poDqwMO2o= +github.com/google/rpmpack v0.7.1/go.mod h1:h1JL16sUTWCLI/c39ox1rDaTBo3BXUQGjczVJyK4toU= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0= +github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E= +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.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA= +github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/goreleaser/chglog v0.7.4 h1:3pnNt/XCrUcAOq+KC91Azlgp5CRv4GHo1nl8Aws7OzI= +github.com/goreleaser/chglog v0.7.4/go.mod h1:dTVoZZagTz7hHdWaZ9OshHntKiF44HbWIHWxYJQ/h0Y= +github.com/goreleaser/fileglob v1.4.0 h1:Y7zcUnzQjT1gbntacGAkIIfLv+OwojxTXBFxjSFoBBs= +github.com/goreleaser/fileglob v1.4.0/go.mod h1:1pbHx7hhmJIxNZvm6fi6WVrnP0tndq6p3ayWdLn1Yf8= +github.com/goreleaser/nfpm/v2 v2.44.1 h1:g+QNjkEx+C2Zu8dB48t9da/VfV0CWS5TMjxT8HG1APY= +github.com/goreleaser/nfpm/v2 v2.44.1/go.mod h1:drIYLqkla9SaOLbSnaFOmSIv5LXGfhHcbK54st97b4s= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/jackmordaunt/icns/v2 v2.2.7 h1:K/RbfvuzjmjVY5y4g+XENRs8ZZatwz4YnLHypa2KwQg= +github.com/jackmordaunt/icns/v2 v2.2.7/go.mod h1:ovoTxGguSuoUGKMk5Nn3R7L7BgMQkylsO+bblBuI22A= +github.com/jaypipes/ghw v0.21.2 h1:woW0lqNMPbYk59sur6thOVM8YFP9Hxxr8PM+JtpUrNU= +github.com/jaypipes/ghw v0.21.2/go.mod h1:GPrvwbtPoxYUenr74+nAnWbardIZq600vJDD5HnPsPE= +github.com/jaypipes/pcidb v1.1.1 h1:QmPhpsbmmnCwZmHeYAATxEaoRuiMAJusKYkUncMC0ro= +github.com/jaypipes/pcidb v1.1.1/go.mod h1:x27LT2krrUgjf875KxQXKB0Ha/YXLdZRVmw6hH0G7g8= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= +github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +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.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= +github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konoui/go-qsort v0.1.0 h1:0Os/0X0Fce6B54jqN26aR+J5uOExN+0t7nb9zs6zzzE= +github.com/konoui/go-qsort v0.1.0/go.mod h1:UOsvdDPBzyQDk9Tb21hETK6KYXGYQTnoZB5qeKA1ARs= +github.com/konoui/lipo v0.10.0 h1:1P2VkBSB6I38kgmyznvAjy9gmAqybK22pJt9iyx5CgY= +github.com/konoui/lipo v0.10.0/go.mod h1:R+0EgDVrLKKS37SumAO8zhpEprjjoKEkrT3QqKQE35k= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= +github.com/leaanthony/clir v1.7.0 h1:xiAnhl7ryPwuH3ERwPWZp/pCHk8wTeiwuAOt6MiNyAw= +github.com/leaanthony/clir v1.7.0/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= +github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= +github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI= +github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +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.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= +github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= +github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-zglob v0.0.6 h1:mP8RnmCgho4oaUYDIDn6GNxYk+qJGUs8fJLn+twYj2A= +github.com/mattn/go-zglob v0.0.6/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +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/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +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/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.82 h1:+D9wYhCaeaK0FIQoZtqbNQuNpe2lB2tajKKsTd5paVQ= +github.com/pterm/pterm v0.12.82/go.mod h1:TyuyrPjnxfwP+ccJdBTeWHtd/e0ybQHkOS/TakajZCw= +github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= +github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY= +github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +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.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= +github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tc-hib/winres v0.3.1 h1:CwRjEGrKdbi5CvZ4ID+iyVhgyfatxFoizjPhzez9Io4= +github.com/tc-hib/winres v0.3.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= +github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/task/v3 v3.40.1-patched3 h1:i6O1WNdSur9CGaiMDIYGjsmj/qS4465zqv+WEs6sPRs= +github.com/wailsapp/task/v3 v3.40.1-patched3/go.mod h1:jIP48r8ftoSQNlxFP4+aEnkvGQqQXqCnRi/B7ROaecE= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= +github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs= +github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s= +github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= +gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= +gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/exp/typeparams v0.0.0-20260112195511-716be5621a96 h1:RMc8anw0hCPcg5CZYN2PEQ8nMwosk461R6vFwPrCFVg= +golang.org/x/exp/typeparams v0.0.0-20260112195511-716be5621a96/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I= +golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/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-20210809222454-d867a43fc93e/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.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +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= +howett.net/plist v1.0.2-0.20250314012144-ee69052608d9 h1:eeH1AIcPvSc0Z25ThsYF+Xoqbn0CI/YnXVYoTLFdGQw= +howett.net/plist v1.0.2-0.20250314012144-ee69052608d9/go.mod h1:fyFX5Hj5tP1Mpk8obqA9MZgXT416Q5711SDT7dQLTLk= +modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= +modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= +modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= +modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= +modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= +modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI= +modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY= +modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI= +mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg= diff --git a/v3/internal/assetserver/asset_fileserver.go b/v3/internal/assetserver/asset_fileserver.go new file mode 100644 index 000000000..974bb582b --- /dev/null +++ b/v3/internal/assetserver/asset_fileserver.go @@ -0,0 +1,161 @@ +package assetserver + +import ( + "bytes" + "context" + "embed" + "errors" + "fmt" + "io" + iofs "io/fs" + "net/http" + "os" + "path" + "strings" +) + +const ( + indexHTML = "index.html" +) + +type assetFileServer struct { + fs iofs.FS + err error +} + +func newAssetFileServerFS(vfs iofs.FS) http.Handler { + subDir, err := findPathToFile(vfs, indexHTML) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + msg := "no `index.html` could be found in your Assets fs.FS" + if embedFs, isEmbedFs := vfs.(embed.FS); isEmbedFs { + rootFolder, _ := findEmbedRootPath(embedFs) + msg += fmt.Sprintf(", please make sure the embedded directory '%s' is correct and contains your assets", rootFolder) + } + + err = errors.New(msg) + } + } else { + vfs, err = iofs.Sub(vfs, path.Clean(subDir)) + } + + return &assetFileServer{fs: vfs, err: err} +} + +func (d *assetFileServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + ctx := req.Context() + url := req.URL.Path + + err := d.err + if err == nil { + filename := path.Clean(strings.TrimPrefix(url, "/")) + d.logInfo(ctx, "Handling request", "url", url, "file", filename) + err = d.serveFSFile(rw, req, filename) + if os.IsNotExist(err) { + rw.WriteHeader(http.StatusNotFound) + return + } + } + + if err != nil { + d.logError(ctx, "Unable to handle request", "url", url, "err", err) + http.Error(rw, err.Error(), http.StatusInternalServerError) + } +} + +// serveFile will try to load the file from the fs.FS and write it to the response +func (d *assetFileServer) serveFSFile(rw http.ResponseWriter, req *http.Request, filename string) error { + if d.fs == nil { + return os.ErrNotExist + } + + file, err := d.fs.Open(filename) + if err != nil { + if s := path.Ext(filename); s == "" { + filename = filename + ".html" + file, err = d.fs.Open(filename) + if err != nil { + return err + } + } else { + return err + } + } + defer file.Close() + + statInfo, err := file.Stat() + if err != nil { + return err + } + + url := req.URL.Path + isDirectoryPath := url == "" || url[len(url)-1] == '/' + if statInfo.IsDir() { + if !isDirectoryPath { + // If the URL doesn't end in a slash normally a http.redirect should be done, but that currently doesn't work on + // WebKit WebViews (macOS/Linux). + // So we handle this as a specific error + return fmt.Errorf("a directory has been requested without a trailing slash, please add a trailing slash to your request") + } + + filename = path.Join(filename, indexHTML) + + file, err = d.fs.Open(filename) + if err != nil { + return err + } + defer file.Close() + + statInfo, err = file.Stat() + if err != nil { + return err + } + } else if isDirectoryPath { + return fmt.Errorf("a file has been requested with a trailing slash, please remove the trailing slash from your request") + } + + var buf [512]byte + var n int + if _, haveType := rw.Header()[HeaderContentType]; !haveType { + // Detect MimeType by sniffing the first 512 bytes + n, err = file.Read(buf[:]) + if err != nil && err != io.EOF { + return err + } + + // Do the custom MimeType sniffing even though http.ServeContent would do it in case + // of an io.ReadSeeker. We would like to have a consistent behaviour in both cases. + if contentType := GetMimetype(filename, buf[:n]); contentType != "" { + rw.Header().Set(HeaderContentType, contentType) + } + } + + if fileSeeker, _ := file.(io.ReadSeeker); fileSeeker != nil { + if _, err := fileSeeker.Seek(0, io.SeekStart); err != nil { + return fmt.Errorf("seeker can't seek") + } + + http.ServeContent(rw, req, statInfo.Name(), statInfo.ModTime(), fileSeeker) + return nil + } + + rw.Header().Set(HeaderContentLength, fmt.Sprintf("%d", statInfo.Size())) + + // Write the first 512 bytes used for MimeType sniffing + _, err = io.Copy(rw, bytes.NewReader(buf[:n])) + if err != nil { + return err + } + + // Copy the remaining content of the file + _, err = io.Copy(rw, file) + return err +} + +func (d *assetFileServer) logInfo(ctx context.Context, message string, args ...interface{}) { + logInfo(ctx, "[AssetFileServerFS] "+message, args...) +} + +func (d *assetFileServer) logError(ctx context.Context, message string, args ...interface{}) { + logError(ctx, "[AssetFileServerFS] "+message, args...) +} diff --git a/v3/internal/assetserver/assetserver.go b/v3/internal/assetserver/assetserver.go new file mode 100644 index 000000000..60b2fcbfb --- /dev/null +++ b/v3/internal/assetserver/assetserver.go @@ -0,0 +1,175 @@ +package assetserver + +import ( + "fmt" + "net" + "net/http" + "net/url" + "strings" + "time" +) + +const ( + webViewRequestHeaderWindowId = "x-wails-window-id" + webViewRequestHeaderWindowName = "x-wails-window-name" + HeaderAcceptLanguage = "accept-language" +) + +type RuntimeHandler interface { + HandleRuntimeCall(w http.ResponseWriter, r *http.Request) +} + +type service struct { + Route string + Handler http.Handler +} + +type AssetServer struct { + options *Options + handler http.Handler + services []service + + assetServerWebView +} + +func NewAssetServer(options *Options) (*AssetServer, error) { + result := &AssetServer{ + options: options, + } + + userHandler := options.Handler + if userHandler == nil { + userHandler = http.NotFoundHandler() + } + + handler := http.Handler( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + result.serveHTTP(w, r, userHandler) + })) + + if middleware := options.Middleware; middleware != nil { + handler = middleware(handler) + } + + result.handler = handler + + return result, nil +} + +func (a *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + start := time.Now() + wrapped := newContentTypeSniffer(rw) + defer func() { + if _, err := wrapped.complete(); err != nil { + a.options.Logger.Error("Error writing response data.", "uri", req.RequestURI, "error", err) + } + }() + + req = req.WithContext(contextWithLogger(req.Context(), a.options.Logger)) + a.handler.ServeHTTP(wrapped, req) + + a.options.Logger.Debug( + "Asset Request:", + "windowName", req.Header.Get(webViewRequestHeaderWindowName), + "windowID", req.Header.Get(webViewRequestHeaderWindowId), + "code", wrapped.status, + "method", req.Method, + "path", req.URL.EscapedPath(), + "duration", time.Since(start), + ) +} + +func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request, userHandler http.Handler) { + if isWebSocket(req) { + // WebSockets are not supported by the AssetServer + rw.WriteHeader(http.StatusNotImplemented) + return + } + + header := rw.Header() + // TODO: I don't think this is needed now? + //if a.servingFromDisk { + // header.Add(HeaderCacheControl, "no-cache") + //} + + reqPath := req.URL.Path + switch reqPath { + case "", "/", "/index.html": + // Cache the accept-language header + // before passing the request down the chain. + acceptLanguage := req.Header.Get(HeaderAcceptLanguage) + if acceptLanguage == "" { + acceptLanguage = "en" + } + + wrapped := &fallbackResponseWriter{ + rw: rw, + req: req, + fallback: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + // Set content type for default index.html + header.Set(HeaderContentType, "text/html; charset=utf-8") + a.writeBlob(rw, indexHTML, defaultIndexHTML(acceptLanguage)) + }), + } + userHandler.ServeHTTP(wrapped, req) + + default: + // Check if the path matches a service route + for _, svc := range a.services { + if strings.HasPrefix(reqPath, svc.Route) { + req.URL.Path = strings.TrimPrefix(reqPath, svc.Route) + svc.Handler.ServeHTTP(rw, req) + return + } + } + + // Forward to the user-provided handler + userHandler.ServeHTTP(rw, req) + } +} + +func (a *AssetServer) AttachServiceHandler(route string, handler http.Handler) { + a.services = append(a.services, service{route, handler}) +} + +func (a *AssetServer) writeBlob(rw http.ResponseWriter, filename string, blob []byte) { + err := ServeFile(rw, filename, blob) + if err != nil { + a.serveError(rw, err, "Error writing file content.", "filename", filename) + } +} + +func (a *AssetServer) serveError(rw http.ResponseWriter, err error, msg string, args ...interface{}) { + args = append(args, "error", err) + a.options.Logger.Error(msg, args...) + rw.WriteHeader(http.StatusInternalServerError) +} + +func GetStartURL(userURL string) (string, error) { + devServerURL := GetDevServerURL() + startURL := baseURL.String() + if devServerURL != "" { + // Parse the port + parsedURL, err := url.Parse(devServerURL) + if err != nil { + return "", fmt.Errorf("error parsing environment variable `FRONTEND_DEVSERVER_URL`: %w. Please check your `Taskfile.yml` file", err) + } + port := parsedURL.Port() + if port != "" { + baseURL.Host = net.JoinHostPort(baseURL.Hostname(), port) + startURL = baseURL.String() + } + } + + if userURL != "" { + parsedURL, err := baseURL.Parse(userURL) + if err != nil { + return "", fmt.Errorf("error parsing URL: %w", err) + } + + startURL = parsedURL.String() + } + + return startURL, nil +} diff --git a/v3/internal/assetserver/assetserver_android.go b/v3/internal/assetserver/assetserver_android.go new file mode 100644 index 000000000..ce616f47e --- /dev/null +++ b/v3/internal/assetserver/assetserver_android.go @@ -0,0 +1,12 @@ +//go:build android + +package assetserver + +import "net/url" + +// Android uses https://wails.localhost as the base URL +// This matches the WebViewAssetLoader domain configuration +var baseURL = url.URL{ + Scheme: "https", + Host: "wails.localhost", +} diff --git a/v3/internal/assetserver/assetserver_bench_test.go b/v3/internal/assetserver/assetserver_bench_test.go new file mode 100644 index 000000000..59a55411b --- /dev/null +++ b/v3/internal/assetserver/assetserver_bench_test.go @@ -0,0 +1,242 @@ +//go:build bench + +package assetserver + +import ( + "fmt" + "log/slog" + "net/http" + "net/http/httptest" + "os" + "sync" + "testing" +) + +// resetMimeCache clears the mime cache for benchmark isolation +func resetMimeCache() { + mimeCache = sync.Map{} +} + +// BenchmarkGetMimetype measures MIME type detection performance +func BenchmarkGetMimetype(b *testing.B) { + // Reset cache between runs + resetMimeCache() + + b.Run("ByExtension/JS", func(b *testing.B) { + data := []byte("function test() {}") + for b.Loop() { + _ = GetMimetype("script.js", data) + } + }) + + resetMimeCache() + b.Run("ByExtension/CSS", func(b *testing.B) { + data := []byte(".class { color: red; }") + for b.Loop() { + _ = GetMimetype("style.css", data) + } + }) + + resetMimeCache() + b.Run("ByExtension/HTML", func(b *testing.B) { + data := []byte("") + for b.Loop() { + _ = GetMimetype("index.html", data) + } + }) + + resetMimeCache() + b.Run("ByExtension/JSON", func(b *testing.B) { + data := []byte(`{"key": "value"}`) + for b.Loop() { + _ = GetMimetype("data.json", data) + } + }) + + resetMimeCache() + b.Run("Detection/Unknown", func(b *testing.B) { + data := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05} + for b.Loop() { + _ = GetMimetype("unknown.bin", data) + } + }) + + resetMimeCache() + b.Run("Detection/PNG", func(b *testing.B) { + // PNG magic bytes + data := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00} + for b.Loop() { + _ = GetMimetype("image.unknown", data) + } + }) + + resetMimeCache() + b.Run("CacheHit", func(b *testing.B) { + data := []byte{0x00, 0x01, 0x02} + // Prime the cache + _ = GetMimetype("cached.bin", data) + b.ResetTimer() + for b.Loop() { + _ = GetMimetype("cached.bin", data) + } + }) +} + +// BenchmarkGetMimetype_Concurrent tests concurrent MIME type lookups +func BenchmarkGetMimetype_Concurrent(b *testing.B) { + resetMimeCache() + data := []byte("function test() {}") + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = GetMimetype("script.js", data) + } + }) +} + +// BenchmarkAssetServerServeHTTP measures request handling overhead +func BenchmarkAssetServerServeHTTP(b *testing.B) { + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError + 1})) + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Hello")) + }) + + server, err := NewAssetServer(&Options{ + Handler: handler, + Logger: logger, + }) + if err != nil { + b.Fatal(err) + } + + b.Run("SimpleRequest", func(b *testing.B) { + req := httptest.NewRequest("GET", "/index.html", nil) + for b.Loop() { + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + } + }) + + b.Run("WithHeaders", func(b *testing.B) { + req := httptest.NewRequest("GET", "/index.html", nil) + req.Header.Set("x-wails-window-id", "1") + req.Header.Set("x-wails-window-name", "main") + req.Header.Set("Accept-Language", "en-US,en;q=0.9") + for b.Loop() { + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + } + }) +} + +// BenchmarkAssetServerServeHTTP_Concurrent tests concurrent request handling +func BenchmarkAssetServerServeHTTP_Concurrent(b *testing.B) { + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError + 1})) + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Hello")) + }) + + server, err := NewAssetServer(&Options{ + Handler: handler, + Logger: logger, + }) + if err != nil { + b.Fatal(err) + } + + b.RunParallel(func(pb *testing.PB) { + req := httptest.NewRequest("GET", "/index.html", nil) + for pb.Next() { + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + } + }) +} + +// BenchmarkContentTypeSniffer measures the content type sniffer overhead +func BenchmarkContentTypeSniffer(b *testing.B) { + b.Run("SmallResponse", func(b *testing.B) { + data := []byte("Hello, World!") + for b.Loop() { + rr := httptest.NewRecorder() + sniffer := newContentTypeSniffer(rr) + _, _ = sniffer.Write(data) + _, _ = sniffer.complete() + } + }) + + b.Run("HTMLResponse", func(b *testing.B) { + data := []byte("Test

        Hello

        ") + for b.Loop() { + rr := httptest.NewRecorder() + sniffer := newContentTypeSniffer(rr) + _, _ = sniffer.Write(data) + _, _ = sniffer.complete() + } + }) + + b.Run("LargeResponse", func(b *testing.B) { + data := make([]byte, 64*1024) // 64KB + for i := range data { + data[i] = byte(i % 256) + } + for b.Loop() { + rr := httptest.NewRecorder() + sniffer := newContentTypeSniffer(rr) + _, _ = sniffer.Write(data) + _, _ = sniffer.complete() + } + }) +} + +// BenchmarkServiceRouting measures service route matching performance +func BenchmarkServiceRouting(b *testing.B) { + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError + 1})) + + dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + server, err := NewAssetServer(&Options{ + Handler: dummyHandler, + Logger: logger, + }) + if err != nil { + b.Fatal(err) + } + + // Attach multiple service routes + for i := 0; i < 10; i++ { + server.AttachServiceHandler(fmt.Sprintf("/api/v%d/", i), dummyHandler) + } + + b.Run("FirstRoute", func(b *testing.B) { + req := httptest.NewRequest("GET", "/api/v0/users", nil) + for b.Loop() { + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + } + }) + + b.Run("LastRoute", func(b *testing.B) { + req := httptest.NewRequest("GET", "/api/v9/users", nil) + for b.Loop() { + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + } + }) + + b.Run("NoMatch", func(b *testing.B) { + req := httptest.NewRequest("GET", "/static/app.js", nil) + for b.Loop() { + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + } + }) +} diff --git a/v3/internal/assetserver/assetserver_darwin.go b/v3/internal/assetserver/assetserver_darwin.go new file mode 100644 index 000000000..d7c647103 --- /dev/null +++ b/v3/internal/assetserver/assetserver_darwin.go @@ -0,0 +1,10 @@ +//go:build darwin && !ios + +package assetserver + +import "net/url" + +var baseURL = url.URL{ + Scheme: "wails", + Host: "localhost", +} diff --git a/v3/internal/assetserver/assetserver_dev.go b/v3/internal/assetserver/assetserver_dev.go new file mode 100644 index 000000000..e847ac480 --- /dev/null +++ b/v3/internal/assetserver/assetserver_dev.go @@ -0,0 +1,50 @@ +//go:build !production + +package assetserver + +import ( + "embed" + "io" + iofs "io/fs" +) + +//go:embed defaults +var defaultHTML embed.FS + +func defaultIndexHTML(language string) []byte { + result := []byte("index.html not found") + // Create an fs.Sub in the defaults directory + defaults, err := iofs.Sub(defaultHTML, "defaults") + if err != nil { + return result + } + // Get the 2 character language code + lang := "en" + if len(language) >= 2 { + lang = language[:2] + } + // Now we can read the index.html file in the format + // index..html. + + indexFile, err := defaults.Open("index." + lang + ".html") + if err != nil { + return result + } + + indexBytes, err := io.ReadAll(indexFile) + if err != nil { + return result + } + return indexBytes +} + +func (a *AssetServer) LogDetails() { + var info = []any{ + "middleware", a.options.Middleware != nil, + "handler", a.options.Handler != nil, + } + if devServerURL := GetDevServerURL(); devServerURL != "" { + info = append(info, "devServerURL", devServerURL) + } + a.options.Logger.Info("AssetServer Info:", info...) +} diff --git a/v3/internal/assetserver/assetserver_ios.go b/v3/internal/assetserver/assetserver_ios.go new file mode 100644 index 000000000..afc05b221 --- /dev/null +++ b/v3/internal/assetserver/assetserver_ios.go @@ -0,0 +1,10 @@ +//go:build ios + +package assetserver + +import "net/url" + +var baseURL = url.URL{ + Scheme: "wails", + Host: "localhost", +} diff --git a/v3/internal/assetserver/assetserver_linux.go b/v3/internal/assetserver/assetserver_linux.go new file mode 100644 index 000000000..ed579fad7 --- /dev/null +++ b/v3/internal/assetserver/assetserver_linux.go @@ -0,0 +1,10 @@ +//go:build linux && !android + +package assetserver + +import "net/url" + +var baseURL = url.URL{ + Scheme: "wails", + Host: "localhost", +} diff --git a/v3/internal/assetserver/assetserver_production.go b/v3/internal/assetserver/assetserver_production.go new file mode 100644 index 000000000..f698fab40 --- /dev/null +++ b/v3/internal/assetserver/assetserver_production.go @@ -0,0 +1,9 @@ +//go:build production + +package assetserver + +func defaultIndexHTML(_ string) []byte { + return []byte("index.html not found") +} + +func (a *AssetServer) LogDetails() {} diff --git a/v3/internal/assetserver/assetserver_test.go b/v3/internal/assetserver/assetserver_test.go new file mode 100644 index 000000000..755ddf09c --- /dev/null +++ b/v3/internal/assetserver/assetserver_test.go @@ -0,0 +1,244 @@ +package assetserver + +import ( + "fmt" + "log/slog" + "net/http" + "net/http/httptest" + "strconv" + "strings" + "testing" + _ "unsafe" + + "github.com/google/go-cmp/cmp" +) + +func TestContentSniffing(t *testing.T) { + longLead := strings.Repeat(" ", 512-6) + + tests := map[string]struct { + Expect string + Status int + Header map[string][]string + Body []string + }{ + "/simple": { + Expect: "text/html; charset=utf-8", + Body: []string{"Hello!"}, + }, + "/split": { + Expect: "text/html; charset=utf-8", + Body: []string{ + "Hello!", + "", + }, + }, + "/lead/short/simple": { + Expect: "text/html; charset=utf-8", + Body: []string{ + " " + "Hello!", + }, + }, + "/lead/short/split": { + Expect: "text/html; charset=utf-8", + Body: []string{ + " ", + "Hello!", + }, + }, + "/lead/long/simple": { + Expect: "text/html; charset=utf-8", + Body: []string{ + longLead + "Hello!", + }, + }, + "/lead/long/split": { + Expect: "text/html; charset=utf-8", + Body: []string{ + longLead, + "Hello!", + }, + }, + "/lead/toolong/simple": { + Expect: "text/plain; charset=utf-8", + Body: []string{ + "Hello" + longLead + "Hello!", + }, + }, + "/lead/toolong/split": { + Expect: "text/plain; charset=utf-8", + Body: []string{ + "Hello" + longLead, + "Hello!", + }, + }, + "/header": { + Expect: "text/html; charset=utf-8", + Status: http.StatusForbidden, + Header: map[string][]string{ + "X-Custom": {"CustomValue"}, + }, + Body: []string{"Hello!"}, + }, + "/custom": { + Expect: "text/plain;charset=utf-8", + Header: map[string][]string{ + "Content-Type": {"text/plain;charset=utf-8"}, + }, + Body: []string{"Hello!"}, + }, + } + + srv, err := NewAssetServer(&Options{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + test, ok := tests[r.URL.Path] + if !ok { + w.WriteHeader(http.StatusNotFound) + return + } + + for key, values := range test.Header { + for _, value := range values { + w.Header().Add(key, value) + } + } + + if test.Status != 0 { + w.WriteHeader(test.Status) + } + + for _, chunk := range test.Body { + w.Write([]byte(chunk)) + } + }), + Logger: slog.Default(), + }) + if err != nil { + t.Fatal("AssetServer initialisation failed: ", err) + } + + for path, test := range tests { + t.Run(path[1:], func(t *testing.T) { + res := httptest.NewRecorder() + + req, err := http.NewRequest(http.MethodGet, path, nil) + if err != nil { + t.Fatal("http.NewRequest failed: ", err) + } + + srv.ServeHTTP(res, req) + + expectedStatus := http.StatusOK + if test.Status != 0 { + expectedStatus = test.Status + } + if res.Code != expectedStatus { + t.Errorf("Status code mismatch: want %d, got %d", expectedStatus, res.Code) + } + + if ct := res.Header().Get("Content-Type"); ct != test.Expect { + t.Errorf("Content type mismatch: want '%s', got '%s'", test.Expect, ct) + } + + for key, values := range test.Header { + if diff := cmp.Diff(values, res.Header().Values(key)); diff != "" { + t.Errorf("Header '%s' mismatch (-want +got):\n%s", key, diff) + } + } + + if diff := cmp.Diff(strings.Join(test.Body, ""), res.Body.String()); diff != "" { + t.Errorf("Response body mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestIndexFallback(t *testing.T) { + // Paths to try and whether a 404 should trigger a fallback. + paths := map[string]bool{ + "": true, + "/": true, + "/index": false, + "/index.html": true, + "/other": false, + } + + statuses := []int{ + http.StatusOK, + http.StatusNotFound, + http.StatusForbidden, + } + + header := map[string][]string{ + "X-Custom": {"CustomValue"}, + } + body := "Hello!" + + srv, err := NewAssetServer(&Options{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + for key, values := range header { + for _, value := range values { + w.Header().Add(key, value) + } + } + + status, err := strconv.Atoi(r.URL.Query().Get("status")) + if err == nil && status != 0 && status != http.StatusOK { + w.WriteHeader(status) + } + + w.Write([]byte(body)) + }), + Logger: slog.Default(), + }) + if err != nil { + t.Fatal("AssetServer initialisation failed: ", err) + } + + for path, fallback := range paths { + for _, status := range statuses { + key := "" + if len(path) > 0 { + key = path[1:] + } + + t.Run(fmt.Sprintf("%s/status=%d", key, status), func(t *testing.T) { + res := httptest.NewRecorder() + + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s?status=%d", path, status), nil) + if err != nil { + t.Fatal("http.NewRequest failed: ", err) + } + + srv.ServeHTTP(res, req) + + fallbackTriggered := false + if status == http.StatusNotFound && fallback { + status = http.StatusOK + fallbackTriggered = true + } + + if res.Code != status { + t.Errorf("Status code mismatch: want %d, got %d", status, res.Code) + } + + if fallbackTriggered { + if cmp.Equal(body, res.Body.String()) { + t.Errorf("Fallback response has the same body as not found response") + } + return + } else { + for key, values := range header { + if diff := cmp.Diff(values, res.Header().Values(key)); diff != "" { + t.Errorf("Header '%s' mismatch (-want +got):\n%s", key, diff) + } + } + + if diff := cmp.Diff(body, res.Body.String()); diff != "" { + t.Errorf("Response body mismatch (-want +got):\n%s", diff) + } + } + }) + } + } +} diff --git a/v3/internal/assetserver/assetserver_webview.go b/v3/internal/assetserver/assetserver_webview.go new file mode 100644 index 000000000..21c01ede2 --- /dev/null +++ b/v3/internal/assetserver/assetserver_webview.go @@ -0,0 +1,198 @@ +package assetserver + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + + "github.com/wailsapp/wails/v3/internal/assetserver/webview" +) + +type assetServerWebView struct { + // ExpectedWebViewHost is checked against the Request Host of every WebViewRequest, other hosts won't be processed. + ExpectedWebViewHost string + + dispatchInit sync.Once + dispatchReqC chan<- webview.Request + dispatchWorkers int +} + +// ServeWebViewRequest processes the HTTP Request asynchronously by faking a golang HTTP Server. +// The request will be finished with a StatusNotImplemented code if no handler has written to the response. +// The AssetServer takes ownership of the request and the caller mustn't close it or access it in any other way. +func (a *AssetServer) ServeWebViewRequest(req webview.Request) { + a.dispatchInit.Do(func() { + workers := a.dispatchWorkers + if workers <= 0 { + return + } + + workerC := make(chan webview.Request, workers*2) + for i := 0; i < workers; i++ { + go func() { + for req := range workerC { + a.processWebViewRequest(req) + } + }() + } + + dispatchC := make(chan webview.Request) + go queueingDispatcher(50, dispatchC, workerC) + + a.dispatchReqC = dispatchC + }) + + if a.dispatchReqC == nil { + go a.processWebViewRequest(req) + } else { + a.dispatchReqC <- req + } +} + +func (a *AssetServer) processWebViewRequest(r webview.Request) { + uri, _ := r.URL() + a.processWebViewRequestInternal(r) + if err := r.Close(); err != nil { + a.options.Logger.Error("Unable to call close for request for uri.", "uri", uri) + } +} + +// processHTTPRequest processes the HTTP Request by faking a golang HTTP Server. +// The request will be finished with a StatusNotImplemented code if no handler has written to the response. +func (a *AssetServer) processWebViewRequestInternal(r webview.Request) { + uri := "unknown" + var err error + + wrw := r.Response() + defer func() { + if err := wrw.Finish(); err != nil { + a.options.Logger.Error("Error finishing request.", "uri", uri, "error", err) + } + }() + + rw := newContentTypeSniffer(wrw) // Make sure we have a Content-Type sniffer + defer func() { + if _, err := rw.complete(); err != nil { + a.options.Logger.Error("Error writing response data.", "uri", uri, "error", err) + } + }() + defer rw.WriteHeader(http.StatusNotImplemented) // This is a NOP when a handler has already written and set the status + + uri, err = r.URL() + if err != nil { + a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("URL: %w", err)) + return + } + + method, err := r.Method() + if err != nil { + a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Method: %w", err)) + return + } + + header, err := r.Header() + if err != nil { + a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Header: %w", err)) + return + } + + body, err := r.Body() + if err != nil { + a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Body: %w", err)) + return + } + + if body == nil { + body = http.NoBody + } + defer body.Close() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, method, uri, body) + if err != nil { + a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Request: %w", err)) + return + } + + // For server requests, the URL is parsed from the URI supplied on the Request-Line as stored in RequestURI. For + // most requests, fields other than Path and RawQuery will be empty. (See RFC 7230, Section 5.3) + req.URL.Scheme = "" + req.URL.Host = "" + req.URL.Fragment = "" + req.URL.RawFragment = "" + + if requestURL := req.URL; req.RequestURI == "" && requestURL != nil { + req.RequestURI = requestURL.String() + } + + req.Header = header + + if req.RemoteAddr == "" { + // 192.0.2.0/24 is "TEST-NET" in RFC 5737 + req.RemoteAddr = "192.0.2.1:1234" + } + + if req.RequestURI == "" && req.URL != nil { + req.RequestURI = req.URL.String() + } + + if req.ContentLength == 0 { + req.ContentLength, _ = strconv.ParseInt(req.Header.Get(HeaderContentLength), 10, 64) + } else { + req.Header.Set(HeaderContentLength, fmt.Sprintf("%d", req.ContentLength)) + } + + if host := req.Header.Get(HeaderHost); host != "" { + req.Host = host + } + + // iOS uses "localhost" while other platforms might use different hosts + // Skip host check for iOS requests from wails:// scheme + if expectedHost := a.ExpectedWebViewHost; expectedHost != "" && expectedHost != req.Host && !strings.HasPrefix(uri, "wails://") { + a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("expected host '%s' in request, but was '%s'", expectedHost, req.Host)) + return + } + + a.ServeHTTP(rw, req) +} + +func (a *AssetServer) webviewRequestErrorHandler(uri string, rw http.ResponseWriter, err error) { + logInfo := uri + if uri, err := url.ParseRequestURI(uri); err == nil { + logInfo = strings.Replace(logInfo, fmt.Sprintf("%s://%s", uri.Scheme, uri.Host), "", 1) + } + + a.options.Logger.Error("Error processing request (HttpResponse=500)", "details", logInfo, "error", err) + http.Error(rw, err.Error(), http.StatusInternalServerError) +} + +func queueingDispatcher[T any](minQueueSize uint, inC <-chan T, outC chan<- T) { + q := newRingqueue[T](minQueueSize) + for { + in, ok := <-inC + if !ok { + return + } + + q.Add(in) + for q.Len() != 0 { + out, _ := q.Peek() + select { + case outC <- out: + q.Remove() + case in, ok := <-inC: + if !ok { + return + } + + q.Add(in) + } + } + } +} diff --git a/v3/internal/assetserver/assetserver_windows.go b/v3/internal/assetserver/assetserver_windows.go new file mode 100644 index 000000000..22deda4d2 --- /dev/null +++ b/v3/internal/assetserver/assetserver_windows.go @@ -0,0 +1,8 @@ +package assetserver + +import "net/url" + +var baseURL = url.URL{ + Scheme: "http", + Host: "wails.localhost", +} diff --git a/v3/internal/assetserver/build_dev.go b/v3/internal/assetserver/build_dev.go new file mode 100644 index 000000000..7747a7142 --- /dev/null +++ b/v3/internal/assetserver/build_dev.go @@ -0,0 +1,41 @@ +//go:build !production + +package assetserver + +import ( + _ "embed" + "io/fs" + "net/http" + "net/http/httputil" + "net/url" + "os" +) + +func NewAssetFileServer(vfs fs.FS) http.Handler { + devServerURL := GetDevServerURL() + if devServerURL == "" { + return newAssetFileServerFS(vfs) + } + + parsedURL, err := url.Parse(devServerURL) + if err != nil { + return http.HandlerFunc( + func(rw http.ResponseWriter, req *http.Request) { + logError(req.Context(), "[ExternalAssetHandler] Invalid FRONTEND_DEVSERVER_URL. Should be valid URL", "error", err.Error()) + http.Error(rw, err.Error(), http.StatusInternalServerError) + }) + + } + + proxy := httputil.NewSingleHostReverseProxy(parsedURL) + proxy.ErrorHandler = func(rw http.ResponseWriter, r *http.Request, err error) { + logError(r.Context(), "[ExternalAssetHandler] Proxy error", "error", err.Error()) + rw.WriteHeader(http.StatusBadGateway) + } + + return proxy +} + +func GetDevServerURL() string { + return os.Getenv("FRONTEND_DEVSERVER_URL") +} diff --git a/v3/internal/assetserver/build_production.go b/v3/internal/assetserver/build_production.go new file mode 100644 index 000000000..98d5b4ffd --- /dev/null +++ b/v3/internal/assetserver/build_production.go @@ -0,0 +1,16 @@ +//go:build production + +package assetserver + +import ( + "io/fs" + "net/http" +) + +func NewAssetFileServer(vfs fs.FS) http.Handler { + return newAssetFileServerFS(vfs) +} + +func GetDevServerURL() string { + return "" +} diff --git a/v3/internal/assetserver/bundled_assetserver.go b/v3/internal/assetserver/bundled_assetserver.go new file mode 100644 index 000000000..15297cd37 --- /dev/null +++ b/v3/internal/assetserver/bundled_assetserver.go @@ -0,0 +1,33 @@ +package assetserver + +import ( + "github.com/wailsapp/wails/v3/internal/assetserver/bundledassets" + "io/fs" + "net/http" + "strings" +) + +type BundledAssetServer struct { + handler http.Handler +} + +func NewBundledAssetFileServer(fs fs.FS) *BundledAssetServer { + return &BundledAssetServer{ + handler: NewAssetFileServer(fs), + } +} + +func (b *BundledAssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if strings.HasPrefix(req.URL.Path, "/wails/") { + // Strip the /wails prefix + req.URL.Path = req.URL.Path[6:] + switch req.URL.Path { + case "/runtime.js": + rw.Header().Set("Content-Type", "application/javascript") + rw.Write([]byte(bundledassets.RuntimeJS)) + return + } + return + } + b.handler.ServeHTTP(rw, req) +} diff --git a/v3/internal/assetserver/bundledassets/runtime.debug.js b/v3/internal/assetserver/bundledassets/runtime.debug.js new file mode 100644 index 000000000..00bde7424 --- /dev/null +++ b/v3/internal/assetserver/bundledassets/runtime.debug.js @@ -0,0 +1,2774 @@ +var __defProp = Object.defineProperty; +var __defProps = Object.defineProperties; +var __getOwnPropDescs = Object.getOwnPropertyDescriptors; +var __getOwnPropSymbols = Object.getOwnPropertySymbols; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __propIsEnum = Object.prototype.propertyIsEnumerable; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __spreadValues = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + if (__getOwnPropSymbols) + for (var prop of __getOwnPropSymbols(b)) { + if (__propIsEnum.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + } + return a; +}; +var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; + +// desktop/@wailsio/runtime/src/index.ts +var index_exports = {}; +__export(index_exports, { + Application: () => application_exports, + Browser: () => browser_exports, + Call: () => calls_exports, + CancelError: () => CancelError, + CancellablePromise: () => CancellablePromise, + CancelledRejectionError: () => CancelledRejectionError, + Clipboard: () => clipboard_exports, + Create: () => create_exports, + Dialogs: () => dialogs_exports, + Events: () => events_exports, + Flags: () => flags_exports, + IOS: () => ios_exports, + Screens: () => screens_exports, + System: () => system_exports, + WML: () => wml_exports, + Window: () => window_default, + clientId: () => clientId, + getTransport: () => getTransport, + loadOptionalScript: () => loadOptionalScript, + objectNames: () => objectNames, + setTransport: () => setTransport +}); + +// desktop/@wailsio/runtime/src/wml.ts +var wml_exports = {}; +__export(wml_exports, { + Enable: () => Enable, + Reload: () => Reload +}); + +// desktop/@wailsio/runtime/src/browser.ts +var browser_exports = {}; +__export(browser_exports, { + OpenURL: () => OpenURL +}); + +// desktop/@wailsio/runtime/src/nanoid.ts +var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; +function nanoid(size = 21) { + let id = ""; + let i = size | 0; + while (i--) { + id += urlAlphabet[Math.random() * 64 | 0]; + } + return id; +} + +// desktop/@wailsio/runtime/src/runtime.ts +var runtimeURL = window.location.origin + "/wails/runtime"; +var objectNames = Object.freeze({ + Call: 0, + Clipboard: 1, + Application: 2, + Events: 3, + ContextMenu: 4, + Dialog: 5, + Window: 6, + Screens: 7, + System: 8, + Browser: 9, + CancelCall: 10, + IOS: 11 +}); +var clientId = nanoid(); +var customTransport = null; +function setTransport(transport) { + customTransport = transport; +} +function getTransport() { + return customTransport; +} +function newRuntimeCaller(object, windowName = "") { + return function(method, args = null) { + return runtimeCallWithID(object, method, windowName, args); + }; +} +async function runtimeCallWithID(objectID, method, windowName, args) { + var _a2, _b; + if (customTransport) { + return customTransport.call(objectID, method, windowName, args); + } + let url = new URL(runtimeURL); + let body = { + object: objectID, + method + }; + if (args !== null && args !== void 0) { + body.args = args; + } + let headers = { + ["x-wails-client-id"]: clientId, + ["Content-Type"]: "application/json" + }; + if (windowName) { + headers["x-wails-window-name"] = windowName; + } + let response = await fetch(url, { + method: "POST", + headers, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(await response.text()); + } + if (((_b = (_a2 = response.headers.get("Content-Type")) == null ? void 0 : _a2.indexOf("application/json")) != null ? _b : -1) !== -1) { + return response.json(); + } else { + return response.text(); + } +} + +// desktop/@wailsio/runtime/src/browser.ts +var call = newRuntimeCaller(objectNames.Browser); +var BrowserOpenURL = 0; +function OpenURL(url) { + return call(BrowserOpenURL, { url: url.toString() }); +} + +// desktop/@wailsio/runtime/src/dialogs.ts +var dialogs_exports = {}; +__export(dialogs_exports, { + Error: () => Error2, + Info: () => Info, + OpenFile: () => OpenFile, + Question: () => Question, + SaveFile: () => SaveFile, + Warning: () => Warning +}); +window._wails = window._wails || {}; +var call2 = newRuntimeCaller(objectNames.Dialog); +var DialogInfo = 0; +var DialogWarning = 1; +var DialogError = 2; +var DialogQuestion = 3; +var DialogOpenFile = 4; +var DialogSaveFile = 5; +function dialog(type, options = {}) { + return call2(type, options); +} +function Info(options) { + return dialog(DialogInfo, options); +} +function Warning(options) { + return dialog(DialogWarning, options); +} +function Error2(options) { + return dialog(DialogError, options); +} +function Question(options) { + return dialog(DialogQuestion, options); +} +function OpenFile(options) { + var _a2; + return (_a2 = dialog(DialogOpenFile, options)) != null ? _a2 : []; +} +function SaveFile(options) { + return dialog(DialogSaveFile, options); +} + +// desktop/@wailsio/runtime/src/events.ts +var events_exports = {}; +__export(events_exports, { + Emit: () => Emit, + Off: () => Off, + OffAll: () => OffAll, + On: () => On, + OnMultiple: () => OnMultiple, + Once: () => Once, + Types: () => Types, + WailsEvent: () => WailsEvent +}); + +// desktop/@wailsio/runtime/src/listener.ts +var eventListeners = /* @__PURE__ */ new Map(); +var Listener = class { + constructor(eventName, callback, maxCallbacks) { + this.eventName = eventName; + this.callback = callback; + this.maxCallbacks = maxCallbacks || -1; + } + dispatch(data) { + try { + this.callback(data); + } catch (err) { + console.error(err); + } + if (this.maxCallbacks === -1) return false; + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + } +}; +function listenerOff(listener) { + let listeners = eventListeners.get(listener.eventName); + if (!listeners) { + return; + } + listeners = listeners.filter((l) => l !== listener); + if (listeners.length === 0) { + eventListeners.delete(listener.eventName); + } else { + eventListeners.set(listener.eventName, listeners); + } +} + +// desktop/@wailsio/runtime/src/create.ts +var create_exports = {}; +__export(create_exports, { + Any: () => Any, + Array: () => Array2, + ByteSlice: () => ByteSlice, + Events: () => Events, + Map: () => Map2, + Nullable: () => Nullable, + Struct: () => Struct +}); +function Any(source) { + return source; +} +function ByteSlice(source) { + return source == null ? "" : source; +} +function Array2(element) { + if (element === Any) { + return (source) => source === null ? [] : source; + } + return (source) => { + if (source === null) { + return []; + } + for (let i = 0; i < source.length; i++) { + source[i] = element(source[i]); + } + return source; + }; +} +function Map2(key, value) { + if (value === Any) { + return (source) => source === null ? {} : source; + } + return (source) => { + if (source === null) { + return {}; + } + for (const key2 in source) { + source[key2] = value(source[key2]); + } + return source; + }; +} +function Nullable(element) { + if (element === Any) { + return Any; + } + return (source) => source === null ? null : element(source); +} +function Struct(createField) { + let allAny = true; + for (const name in createField) { + if (createField[name] !== Any) { + allAny = false; + break; + } + } + if (allAny) { + return Any; + } + return (source) => { + for (const name in createField) { + if (name in source) { + source[name] = createField[name](source[name]); + } + } + return source; + }; +} +var Events = {}; + +// desktop/@wailsio/runtime/src/event_types.ts +var Types = Object.freeze({ + Windows: Object.freeze({ + APMPowerSettingChange: "windows:APMPowerSettingChange", + APMPowerStatusChange: "windows:APMPowerStatusChange", + APMResumeAutomatic: "windows:APMResumeAutomatic", + APMResumeSuspend: "windows:APMResumeSuspend", + APMSuspend: "windows:APMSuspend", + ApplicationStarted: "windows:ApplicationStarted", + SystemThemeChanged: "windows:SystemThemeChanged", + WebViewNavigationCompleted: "windows:WebViewNavigationCompleted", + WindowActive: "windows:WindowActive", + WindowBackgroundErase: "windows:WindowBackgroundErase", + WindowClickActive: "windows:WindowClickActive", + WindowClosing: "windows:WindowClosing", + WindowDidMove: "windows:WindowDidMove", + WindowDidResize: "windows:WindowDidResize", + WindowDPIChanged: "windows:WindowDPIChanged", + WindowDragDrop: "windows:WindowDragDrop", + WindowDragEnter: "windows:WindowDragEnter", + WindowDragLeave: "windows:WindowDragLeave", + WindowDragOver: "windows:WindowDragOver", + WindowEndMove: "windows:WindowEndMove", + WindowEndResize: "windows:WindowEndResize", + WindowFullscreen: "windows:WindowFullscreen", + WindowHide: "windows:WindowHide", + WindowInactive: "windows:WindowInactive", + WindowKeyDown: "windows:WindowKeyDown", + WindowKeyUp: "windows:WindowKeyUp", + WindowKillFocus: "windows:WindowKillFocus", + WindowNonClientHit: "windows:WindowNonClientHit", + WindowNonClientMouseDown: "windows:WindowNonClientMouseDown", + WindowNonClientMouseLeave: "windows:WindowNonClientMouseLeave", + WindowNonClientMouseMove: "windows:WindowNonClientMouseMove", + WindowNonClientMouseUp: "windows:WindowNonClientMouseUp", + WindowPaint: "windows:WindowPaint", + WindowRestore: "windows:WindowRestore", + WindowSetFocus: "windows:WindowSetFocus", + WindowShow: "windows:WindowShow", + WindowStartMove: "windows:WindowStartMove", + WindowStartResize: "windows:WindowStartResize", + WindowUnFullscreen: "windows:WindowUnFullscreen", + WindowZOrderChanged: "windows:WindowZOrderChanged", + WindowMinimise: "windows:WindowMinimise", + WindowUnMinimise: "windows:WindowUnMinimise", + WindowMaximise: "windows:WindowMaximise", + WindowUnMaximise: "windows:WindowUnMaximise" + }), + Mac: Object.freeze({ + ApplicationDidBecomeActive: "mac:ApplicationDidBecomeActive", + ApplicationDidChangeBackingProperties: "mac:ApplicationDidChangeBackingProperties", + ApplicationDidChangeEffectiveAppearance: "mac:ApplicationDidChangeEffectiveAppearance", + ApplicationDidChangeIcon: "mac:ApplicationDidChangeIcon", + ApplicationDidChangeOcclusionState: "mac:ApplicationDidChangeOcclusionState", + ApplicationDidChangeScreenParameters: "mac:ApplicationDidChangeScreenParameters", + ApplicationDidChangeStatusBarFrame: "mac:ApplicationDidChangeStatusBarFrame", + ApplicationDidChangeStatusBarOrientation: "mac:ApplicationDidChangeStatusBarOrientation", + ApplicationDidChangeTheme: "mac:ApplicationDidChangeTheme", + ApplicationDidFinishLaunching: "mac:ApplicationDidFinishLaunching", + ApplicationDidHide: "mac:ApplicationDidHide", + ApplicationDidResignActive: "mac:ApplicationDidResignActive", + ApplicationDidUnhide: "mac:ApplicationDidUnhide", + ApplicationDidUpdate: "mac:ApplicationDidUpdate", + ApplicationShouldHandleReopen: "mac:ApplicationShouldHandleReopen", + ApplicationWillBecomeActive: "mac:ApplicationWillBecomeActive", + ApplicationWillFinishLaunching: "mac:ApplicationWillFinishLaunching", + ApplicationWillHide: "mac:ApplicationWillHide", + ApplicationWillResignActive: "mac:ApplicationWillResignActive", + ApplicationWillTerminate: "mac:ApplicationWillTerminate", + ApplicationWillUnhide: "mac:ApplicationWillUnhide", + ApplicationWillUpdate: "mac:ApplicationWillUpdate", + MenuDidAddItem: "mac:MenuDidAddItem", + MenuDidBeginTracking: "mac:MenuDidBeginTracking", + MenuDidClose: "mac:MenuDidClose", + MenuDidDisplayItem: "mac:MenuDidDisplayItem", + MenuDidEndTracking: "mac:MenuDidEndTracking", + MenuDidHighlightItem: "mac:MenuDidHighlightItem", + MenuDidOpen: "mac:MenuDidOpen", + MenuDidPopUp: "mac:MenuDidPopUp", + MenuDidRemoveItem: "mac:MenuDidRemoveItem", + MenuDidSendAction: "mac:MenuDidSendAction", + MenuDidSendActionToItem: "mac:MenuDidSendActionToItem", + MenuDidUpdate: "mac:MenuDidUpdate", + MenuWillAddItem: "mac:MenuWillAddItem", + MenuWillBeginTracking: "mac:MenuWillBeginTracking", + MenuWillDisplayItem: "mac:MenuWillDisplayItem", + MenuWillEndTracking: "mac:MenuWillEndTracking", + MenuWillHighlightItem: "mac:MenuWillHighlightItem", + MenuWillOpen: "mac:MenuWillOpen", + MenuWillPopUp: "mac:MenuWillPopUp", + MenuWillRemoveItem: "mac:MenuWillRemoveItem", + MenuWillSendAction: "mac:MenuWillSendAction", + MenuWillSendActionToItem: "mac:MenuWillSendActionToItem", + MenuWillUpdate: "mac:MenuWillUpdate", + WebViewDidCommitNavigation: "mac:WebViewDidCommitNavigation", + WebViewDidFinishNavigation: "mac:WebViewDidFinishNavigation", + WebViewDidReceiveServerRedirectForProvisionalNavigation: "mac:WebViewDidReceiveServerRedirectForProvisionalNavigation", + WebViewDidStartProvisionalNavigation: "mac:WebViewDidStartProvisionalNavigation", + WindowDidBecomeKey: "mac:WindowDidBecomeKey", + WindowDidBecomeMain: "mac:WindowDidBecomeMain", + WindowDidBeginSheet: "mac:WindowDidBeginSheet", + WindowDidChangeAlpha: "mac:WindowDidChangeAlpha", + WindowDidChangeBackingLocation: "mac:WindowDidChangeBackingLocation", + WindowDidChangeBackingProperties: "mac:WindowDidChangeBackingProperties", + WindowDidChangeCollectionBehavior: "mac:WindowDidChangeCollectionBehavior", + WindowDidChangeEffectiveAppearance: "mac:WindowDidChangeEffectiveAppearance", + WindowDidChangeOcclusionState: "mac:WindowDidChangeOcclusionState", + WindowDidChangeOrderingMode: "mac:WindowDidChangeOrderingMode", + WindowDidChangeScreen: "mac:WindowDidChangeScreen", + WindowDidChangeScreenParameters: "mac:WindowDidChangeScreenParameters", + WindowDidChangeScreenProfile: "mac:WindowDidChangeScreenProfile", + WindowDidChangeScreenSpace: "mac:WindowDidChangeScreenSpace", + WindowDidChangeScreenSpaceProperties: "mac:WindowDidChangeScreenSpaceProperties", + WindowDidChangeSharingType: "mac:WindowDidChangeSharingType", + WindowDidChangeSpace: "mac:WindowDidChangeSpace", + WindowDidChangeSpaceOrderingMode: "mac:WindowDidChangeSpaceOrderingMode", + WindowDidChangeTitle: "mac:WindowDidChangeTitle", + WindowDidChangeToolbar: "mac:WindowDidChangeToolbar", + WindowDidDeminiaturize: "mac:WindowDidDeminiaturize", + WindowDidEndSheet: "mac:WindowDidEndSheet", + WindowDidEnterFullScreen: "mac:WindowDidEnterFullScreen", + WindowDidEnterVersionBrowser: "mac:WindowDidEnterVersionBrowser", + WindowDidExitFullScreen: "mac:WindowDidExitFullScreen", + WindowDidExitVersionBrowser: "mac:WindowDidExitVersionBrowser", + WindowDidExpose: "mac:WindowDidExpose", + WindowDidFocus: "mac:WindowDidFocus", + WindowDidMiniaturize: "mac:WindowDidMiniaturize", + WindowDidMove: "mac:WindowDidMove", + WindowDidOrderOffScreen: "mac:WindowDidOrderOffScreen", + WindowDidOrderOnScreen: "mac:WindowDidOrderOnScreen", + WindowDidResignKey: "mac:WindowDidResignKey", + WindowDidResignMain: "mac:WindowDidResignMain", + WindowDidResize: "mac:WindowDidResize", + WindowDidUpdate: "mac:WindowDidUpdate", + WindowDidUpdateAlpha: "mac:WindowDidUpdateAlpha", + WindowDidUpdateCollectionBehavior: "mac:WindowDidUpdateCollectionBehavior", + WindowDidUpdateCollectionProperties: "mac:WindowDidUpdateCollectionProperties", + WindowDidUpdateShadow: "mac:WindowDidUpdateShadow", + WindowDidUpdateTitle: "mac:WindowDidUpdateTitle", + WindowDidUpdateToolbar: "mac:WindowDidUpdateToolbar", + WindowDidZoom: "mac:WindowDidZoom", + WindowFileDraggingEntered: "mac:WindowFileDraggingEntered", + WindowFileDraggingExited: "mac:WindowFileDraggingExited", + WindowFileDraggingPerformed: "mac:WindowFileDraggingPerformed", + WindowHide: "mac:WindowHide", + WindowMaximise: "mac:WindowMaximise", + WindowUnMaximise: "mac:WindowUnMaximise", + WindowMinimise: "mac:WindowMinimise", + WindowUnMinimise: "mac:WindowUnMinimise", + WindowShouldClose: "mac:WindowShouldClose", + WindowShow: "mac:WindowShow", + WindowWillBecomeKey: "mac:WindowWillBecomeKey", + WindowWillBecomeMain: "mac:WindowWillBecomeMain", + WindowWillBeginSheet: "mac:WindowWillBeginSheet", + WindowWillChangeOrderingMode: "mac:WindowWillChangeOrderingMode", + WindowWillClose: "mac:WindowWillClose", + WindowWillDeminiaturize: "mac:WindowWillDeminiaturize", + WindowWillEnterFullScreen: "mac:WindowWillEnterFullScreen", + WindowWillEnterVersionBrowser: "mac:WindowWillEnterVersionBrowser", + WindowWillExitFullScreen: "mac:WindowWillExitFullScreen", + WindowWillExitVersionBrowser: "mac:WindowWillExitVersionBrowser", + WindowWillFocus: "mac:WindowWillFocus", + WindowWillMiniaturize: "mac:WindowWillMiniaturize", + WindowWillMove: "mac:WindowWillMove", + WindowWillOrderOffScreen: "mac:WindowWillOrderOffScreen", + WindowWillOrderOnScreen: "mac:WindowWillOrderOnScreen", + WindowWillResignMain: "mac:WindowWillResignMain", + WindowWillResize: "mac:WindowWillResize", + WindowWillUnfocus: "mac:WindowWillUnfocus", + WindowWillUpdate: "mac:WindowWillUpdate", + WindowWillUpdateAlpha: "mac:WindowWillUpdateAlpha", + WindowWillUpdateCollectionBehavior: "mac:WindowWillUpdateCollectionBehavior", + WindowWillUpdateCollectionProperties: "mac:WindowWillUpdateCollectionProperties", + WindowWillUpdateShadow: "mac:WindowWillUpdateShadow", + WindowWillUpdateTitle: "mac:WindowWillUpdateTitle", + WindowWillUpdateToolbar: "mac:WindowWillUpdateToolbar", + WindowWillUpdateVisibility: "mac:WindowWillUpdateVisibility", + WindowWillUseStandardFrame: "mac:WindowWillUseStandardFrame", + WindowZoomIn: "mac:WindowZoomIn", + WindowZoomOut: "mac:WindowZoomOut", + WindowZoomReset: "mac:WindowZoomReset" + }), + Linux: Object.freeze({ + ApplicationStartup: "linux:ApplicationStartup", + SystemThemeChanged: "linux:SystemThemeChanged", + WindowDeleteEvent: "linux:WindowDeleteEvent", + WindowDidMove: "linux:WindowDidMove", + WindowDidResize: "linux:WindowDidResize", + WindowFocusIn: "linux:WindowFocusIn", + WindowFocusOut: "linux:WindowFocusOut", + WindowLoadStarted: "linux:WindowLoadStarted", + WindowLoadRedirected: "linux:WindowLoadRedirected", + WindowLoadCommitted: "linux:WindowLoadCommitted", + WindowLoadFinished: "linux:WindowLoadFinished" + }), + iOS: Object.freeze({ + ApplicationDidBecomeActive: "ios:ApplicationDidBecomeActive", + ApplicationDidEnterBackground: "ios:ApplicationDidEnterBackground", + ApplicationDidFinishLaunching: "ios:ApplicationDidFinishLaunching", + ApplicationDidReceiveMemoryWarning: "ios:ApplicationDidReceiveMemoryWarning", + ApplicationWillEnterForeground: "ios:ApplicationWillEnterForeground", + ApplicationWillResignActive: "ios:ApplicationWillResignActive", + ApplicationWillTerminate: "ios:ApplicationWillTerminate", + WindowDidLoad: "ios:WindowDidLoad", + WindowWillAppear: "ios:WindowWillAppear", + WindowDidAppear: "ios:WindowDidAppear", + WindowWillDisappear: "ios:WindowWillDisappear", + WindowDidDisappear: "ios:WindowDidDisappear", + WindowSafeAreaInsetsChanged: "ios:WindowSafeAreaInsetsChanged", + WindowOrientationChanged: "ios:WindowOrientationChanged", + WindowTouchBegan: "ios:WindowTouchBegan", + WindowTouchMoved: "ios:WindowTouchMoved", + WindowTouchEnded: "ios:WindowTouchEnded", + WindowTouchCancelled: "ios:WindowTouchCancelled", + WebViewDidStartNavigation: "ios:WebViewDidStartNavigation", + WebViewDidFinishNavigation: "ios:WebViewDidFinishNavigation", + WebViewDidFailNavigation: "ios:WebViewDidFailNavigation", + WebViewDecidePolicyForNavigationAction: "ios:WebViewDecidePolicyForNavigationAction" + }), + Common: Object.freeze({ + ApplicationOpenedWithFile: "common:ApplicationOpenedWithFile", + ApplicationStarted: "common:ApplicationStarted", + ApplicationLaunchedWithUrl: "common:ApplicationLaunchedWithUrl", + ThemeChanged: "common:ThemeChanged", + WindowClosing: "common:WindowClosing", + WindowDidMove: "common:WindowDidMove", + WindowDidResize: "common:WindowDidResize", + WindowDPIChanged: "common:WindowDPIChanged", + WindowFilesDropped: "common:WindowFilesDropped", + WindowFocus: "common:WindowFocus", + WindowFullscreen: "common:WindowFullscreen", + WindowHide: "common:WindowHide", + WindowLostFocus: "common:WindowLostFocus", + WindowMaximise: "common:WindowMaximise", + WindowMinimise: "common:WindowMinimise", + WindowToggleFrameless: "common:WindowToggleFrameless", + WindowRestore: "common:WindowRestore", + WindowRuntimeReady: "common:WindowRuntimeReady", + WindowShow: "common:WindowShow", + WindowUnFullscreen: "common:WindowUnFullscreen", + WindowUnMaximise: "common:WindowUnMaximise", + WindowUnMinimise: "common:WindowUnMinimise", + WindowZoom: "common:WindowZoom", + WindowZoomIn: "common:WindowZoomIn", + WindowZoomOut: "common:WindowZoomOut", + WindowZoomReset: "common:WindowZoomReset" + }) +}); + +// desktop/@wailsio/runtime/src/events.ts +window._wails = window._wails || {}; +window._wails.dispatchWailsEvent = dispatchWailsEvent; +var call3 = newRuntimeCaller(objectNames.Events); +var EmitMethod = 0; +var WailsEvent = class { + constructor(name, data) { + this.name = name; + this.data = data != null ? data : null; + } +}; +function dispatchWailsEvent(event) { + let listeners = eventListeners.get(event.name); + if (!listeners) { + return; + } + let wailsEvent = new WailsEvent( + event.name, + event.name in Events ? Events[event.name](event.data) : event.data + ); + if ("sender" in event) { + wailsEvent.sender = event.sender; + } + listeners = listeners.filter((listener) => !listener.dispatch(wailsEvent)); + if (listeners.length === 0) { + eventListeners.delete(event.name); + } else { + eventListeners.set(event.name, listeners); + } +} +function OnMultiple(eventName, callback, maxCallbacks) { + let listeners = eventListeners.get(eventName) || []; + const thisListener = new Listener(eventName, callback, maxCallbacks); + listeners.push(thisListener); + eventListeners.set(eventName, listeners); + return () => listenerOff(thisListener); +} +function On(eventName, callback) { + return OnMultiple(eventName, callback, -1); +} +function Once(eventName, callback) { + return OnMultiple(eventName, callback, 1); +} +function Off(...eventNames) { + eventNames.forEach((eventName) => eventListeners.delete(eventName)); +} +function OffAll() { + eventListeners.clear(); +} +function Emit(name, data) { + return call3(EmitMethod, new WailsEvent(name, data)); +} + +// desktop/@wailsio/runtime/src/utils.ts +function debugLog(message) { + console.log( + "%c wails3 %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" + ); +} +function canTrackButtons() { + return new MouseEvent("mousedown").buttons === 0; +} +function canAbortListeners() { + if (!EventTarget || !AbortSignal || !AbortController) + return false; + let result = true; + const target = new EventTarget(); + const controller = new AbortController(); + target.addEventListener("test", () => { + result = false; + }, { signal: controller.signal }); + controller.abort(); + target.dispatchEvent(new CustomEvent("test")); + return result; +} +function eventTarget(event) { + var _a2; + if (event.target instanceof HTMLElement) { + return event.target; + } else if (!(event.target instanceof HTMLElement) && event.target instanceof Node) { + return (_a2 = event.target.parentElement) != null ? _a2 : document.body; + } else { + return document.body; + } +} +var isReady = false; +document.addEventListener("DOMContentLoaded", () => { + isReady = true; +}); +function whenReady(callback) { + if (isReady || document.readyState === "complete") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +} + +// desktop/@wailsio/runtime/src/window.ts +var DROP_TARGET_ATTRIBUTE = "data-file-drop-target"; +var DROP_TARGET_ACTIVE_CLASS = "file-drop-target-active"; +var currentDropTarget = null; +var PositionMethod = 0; +var CenterMethod = 1; +var CloseMethod = 2; +var DisableSizeConstraintsMethod = 3; +var EnableSizeConstraintsMethod = 4; +var FocusMethod = 5; +var ForceReloadMethod = 6; +var FullscreenMethod = 7; +var GetScreenMethod = 8; +var GetZoomMethod = 9; +var HeightMethod = 10; +var HideMethod = 11; +var IsFocusedMethod = 12; +var IsFullscreenMethod = 13; +var IsMaximisedMethod = 14; +var IsMinimisedMethod = 15; +var MaximiseMethod = 16; +var MinimiseMethod = 17; +var NameMethod = 18; +var OpenDevToolsMethod = 19; +var RelativePositionMethod = 20; +var ReloadMethod = 21; +var ResizableMethod = 22; +var RestoreMethod = 23; +var SetPositionMethod = 24; +var SetAlwaysOnTopMethod = 25; +var SetBackgroundColourMethod = 26; +var SetFramelessMethod = 27; +var SetFullscreenButtonEnabledMethod = 28; +var SetMaxSizeMethod = 29; +var SetMinSizeMethod = 30; +var SetRelativePositionMethod = 31; +var SetResizableMethod = 32; +var SetSizeMethod = 33; +var SetTitleMethod = 34; +var SetZoomMethod = 35; +var ShowMethod = 36; +var SizeMethod = 37; +var ToggleFullscreenMethod = 38; +var ToggleMaximiseMethod = 39; +var ToggleFramelessMethod = 40; +var UnFullscreenMethod = 41; +var UnMaximiseMethod = 42; +var UnMinimiseMethod = 43; +var WidthMethod = 44; +var ZoomMethod = 45; +var ZoomInMethod = 46; +var ZoomOutMethod = 47; +var ZoomResetMethod = 48; +var SnapAssistMethod = 49; +var FilesDropped = 50; +var PrintMethod = 51; +function getDropTargetElement(element) { + if (!element) { + return null; + } + return element.closest("[".concat(DROP_TARGET_ATTRIBUTE, "]")); +} +function canResolveFilePaths() { + var _a2, _b, _c, _d; + if (((_b = (_a2 = window.chrome) == null ? void 0 : _a2.webview) == null ? void 0 : _b.postMessageWithAdditionalObjects) == null) { + return false; + } + return ((_d = (_c = window._wails) == null ? void 0 : _c.flags) == null ? void 0 : _d.enableFileDrop) === true; +} +function resolveFilePaths(x, y, files) { + var _a2, _b; + if ((_b = (_a2 = window.chrome) == null ? void 0 : _a2.webview) == null ? void 0 : _b.postMessageWithAdditionalObjects) { + window.chrome.webview.postMessageWithAdditionalObjects("file:drop:".concat(x, ":").concat(y), files); + } +} +var nativeDragActive = false; +function cleanupNativeDrag() { + nativeDragActive = false; + if (currentDropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = null; + } +} +function handleDragEnter() { + var _a2, _b; + if (((_b = (_a2 = window._wails) == null ? void 0 : _a2.flags) == null ? void 0 : _b.enableFileDrop) === false) { + return; + } + nativeDragActive = true; +} +function handleDragLeave() { + cleanupNativeDrag(); +} +function handleDragOver(x, y) { + var _a2, _b; + if (!nativeDragActive) return; + if (((_b = (_a2 = window._wails) == null ? void 0 : _a2.flags) == null ? void 0 : _b.enableFileDrop) === false) { + return; + } + const targetElement = document.elementFromPoint(x, y); + const dropTarget = getDropTargetElement(targetElement); + if (currentDropTarget && currentDropTarget !== dropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + } + if (dropTarget) { + dropTarget.classList.add(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = dropTarget; + } else { + currentDropTarget = null; + } +} +var callerSym = /* @__PURE__ */ Symbol("caller"); +callerSym; +var _Window = class _Window { + /** + * Initialises a window object with the specified name. + * + * @private + * @param name - The name of the target window. + */ + constructor(name = "") { + this[callerSym] = newRuntimeCaller(objectNames.Window, name); + for (const method of Object.getOwnPropertyNames(_Window.prototype)) { + if (method !== "constructor" && typeof this[method] === "function") { + this[method] = this[method].bind(this); + } + } + } + /** + * Gets the specified window. + * + * @param name - The name of the window to get. + * @returns The corresponding window object. + */ + Get(name) { + return new _Window(name); + } + /** + * Returns the absolute position of the window. + * + * @returns The current absolute position of the window. + */ + Position() { + return this[callerSym](PositionMethod); + } + /** + * Centers the window on the screen. + */ + Center() { + return this[callerSym](CenterMethod); + } + /** + * Closes the window. + */ + Close() { + return this[callerSym](CloseMethod); + } + /** + * Disables min/max size constraints. + */ + DisableSizeConstraints() { + return this[callerSym](DisableSizeConstraintsMethod); + } + /** + * Enables min/max size constraints. + */ + EnableSizeConstraints() { + return this[callerSym](EnableSizeConstraintsMethod); + } + /** + * Focuses the window. + */ + Focus() { + return this[callerSym](FocusMethod); + } + /** + * Forces the window to reload the page assets. + */ + ForceReload() { + return this[callerSym](ForceReloadMethod); + } + /** + * Switches the window to fullscreen mode. + */ + Fullscreen() { + return this[callerSym](FullscreenMethod); + } + /** + * Returns the screen that the window is on. + * + * @returns The screen the window is currently on. + */ + GetScreen() { + return this[callerSym](GetScreenMethod); + } + /** + * Returns the current zoom level of the window. + * + * @returns The current zoom level. + */ + GetZoom() { + return this[callerSym](GetZoomMethod); + } + /** + * Returns the height of the window. + * + * @returns The current height of the window. + */ + Height() { + return this[callerSym](HeightMethod); + } + /** + * Hides the window. + */ + Hide() { + return this[callerSym](HideMethod); + } + /** + * Returns true if the window is focused. + * + * @returns Whether the window is currently focused. + */ + IsFocused() { + return this[callerSym](IsFocusedMethod); + } + /** + * Returns true if the window is fullscreen. + * + * @returns Whether the window is currently fullscreen. + */ + IsFullscreen() { + return this[callerSym](IsFullscreenMethod); + } + /** + * Returns true if the window is maximised. + * + * @returns Whether the window is currently maximised. + */ + IsMaximised() { + return this[callerSym](IsMaximisedMethod); + } + /** + * Returns true if the window is minimised. + * + * @returns Whether the window is currently minimised. + */ + IsMinimised() { + return this[callerSym](IsMinimisedMethod); + } + /** + * Maximises the window. + */ + Maximise() { + return this[callerSym](MaximiseMethod); + } + /** + * Minimises the window. + */ + Minimise() { + return this[callerSym](MinimiseMethod); + } + /** + * Returns the name of the window. + * + * @returns The name of the window. + */ + Name() { + return this[callerSym](NameMethod); + } + /** + * Opens the development tools pane. + */ + OpenDevTools() { + return this[callerSym](OpenDevToolsMethod); + } + /** + * Returns the relative position of the window to the screen. + * + * @returns The current relative position of the window. + */ + RelativePosition() { + return this[callerSym](RelativePositionMethod); + } + /** + * Reloads the page assets. + */ + Reload() { + return this[callerSym](ReloadMethod); + } + /** + * Returns true if the window is resizable. + * + * @returns Whether the window is currently resizable. + */ + Resizable() { + return this[callerSym](ResizableMethod); + } + /** + * Restores the window to its previous state if it was previously minimised, maximised or fullscreen. + */ + Restore() { + return this[callerSym](RestoreMethod); + } + /** + * Sets the absolute position of the window. + * + * @param x - The desired horizontal absolute position of the window. + * @param y - The desired vertical absolute position of the window. + */ + SetPosition(x, y) { + return this[callerSym](SetPositionMethod, { x, y }); + } + /** + * Sets the window to be always on top. + * + * @param alwaysOnTop - Whether the window should stay on top. + */ + SetAlwaysOnTop(alwaysOnTop) { + return this[callerSym](SetAlwaysOnTopMethod, { alwaysOnTop }); + } + /** + * Sets the background colour of the window. + * + * @param r - The desired red component of the window background. + * @param g - The desired green component of the window background. + * @param b - The desired blue component of the window background. + * @param a - The desired alpha component of the window background. + */ + SetBackgroundColour(r, g, b, a) { + return this[callerSym](SetBackgroundColourMethod, { r, g, b, a }); + } + /** + * Removes the window frame and title bar. + * + * @param frameless - Whether the window should be frameless. + */ + SetFrameless(frameless) { + return this[callerSym](SetFramelessMethod, { frameless }); + } + /** + * Disables the system fullscreen button. + * + * @param enabled - Whether the fullscreen button should be enabled. + */ + SetFullscreenButtonEnabled(enabled) { + return this[callerSym](SetFullscreenButtonEnabledMethod, { enabled }); + } + /** + * Sets the maximum size of the window. + * + * @param width - The desired maximum width of the window. + * @param height - The desired maximum height of the window. + */ + SetMaxSize(width, height) { + return this[callerSym](SetMaxSizeMethod, { width, height }); + } + /** + * Sets the minimum size of the window. + * + * @param width - The desired minimum width of the window. + * @param height - The desired minimum height of the window. + */ + SetMinSize(width, height) { + return this[callerSym](SetMinSizeMethod, { width, height }); + } + /** + * Sets the relative position of the window to the screen. + * + * @param x - The desired horizontal relative position of the window. + * @param y - The desired vertical relative position of the window. + */ + SetRelativePosition(x, y) { + return this[callerSym](SetRelativePositionMethod, { x, y }); + } + /** + * Sets whether the window is resizable. + * + * @param resizable - Whether the window should be resizable. + */ + SetResizable(resizable2) { + return this[callerSym](SetResizableMethod, { resizable: resizable2 }); + } + /** + * Sets the size of the window. + * + * @param width - The desired width of the window. + * @param height - The desired height of the window. + */ + SetSize(width, height) { + return this[callerSym](SetSizeMethod, { width, height }); + } + /** + * Sets the title of the window. + * + * @param title - The desired title of the window. + */ + SetTitle(title) { + return this[callerSym](SetTitleMethod, { title }); + } + /** + * Sets the zoom level of the window. + * + * @param zoom - The desired zoom level. + */ + SetZoom(zoom) { + return this[callerSym](SetZoomMethod, { zoom }); + } + /** + * Shows the window. + */ + Show() { + return this[callerSym](ShowMethod); + } + /** + * Returns the size of the window. + * + * @returns The current size of the window. + */ + Size() { + return this[callerSym](SizeMethod); + } + /** + * Toggles the window between fullscreen and normal. + */ + ToggleFullscreen() { + return this[callerSym](ToggleFullscreenMethod); + } + /** + * Toggles the window between maximised and normal. + */ + ToggleMaximise() { + return this[callerSym](ToggleMaximiseMethod); + } + /** + * Toggles the window between frameless and normal. + */ + ToggleFrameless() { + return this[callerSym](ToggleFramelessMethod); + } + /** + * Un-fullscreens the window. + */ + UnFullscreen() { + return this[callerSym](UnFullscreenMethod); + } + /** + * Un-maximises the window. + */ + UnMaximise() { + return this[callerSym](UnMaximiseMethod); + } + /** + * Un-minimises the window. + */ + UnMinimise() { + return this[callerSym](UnMinimiseMethod); + } + /** + * Returns the width of the window. + * + * @returns The current width of the window. + */ + Width() { + return this[callerSym](WidthMethod); + } + /** + * Zooms the window. + */ + Zoom() { + return this[callerSym](ZoomMethod); + } + /** + * Increases the zoom level of the webview content. + */ + ZoomIn() { + return this[callerSym](ZoomInMethod); + } + /** + * Decreases the zoom level of the webview content. + */ + ZoomOut() { + return this[callerSym](ZoomOutMethod); + } + /** + * Resets the zoom level of the webview content. + */ + ZoomReset() { + return this[callerSym](ZoomResetMethod); + } + /** + * Handles file drops originating from platform-specific code (e.g., macOS/Linux native drag-and-drop). + * Gathers information about the drop target element and sends it back to the Go backend. + * + * @param filenames - An array of file paths (strings) that were dropped. + * @param x - The x-coordinate of the drop event (CSS pixels). + * @param y - The y-coordinate of the drop event (CSS pixels). + */ + HandlePlatformFileDrop(filenames, x, y) { + var _a2, _b; + if (((_b = (_a2 = window._wails) == null ? void 0 : _a2.flags) == null ? void 0 : _b.enableFileDrop) === false) { + return; + } + const element = document.elementFromPoint(x, y); + const dropTarget = getDropTargetElement(element); + if (!dropTarget) { + return; + } + const elementDetails = { + id: dropTarget.id, + classList: Array.from(dropTarget.classList), + attributes: {} + }; + for (let i = 0; i < dropTarget.attributes.length; i++) { + const attr = dropTarget.attributes[i]; + elementDetails.attributes[attr.name] = attr.value; + } + const payload = { + filenames, + x, + y, + elementDetails + }; + this[callerSym](FilesDropped, payload); + cleanupNativeDrag(); + } + /* Triggers Windows 11 Snap Assist feature (Windows only). + * This is equivalent to pressing Win+Z and shows snap layout options. + */ + SnapAssist() { + return this[callerSym](SnapAssistMethod); + } + /** + * Opens the print dialog for the window. + */ + Print() { + return this[callerSym](PrintMethod); + } +}; +var Window = _Window; +var thisWindow = new Window(""); +function setupDropTargetListeners() { + const docElement = document.documentElement; + let dragEnterCounter = 0; + docElement.addEventListener("dragenter", (event) => { + var _a2, _b, _c; + if (!((_a2 = event.dataTransfer) == null ? void 0 : _a2.types.includes("Files"))) { + return; + } + event.preventDefault(); + if (((_c = (_b = window._wails) == null ? void 0 : _b.flags) == null ? void 0 : _c.enableFileDrop) === false) { + event.dataTransfer.dropEffect = "none"; + return; + } + dragEnterCounter++; + const targetElement = document.elementFromPoint(event.clientX, event.clientY); + const dropTarget = getDropTargetElement(targetElement); + if (currentDropTarget && currentDropTarget !== dropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + } + if (dropTarget) { + dropTarget.classList.add(DROP_TARGET_ACTIVE_CLASS); + event.dataTransfer.dropEffect = "copy"; + currentDropTarget = dropTarget; + } else { + event.dataTransfer.dropEffect = "none"; + currentDropTarget = null; + } + }, false); + docElement.addEventListener("dragover", (event) => { + var _a2, _b, _c; + if (!((_a2 = event.dataTransfer) == null ? void 0 : _a2.types.includes("Files"))) { + return; + } + event.preventDefault(); + if (((_c = (_b = window._wails) == null ? void 0 : _b.flags) == null ? void 0 : _c.enableFileDrop) === false) { + event.dataTransfer.dropEffect = "none"; + return; + } + const targetElement = document.elementFromPoint(event.clientX, event.clientY); + const dropTarget = getDropTargetElement(targetElement); + if (currentDropTarget && currentDropTarget !== dropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + } + if (dropTarget) { + if (!dropTarget.classList.contains(DROP_TARGET_ACTIVE_CLASS)) { + dropTarget.classList.add(DROP_TARGET_ACTIVE_CLASS); + } + event.dataTransfer.dropEffect = "copy"; + currentDropTarget = dropTarget; + } else { + event.dataTransfer.dropEffect = "none"; + currentDropTarget = null; + } + }, false); + docElement.addEventListener("dragleave", (event) => { + var _a2, _b, _c; + if (!((_a2 = event.dataTransfer) == null ? void 0 : _a2.types.includes("Files"))) { + return; + } + event.preventDefault(); + if (((_c = (_b = window._wails) == null ? void 0 : _b.flags) == null ? void 0 : _c.enableFileDrop) === false) { + return; + } + if (event.relatedTarget === null) { + return; + } + dragEnterCounter--; + if (dragEnterCounter === 0 || currentDropTarget && !currentDropTarget.contains(event.relatedTarget)) { + if (currentDropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = null; + } + dragEnterCounter = 0; + } + }, false); + docElement.addEventListener("drop", (event) => { + var _a2, _b, _c; + if (!((_a2 = event.dataTransfer) == null ? void 0 : _a2.types.includes("Files"))) { + return; + } + event.preventDefault(); + if (((_c = (_b = window._wails) == null ? void 0 : _b.flags) == null ? void 0 : _c.enableFileDrop) === false) { + return; + } + dragEnterCounter = 0; + if (currentDropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = null; + } + if (canResolveFilePaths()) { + const files = []; + if (event.dataTransfer.items) { + for (const item of event.dataTransfer.items) { + if (item.kind === "file") { + const file = item.getAsFile(); + if (file) files.push(file); + } + } + } else if (event.dataTransfer.files) { + for (const file of event.dataTransfer.files) { + files.push(file); + } + } + if (files.length > 0) { + resolveFilePaths(event.clientX, event.clientY, files); + } + } + }, false); +} +if (typeof window !== "undefined" && typeof document !== "undefined") { + setupDropTargetListeners(); +} +var window_default = thisWindow; + +// desktop/@wailsio/runtime/src/wml.ts +function sendEvent(eventName, data = null) { + Emit(eventName, data); +} +function callWindowMethod(windowName, methodName) { + const targetWindow = window_default.Get(windowName); + const method = targetWindow[methodName]; + if (typeof method !== "function") { + console.error("Window method '".concat(methodName, "' not found")); + return; + } + try { + method.call(targetWindow); + } catch (e) { + console.error("Error calling window method '".concat(methodName, "': "), e); + } +} +function onWMLTriggered(ev) { + const element = ev.currentTarget; + function runEffect(choice = "Yes") { + if (choice !== "Yes") + return; + const eventType = element.getAttribute("wml-event") || element.getAttribute("data-wml-event"); + const targetWindow = element.getAttribute("wml-target-window") || element.getAttribute("data-wml-target-window") || ""; + const windowMethod = element.getAttribute("wml-window") || element.getAttribute("data-wml-window"); + const url = element.getAttribute("wml-openurl") || element.getAttribute("data-wml-openurl"); + if (eventType !== null) + sendEvent(eventType); + if (windowMethod !== null) + callWindowMethod(targetWindow, windowMethod); + if (url !== null) + void OpenURL(url); + } + const confirm = element.getAttribute("wml-confirm") || element.getAttribute("data-wml-confirm"); + if (confirm) { + Question({ + Title: "Confirm", + Message: confirm, + Detached: false, + Buttons: [ + { Label: "Yes" }, + { Label: "No", IsDefault: true } + ] + }).then(runEffect); + } else { + runEffect(); + } +} +var controllerSym = /* @__PURE__ */ Symbol("controller"); +var triggerMapSym = /* @__PURE__ */ Symbol("triggerMap"); +var elementCountSym = /* @__PURE__ */ Symbol("elementCount"); +controllerSym; +var AbortControllerRegistry = class { + constructor() { + this[controllerSym] = new AbortController(); + } + /** + * Returns an options object for addEventListener that ties the listener + * to the AbortSignal from the current AbortController. + * + * @param element - An HTML element + * @param triggers - The list of active WML trigger events for the specified elements + */ + set(element, triggers) { + return { signal: this[controllerSym].signal }; + } + /** + * Removes all registered event listeners and resets the registry. + */ + reset() { + this[controllerSym].abort(); + this[controllerSym] = new AbortController(); + } +}; +triggerMapSym, elementCountSym; +var WeakMapRegistry = class { + constructor() { + this[triggerMapSym] = /* @__PURE__ */ new WeakMap(); + this[elementCountSym] = 0; + } + /** + * Sets active triggers for the specified element. + * + * @param element - An HTML element + * @param triggers - The list of active WML trigger events for the specified element + */ + set(element, triggers) { + if (!this[triggerMapSym].has(element)) { + this[elementCountSym]++; + } + this[triggerMapSym].set(element, triggers); + return {}; + } + /** + * Removes all registered event listeners. + */ + reset() { + if (this[elementCountSym] <= 0) + return; + for (const element of document.body.querySelectorAll("*")) { + if (this[elementCountSym] <= 0) + break; + const triggers = this[triggerMapSym].get(element); + if (triggers != null) { + this[elementCountSym]--; + } + for (const trigger of triggers || []) + element.removeEventListener(trigger, onWMLTriggered); + } + this[triggerMapSym] = /* @__PURE__ */ new WeakMap(); + this[elementCountSym] = 0; + } +}; +var triggerRegistry = canAbortListeners() ? new AbortControllerRegistry() : new WeakMapRegistry(); +function addWMLListeners(element) { + const triggerRegExp = /\S+/g; + const triggerAttr = element.getAttribute("wml-trigger") || element.getAttribute("data-wml-trigger") || "click"; + const triggers = []; + let match; + while ((match = triggerRegExp.exec(triggerAttr)) !== null) + triggers.push(match[0]); + const options = triggerRegistry.set(element, triggers); + for (const trigger of triggers) + element.addEventListener(trigger, onWMLTriggered, options); +} +function Enable() { + whenReady(Reload); +} +function Reload() { + triggerRegistry.reset(); + document.body.querySelectorAll("[wml-event], [wml-window], [wml-openurl], [data-wml-event], [data-wml-window], [data-wml-openurl]").forEach(addWMLListeners); +} + +// desktop/compiled/main.js +window.wails = index_exports; +Enable(); +if (true) { + debugLog("Wails Runtime Loaded"); +} + +// desktop/@wailsio/runtime/src/system.ts +var system_exports = {}; +__export(system_exports, { + Capabilities: () => Capabilities, + Environment: () => Environment, + IsAMD64: () => IsAMD64, + IsARM: () => IsARM, + IsARM64: () => IsARM64, + IsDarkMode: () => IsDarkMode, + IsDebug: () => IsDebug, + IsLinux: () => IsLinux, + IsMac: () => IsMac, + IsWindows: () => IsWindows, + invoke: () => invoke +}); +var call4 = newRuntimeCaller(objectNames.System); +var SystemIsDarkMode = 0; +var SystemEnvironment = 1; +var SystemCapabilities = 2; +var _invoke = (function() { + var _a2, _b, _c, _d, _e, _f; + try { + if ((_b = (_a2 = window.chrome) == null ? void 0 : _a2.webview) == null ? void 0 : _b.postMessage) { + return window.chrome.webview.postMessage.bind(window.chrome.webview); + } else if ((_e = (_d = (_c = window.webkit) == null ? void 0 : _c.messageHandlers) == null ? void 0 : _d["external"]) == null ? void 0 : _e.postMessage) { + return window.webkit.messageHandlers["external"].postMessage.bind(window.webkit.messageHandlers["external"]); + } else if ((_f = window.wails) == null ? void 0 : _f.invoke) { + return (msg) => window.wails.invoke(typeof msg === "string" ? msg : JSON.stringify(msg)); + } + } catch (e) { + } + console.warn( + "\n%c\u26A0\uFE0F Browser Environment Detected %c\n\n%cOnly UI previews are available in the browser. For full functionality, please run the application in desktop mode.\nMore information at: https://v3.wails.io/learn/build/#using-a-browser-for-development\n", + "background: #ffffff; color: #000000; font-weight: bold; padding: 4px 8px; border-radius: 4px; border: 2px solid #000000;", + "background: transparent;", + "color: #ffffff; font-style: italic; font-weight: bold;" + ); + return null; +})(); +function invoke(msg) { + _invoke == null ? void 0 : _invoke(msg); +} +function IsDarkMode() { + return call4(SystemIsDarkMode); +} +async function Capabilities() { + return call4(SystemCapabilities); +} +function Environment() { + return call4(SystemEnvironment); +} +function IsWindows() { + var _a2, _b; + return ((_b = (_a2 = window._wails) == null ? void 0 : _a2.environment) == null ? void 0 : _b.OS) === "windows"; +} +function IsLinux() { + var _a2, _b; + return ((_b = (_a2 = window._wails) == null ? void 0 : _a2.environment) == null ? void 0 : _b.OS) === "linux"; +} +function IsMac() { + var _a2, _b; + return ((_b = (_a2 = window._wails) == null ? void 0 : _a2.environment) == null ? void 0 : _b.OS) === "darwin"; +} +function IsAMD64() { + var _a2, _b; + return ((_b = (_a2 = window._wails) == null ? void 0 : _a2.environment) == null ? void 0 : _b.Arch) === "amd64"; +} +function IsARM() { + var _a2, _b; + return ((_b = (_a2 = window._wails) == null ? void 0 : _a2.environment) == null ? void 0 : _b.Arch) === "arm"; +} +function IsARM64() { + var _a2, _b; + return ((_b = (_a2 = window._wails) == null ? void 0 : _a2.environment) == null ? void 0 : _b.Arch) === "arm64"; +} +function IsDebug() { + var _a2, _b; + return Boolean((_b = (_a2 = window._wails) == null ? void 0 : _a2.environment) == null ? void 0 : _b.Debug); +} + +// desktop/@wailsio/runtime/src/contextmenu.ts +window.addEventListener("contextmenu", contextMenuHandler); +var call5 = newRuntimeCaller(objectNames.ContextMenu); +var ContextMenuOpen = 0; +function openContextMenu(id, x, y, data) { + void call5(ContextMenuOpen, { id, x, y, data }); +} +function contextMenuHandler(event) { + const target = eventTarget(event); + const customContextMenu = window.getComputedStyle(target).getPropertyValue("--custom-contextmenu").trim(); + if (customContextMenu) { + event.preventDefault(); + const data = window.getComputedStyle(target).getPropertyValue("--custom-contextmenu-data"); + openContextMenu(customContextMenu, event.clientX, event.clientY, data); + } else { + processDefaultContextMenu(event, target); + } +} +function processDefaultContextMenu(event, target) { + if (IsDebug()) { + return; + } + switch (window.getComputedStyle(target).getPropertyValue("--default-contextmenu").trim()) { + case "show": + return; + case "hide": + event.preventDefault(); + return; + } + if (target.isContentEditable) { + return; + } + const selection = window.getSelection(); + const hasSelection = selection && selection.toString().length > 0; + if (hasSelection) { + for (let i = 0; i < selection.rangeCount; i++) { + const range = selection.getRangeAt(i); + const rects = range.getClientRects(); + for (let j = 0; j < rects.length; j++) { + const rect = rects[j]; + if (document.elementFromPoint(rect.left, rect.top) === target) { + return; + } + } + } + } + if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) { + if (hasSelection || !target.readOnly && !target.disabled) { + return; + } + } + event.preventDefault(); +} + +// desktop/@wailsio/runtime/src/flags.ts +var flags_exports = {}; +__export(flags_exports, { + GetFlag: () => GetFlag +}); +function GetFlag(key) { + try { + return window._wails.flags[key]; + } catch (e) { + throw new Error("Unable to retrieve flag '" + key + "': " + e, { cause: e }); + } +} + +// desktop/@wailsio/runtime/src/drag.ts +var canDrag = false; +var dragging = false; +var resizable = false; +var canResize = false; +var resizing = false; +var resizeEdge = ""; +var defaultCursor = "auto"; +var buttons = 0; +var buttonsTracked = canTrackButtons(); +window._wails = window._wails || {}; +window._wails.setResizable = (value) => { + resizable = value; + if (!resizable) { + canResize = resizing = false; + setResize(); + } +}; +var dragInitDone = false; +function isMobile() { + var _a2, _b; + const os = (_b = (_a2 = window._wails) == null ? void 0 : _a2.environment) == null ? void 0 : _b.OS; + if (os === "ios" || os === "android") return true; + const ua = navigator.userAgent || navigator.vendor || window.opera || ""; + return /android|iphone|ipad|ipod|iemobile|wpdesktop/i.test(ua); +} +function tryInitDragHandlers() { + if (dragInitDone) return; + if (isMobile()) return; + window.addEventListener("mousedown", update, { capture: true }); + window.addEventListener("mousemove", update, { capture: true }); + window.addEventListener("mouseup", update, { capture: true }); + for (const ev of ["click", "contextmenu", "dblclick"]) { + window.addEventListener(ev, suppressEvent, { capture: true }); + } + dragInitDone = true; +} +tryInitDragHandlers(); +document.addEventListener("DOMContentLoaded", tryInitDragHandlers, { once: true }); +var dragEnvPolls = 0; +var dragEnvPoll = window.setInterval(() => { + if (dragInitDone) { + window.clearInterval(dragEnvPoll); + return; + } + tryInitDragHandlers(); + if (++dragEnvPolls > 100) { + window.clearInterval(dragEnvPoll); + } +}, 50); +function suppressEvent(event) { + if (dragging || resizing) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } +} +var MouseDown = 0; +var MouseUp = 1; +var MouseMove = 2; +function update(event) { + let eventType, eventButtons = event.buttons; + switch (event.type) { + case "mousedown": + eventType = MouseDown; + if (!buttonsTracked) { + eventButtons = buttons | 1 << event.button; + } + break; + case "mouseup": + eventType = MouseUp; + if (!buttonsTracked) { + eventButtons = buttons & ~(1 << event.button); + } + break; + default: + eventType = MouseMove; + if (!buttonsTracked) { + eventButtons = buttons; + } + break; + } + let released = buttons & ~eventButtons; + let pressed = eventButtons & ~buttons; + buttons = eventButtons; + if (eventType === MouseDown && !(pressed & event.button)) { + released |= 1 << event.button; + pressed |= 1 << event.button; + } + if (eventType !== MouseMove && resizing || dragging && (eventType === MouseDown || event.button !== 0)) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } + if (released & 1) { + primaryUp(event); + } + if (pressed & 1) { + primaryDown(event); + } + if (eventType === MouseMove) { + onMouseMove(event); + } + ; +} +function primaryDown(event) { + canDrag = false; + canResize = false; + if (!IsWindows()) { + if (event.type === "mousedown" && event.button === 0 && event.detail !== 1) { + return; + } + } + if (resizeEdge) { + canResize = true; + return; + } + const target = eventTarget(event); + const style = window.getComputedStyle(target); + canDrag = style.getPropertyValue("--wails-draggable").trim() === "drag" && (event.offsetX - parseFloat(style.paddingLeft) < target.clientWidth && event.offsetY - parseFloat(style.paddingTop) < target.clientHeight); +} +function primaryUp(event) { + canDrag = false; + dragging = false; + canResize = false; + resizing = false; +} +var cursorForEdge = Object.freeze({ + "se-resize": "nwse-resize", + "sw-resize": "nesw-resize", + "nw-resize": "nwse-resize", + "ne-resize": "nesw-resize", + "w-resize": "ew-resize", + "n-resize": "ns-resize", + "s-resize": "ns-resize", + "e-resize": "ew-resize" +}); +function setResize(edge) { + if (edge) { + if (!resizeEdge) { + defaultCursor = document.body.style.cursor; + } + document.body.style.cursor = cursorForEdge[edge]; + } else if (!edge && resizeEdge) { + document.body.style.cursor = defaultCursor; + } + resizeEdge = edge || ""; +} +function onMouseMove(event) { + if (canResize && resizeEdge) { + resizing = true; + invoke("wails:resize:" + resizeEdge); + } else if (canDrag) { + dragging = true; + invoke("wails:drag"); + } + if (dragging || resizing) { + canDrag = canResize = false; + return; + } + if (!resizable || !IsWindows()) { + if (resizeEdge) { + setResize(); + } + return; + } + const resizeHandleHeight = GetFlag("system.resizeHandleHeight") || 5; + const resizeHandleWidth = GetFlag("system.resizeHandleWidth") || 5; + const cornerExtra = GetFlag("resizeCornerExtra") || 10; + const rightBorder = window.outerWidth - event.clientX < resizeHandleWidth; + const leftBorder = event.clientX < resizeHandleWidth; + const topBorder = event.clientY < resizeHandleHeight; + const bottomBorder = window.outerHeight - event.clientY < resizeHandleHeight; + const rightCorner = window.outerWidth - event.clientX < resizeHandleWidth + cornerExtra; + const leftCorner = event.clientX < resizeHandleWidth + cornerExtra; + const topCorner = event.clientY < resizeHandleHeight + cornerExtra; + const bottomCorner = window.outerHeight - event.clientY < resizeHandleHeight + cornerExtra; + if (!leftCorner && !topCorner && !bottomCorner && !rightCorner) { + setResize(); + } else if (rightCorner && bottomCorner) setResize("se-resize"); + else if (leftCorner && bottomCorner) setResize("sw-resize"); + else if (leftCorner && topCorner) setResize("nw-resize"); + else if (topCorner && rightCorner) setResize("ne-resize"); + else if (leftBorder) setResize("w-resize"); + else if (topBorder) setResize("n-resize"); + else if (bottomBorder) setResize("s-resize"); + else if (rightBorder) setResize("e-resize"); + else setResize(); +} + +// desktop/@wailsio/runtime/src/application.ts +var application_exports = {}; +__export(application_exports, { + Hide: () => Hide, + Quit: () => Quit, + Show: () => Show +}); +var call6 = newRuntimeCaller(objectNames.Application); +var HideMethod2 = 0; +var ShowMethod2 = 1; +var QuitMethod = 2; +function Hide() { + return call6(HideMethod2); +} +function Show() { + return call6(ShowMethod2); +} +function Quit() { + return call6(QuitMethod); +} + +// desktop/@wailsio/runtime/src/calls.ts +var calls_exports = {}; +__export(calls_exports, { + ByID: () => ByID, + ByName: () => ByName, + Call: () => Call, + RuntimeError: () => RuntimeError +}); + +// desktop/@wailsio/runtime/src/callable.ts +var fnToStr = Function.prototype.toString; +var reflectApply = typeof Reflect === "object" && Reflect !== null && Reflect.apply; +var badArrayLike; +var isCallableMarker; +if (typeof reflectApply === "function" && typeof Object.defineProperty === "function") { + try { + badArrayLike = Object.defineProperty({}, "length", { + get: function() { + throw isCallableMarker; + } + }); + isCallableMarker = {}; + reflectApply(function() { + throw 42; + }, null, badArrayLike); + } catch (_) { + if (_ !== isCallableMarker) { + reflectApply = null; + } + } +} else { + reflectApply = null; +} +var constructorRegex = /^\s*class\b/; +var isES6ClassFn = function isES6ClassFunction(value) { + try { + var fnStr = fnToStr.call(value); + return constructorRegex.test(fnStr); + } catch (e) { + return false; + } +}; +var tryFunctionObject = function tryFunctionToStr(value) { + try { + if (isES6ClassFn(value)) { + return false; + } + fnToStr.call(value); + return true; + } catch (e) { + return false; + } +}; +var toStr = Object.prototype.toString; +var objectClass = "[object Object]"; +var fnClass = "[object Function]"; +var genClass = "[object GeneratorFunction]"; +var ddaClass = "[object HTMLAllCollection]"; +var ddaClass2 = "[object HTML document.all class]"; +var ddaClass3 = "[object HTMLCollection]"; +var hasToStringTag = typeof Symbol === "function" && !!Symbol.toStringTag; +var isIE68 = !(0 in [,]); +var isDDA = function isDocumentDotAll() { + return false; +}; +if (typeof document === "object") { + all = document.all; + if (toStr.call(all) === toStr.call(document.all)) { + isDDA = function isDocumentDotAll2(value) { + if ((isIE68 || !value) && (typeof value === "undefined" || typeof value === "object")) { + try { + var str = toStr.call(value); + return (str === ddaClass || str === ddaClass2 || str === ddaClass3 || str === objectClass) && value("") == null; + } catch (e) { + } + } + return false; + }; + } +} +var all; +function isCallableRefApply(value) { + if (isDDA(value)) { + return true; + } + if (!value) { + return false; + } + if (typeof value !== "function" && typeof value !== "object") { + return false; + } + try { + reflectApply(value, null, badArrayLike); + } catch (e) { + if (e !== isCallableMarker) { + return false; + } + } + return !isES6ClassFn(value) && tryFunctionObject(value); +} +function isCallableNoRefApply(value) { + if (isDDA(value)) { + return true; + } + if (!value) { + return false; + } + if (typeof value !== "function" && typeof value !== "object") { + return false; + } + if (hasToStringTag) { + return tryFunctionObject(value); + } + if (isES6ClassFn(value)) { + return false; + } + var strClass = toStr.call(value); + if (strClass !== fnClass && strClass !== genClass && !/^\[object HTML/.test(strClass)) { + return false; + } + return tryFunctionObject(value); +} +var callable_default = reflectApply ? isCallableRefApply : isCallableNoRefApply; + +// desktop/@wailsio/runtime/src/cancellable.ts +var CancelError = class extends Error { + /** + * Constructs a new `CancelError` instance. + * @param message - The error message. + * @param options - Options to be forwarded to the Error constructor. + */ + constructor(message, options) { + super(message, options); + this.name = "CancelError"; + } +}; +var CancelledRejectionError = class extends Error { + /** + * Constructs a new `CancelledRejectionError` instance. + * @param promise - The promise that caused the error originally. + * @param reason - The rejection reason. + * @param info - An optional informative message specifying the circumstances in which the error was thrown. + * Defaults to the string `"Unhandled rejection in cancelled promise."`. + */ + constructor(promise, reason, info) { + super((info != null ? info : "Unhandled rejection in cancelled promise.") + " Reason: " + errorMessage(reason), { cause: reason }); + this.promise = promise; + this.name = "CancelledRejectionError"; + } +}; +var barrierSym = /* @__PURE__ */ Symbol("barrier"); +var cancelImplSym = /* @__PURE__ */ Symbol("cancelImpl"); +var _a; +var species = (_a = Symbol.species) != null ? _a : /* @__PURE__ */ Symbol("speciesPolyfill"); +var CancellablePromise = class _CancellablePromise extends Promise { + /** + * Creates a new `CancellablePromise`. + * + * @param executor - A callback used to initialize the promise. This callback is passed two arguments: + * a `resolve` callback used to resolve the promise with a value + * or the result of another promise (possibly cancellable), + * and a `reject` callback used to reject the promise with a provided reason or error. + * If the value provided to the `resolve` callback is a thenable _and_ cancellable object + * (it has a `then` _and_ a `cancel` method), + * cancellation requests will be forwarded to that object and the oncancelled will not be invoked anymore. + * If any one of the two callbacks is called _after_ the promise has been cancelled, + * the provided values will be cancelled and resolved as usual, + * but their results will be discarded. + * However, if the resolution process ultimately ends up in a rejection + * that is not due to cancellation, the rejection reason + * will be wrapped in a {@link CancelledRejectionError} + * and bubbled up as an unhandled rejection. + * @param oncancelled - It is the caller's responsibility to ensure that any operation + * started by the executor is properly halted upon cancellation. + * This optional callback can be used to that purpose. + * It will be called _synchronously_ with a cancellation cause + * when cancellation is requested, _after_ the promise has already rejected + * with a {@link CancelError}, but _before_ + * any {@link then}/{@link catch}/{@link finally} callback runs. + * If the callback returns a thenable, the promise returned from {@link cancel} + * will only fulfill after the former has settled. + * Unhandled exceptions or rejections from the callback will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as unhandled rejections. + * If the `resolve` callback is called before cancellation with a cancellable promise, + * cancellation requests on this promise will be diverted to that promise, + * and the original `oncancelled` callback will be discarded. + */ + constructor(executor, oncancelled) { + let resolve; + let reject; + super((res, rej) => { + resolve = res; + reject = rej; + }); + if (this.constructor[species] !== Promise) { + throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property."); + } + let promise = { + promise: this, + resolve, + reject, + get oncancelled() { + return oncancelled != null ? oncancelled : null; + }, + set oncancelled(cb) { + oncancelled = cb != null ? cb : void 0; + } + }; + const state = { + get root() { + return state; + }, + resolving: false, + settled: false + }; + void Object.defineProperties(this, { + [barrierSym]: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + [cancelImplSym]: { + configurable: false, + enumerable: false, + writable: false, + value: cancellerFor(promise, state) + } + }); + const rejector = rejectorFor(promise, state); + try { + executor(resolverFor(promise, state), rejector); + } catch (err) { + if (state.resolving) { + console.log("Unhandled exception in CancellablePromise executor.", err); + } else { + rejector(err); + } + } + } + /** + * Cancels immediately the execution of the operation associated with this promise. + * The promise rejects with a {@link CancelError} instance as reason, + * with the {@link CancelError#cause} property set to the given argument, if any. + * + * Has no effect if called after the promise has already settled; + * repeated calls in particular are safe, but only the first one + * will set the cancellation cause. + * + * The `CancelError` exception _need not_ be handled explicitly _on the promises that are being cancelled:_ + * cancelling a promise with no attached rejection handler does not trigger an unhandled rejection event. + * Therefore, the following idioms are all equally correct: + * ```ts + * new CancellablePromise((resolve, reject) => { ... }).cancel(); + * new CancellablePromise((resolve, reject) => { ... }).then(...).cancel(); + * new CancellablePromise((resolve, reject) => { ... }).then(...).catch(...).cancel(); + * ``` + * Whenever some cancelled promise in a chain rejects with a `CancelError` + * with the same cancellation cause as itself, the error will be discarded silently. + * However, the `CancelError` _will still be delivered_ to all attached rejection handlers + * added by {@link then} and related methods: + * ```ts + * let cancellable = new CancellablePromise((resolve, reject) => { ... }); + * cancellable.then(() => { ... }).catch(console.log); + * cancellable.cancel(); // A CancelError is printed to the console. + * ``` + * If the `CancelError` is not handled downstream by the time it reaches + * a _non-cancelled_ promise, it _will_ trigger an unhandled rejection event, + * just like normal rejections would: + * ```ts + * let cancellable = new CancellablePromise((resolve, reject) => { ... }); + * let chained = cancellable.then(() => { ... }).then(() => { ... }); // No catch... + * cancellable.cancel(); // Unhandled rejection event on chained! + * ``` + * Therefore, it is important to either cancel whole promise chains from their tail, + * as shown in the correct idioms above, or take care of handling errors everywhere. + * + * @returns A cancellable promise that _fulfills_ after the cancel callback (if any) + * and all handlers attached up to the call to cancel have run. + * If the cancel callback returns a thenable, the promise returned by `cancel` + * will also wait for that thenable to settle. + * This enables callers to wait for the cancelled operation to terminate + * without being forced to handle potential errors at the call site. + * ```ts + * cancellable.cancel().then(() => { + * // Cleanup finished, it's safe to do something else. + * }, (err) => { + * // Unreachable: the promise returned from cancel will never reject. + * }); + * ``` + * Note that the returned promise will _not_ handle implicitly any rejection + * that might have occurred already in the cancelled chain. + * It will just track whether registered handlers have been executed or not. + * Therefore, unhandled rejections will never be silently handled by calling cancel. + */ + cancel(cause) { + return new _CancellablePromise((resolve) => { + Promise.all([ + this[cancelImplSym](new CancelError("Promise cancelled.", { cause })), + currentBarrier(this) + ]).then(() => resolve(), () => resolve()); + }); + } + /** + * Binds promise cancellation to the abort event of the given {@link AbortSignal}. + * If the signal has already aborted, the promise will be cancelled immediately. + * When either condition is verified, the cancellation cause will be set + * to the signal's abort reason (see {@link AbortSignal#reason}). + * + * Has no effect if called (or if the signal aborts) _after_ the promise has already settled. + * Only the first signal to abort will set the cancellation cause. + * + * For more details about the cancellation process, + * see {@link cancel} and the `CancellablePromise` constructor. + * + * This method enables `await`ing cancellable promises without having + * to store them for future cancellation, e.g.: + * ```ts + * await longRunningOperation().cancelOn(signal); + * ``` + * instead of: + * ```ts + * let promiseToBeCancelled = longRunningOperation(); + * await promiseToBeCancelled; + * ``` + * + * @returns This promise, for method chaining. + */ + cancelOn(signal) { + if (signal.aborted) { + void this.cancel(signal.reason); + } else { + signal.addEventListener("abort", () => void this.cancel(signal.reason), { capture: true }); + } + return this; + } + /** + * Attaches callbacks for the resolution and/or rejection of the `CancellablePromise`. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * When the parent promise rejects or is cancelled, the `onrejected` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A `CancellablePromise` for the completion of whichever callback is executed. + * The returned promise is hooked up to propagate cancellation requests up the chain, but not down: + * + * - if the parent promise is cancelled, the `onrejected` handler will be invoked with a `CancelError` + * and the returned promise _will resolve regularly_ with its result; + * - conversely, if the returned promise is cancelled, _the parent promise is cancelled too;_ + * the `onrejected` handler will still be invoked with the parent's `CancelError`, + * but its result will be discarded + * and the returned promise will reject with a `CancelError` as well. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If either callback returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + */ + then(onfulfilled, onrejected, oncancelled) { + if (!(this instanceof _CancellablePromise)) { + throw new TypeError("CancellablePromise.prototype.then called on an invalid object."); + } + if (!callable_default(onfulfilled)) { + onfulfilled = identity; + } + if (!callable_default(onrejected)) { + onrejected = thrower; + } + if (onfulfilled === identity && onrejected == thrower) { + return new _CancellablePromise((resolve) => resolve(this)); + } + const barrier = {}; + this[barrierSym] = barrier; + return new _CancellablePromise((resolve, reject) => { + void super.then( + (value) => { + var _a2; + if (this[barrierSym] === barrier) { + this[barrierSym] = null; + } + (_a2 = barrier.resolve) == null ? void 0 : _a2.call(barrier); + try { + resolve(onfulfilled(value)); + } catch (err) { + reject(err); + } + }, + (reason) => { + var _a2; + if (this[barrierSym] === barrier) { + this[barrierSym] = null; + } + (_a2 = barrier.resolve) == null ? void 0 : _a2.call(barrier); + try { + resolve(onrejected(reason)); + } catch (err) { + reject(err); + } + } + ); + }, async (cause) => { + try { + return oncancelled == null ? void 0 : oncancelled(cause); + } finally { + await this.cancel(cause); + } + }); + } + /** + * Attaches a callback for only the rejection of the Promise. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * When the parent promise rejects or is cancelled, the `onrejected` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * It is equivalent to + * ```ts + * cancellablePromise.then(undefined, onrejected, oncancelled); + * ``` + * and the same caveats apply. + * + * @returns A Promise for the completion of the callback. + * Cancellation requests on the returned promise + * will propagate up the chain to the parent promise, + * but not in the other direction. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If `onrejected` returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + * See {@link then} for more details. + */ + catch(onrejected, oncancelled) { + return this.then(void 0, onrejected, oncancelled); + } + /** + * Attaches a callback that is invoked when the CancellablePromise is settled (fulfilled or rejected). The + * resolved value cannot be accessed or modified from the callback. + * The returned promise will settle in the same state as the original one + * after the provided callback has completed execution, + * unless the callback throws or returns a rejecting promise, + * in which case the returned promise will reject as well. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * Once the parent promise settles, the `onfinally` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * This method is implemented in terms of {@link then} and the same caveats apply. + * It is polyfilled, hence available in every OS/webview version. + * + * @returns A Promise for the completion of the callback. + * Cancellation requests on the returned promise + * will propagate up the chain to the parent promise, + * but not in the other direction. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If `onfinally` returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + * See {@link then} for more details. + */ + finally(onfinally, oncancelled) { + if (!(this instanceof _CancellablePromise)) { + throw new TypeError("CancellablePromise.prototype.finally called on an invalid object."); + } + if (!callable_default(onfinally)) { + return this.then(onfinally, onfinally, oncancelled); + } + return this.then( + (value) => _CancellablePromise.resolve(onfinally()).then(() => value), + (reason) => _CancellablePromise.resolve(onfinally()).then(() => { + throw reason; + }), + oncancelled + ); + } + /** + * We use the `[Symbol.species]` static property, if available, + * to disable the built-in automatic subclassing features from {@link Promise}. + * It is critical for performance reasons that extenders do not override this. + * Once the proposal at https://github.com/tc39/proposal-rm-builtin-subclassing + * is either accepted or retired, this implementation will have to be revised accordingly. + * + * @ignore + * @internal + */ + static get [(barrierSym, cancelImplSym, species)]() { + return Promise; + } + static all(values) { + let collected = Array.from(values); + const promise = collected.length === 0 ? _CancellablePromise.resolve(collected) : new _CancellablePromise((resolve, reject) => { + void Promise.all(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + static allSettled(values) { + let collected = Array.from(values); + const promise = collected.length === 0 ? _CancellablePromise.resolve(collected) : new _CancellablePromise((resolve, reject) => { + void Promise.allSettled(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + static any(values) { + let collected = Array.from(values); + const promise = collected.length === 0 ? _CancellablePromise.resolve(collected) : new _CancellablePromise((resolve, reject) => { + void Promise.any(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + static race(values) { + let collected = Array.from(values); + const promise = new _CancellablePromise((resolve, reject) => { + void Promise.race(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + /** + * Creates a new cancelled CancellablePromise for the provided cause. + * + * @group Static Methods + */ + static cancel(cause) { + const p = new _CancellablePromise(() => { + }); + p.cancel(cause); + return p; + } + /** + * Creates a new CancellablePromise that cancels + * after the specified timeout, with the provided cause. + * + * If the {@link AbortSignal.timeout} factory method is available, + * it is used to base the timeout on _active_ time rather than _elapsed_ time. + * Otherwise, `timeout` falls back to {@link setTimeout}. + * + * @group Static Methods + */ + static timeout(milliseconds, cause) { + const promise = new _CancellablePromise(() => { + }); + if (AbortSignal && typeof AbortSignal === "function" && AbortSignal.timeout && typeof AbortSignal.timeout === "function") { + AbortSignal.timeout(milliseconds).addEventListener("abort", () => void promise.cancel(cause)); + } else { + setTimeout(() => void promise.cancel(cause), milliseconds); + } + return promise; + } + static sleep(milliseconds, value) { + return new _CancellablePromise((resolve) => { + setTimeout(() => resolve(value), milliseconds); + }); + } + /** + * Creates a new rejected CancellablePromise for the provided reason. + * + * @group Static Methods + */ + static reject(reason) { + return new _CancellablePromise((_, reject) => reject(reason)); + } + static resolve(value) { + if (value instanceof _CancellablePromise) { + return value; + } + return new _CancellablePromise((resolve) => resolve(value)); + } + /** + * Creates a new CancellablePromise and returns it in an object, along with its resolve and reject functions + * and a getter/setter for the cancellation callback. + * + * This method is polyfilled, hence available in every OS/webview version. + * + * @group Static Methods + */ + static withResolvers() { + let result = { oncancelled: null }; + result.promise = new _CancellablePromise((resolve, reject) => { + result.resolve = resolve; + result.reject = reject; + }, (cause) => { + var _a2; + (_a2 = result.oncancelled) == null ? void 0 : _a2.call(result, cause); + }); + return result; + } +}; +function cancellerFor(promise, state) { + let cancellationPromise = void 0; + return (reason) => { + if (!state.settled) { + state.settled = true; + state.reason = reason; + promise.reject(reason); + void Promise.prototype.then.call(promise.promise, void 0, (err) => { + if (err !== reason) { + throw err; + } + }); + } + if (!state.reason || !promise.oncancelled) { + return; + } + cancellationPromise = new Promise((resolve) => { + try { + resolve(promise.oncancelled(state.reason.cause)); + } catch (err) { + Promise.reject(new CancelledRejectionError(promise.promise, err, "Unhandled exception in oncancelled callback.")); + } + }).catch((reason2) => { + Promise.reject(new CancelledRejectionError(promise.promise, reason2, "Unhandled rejection in oncancelled callback.")); + }); + promise.oncancelled = null; + return cancellationPromise; + }; +} +function resolverFor(promise, state) { + return (value) => { + if (state.resolving) { + return; + } + state.resolving = true; + if (value === promise.promise) { + if (state.settled) { + return; + } + state.settled = true; + promise.reject(new TypeError("A promise cannot be resolved with itself.")); + return; + } + if (value != null && (typeof value === "object" || typeof value === "function")) { + let then; + try { + then = value.then; + } catch (err) { + state.settled = true; + promise.reject(err); + return; + } + if (callable_default(then)) { + try { + let cancel = value.cancel; + if (callable_default(cancel)) { + const oncancelled = (cause) => { + Reflect.apply(cancel, value, [cause]); + }; + if (state.reason) { + void cancellerFor(__spreadProps(__spreadValues({}, promise), { oncancelled }), state)(state.reason); + } else { + promise.oncancelled = oncancelled; + } + } + } catch (e) { + } + const newState = { + root: state.root, + resolving: false, + get settled() { + return this.root.settled; + }, + set settled(value2) { + this.root.settled = value2; + }, + get reason() { + return this.root.reason; + } + }; + const rejector = rejectorFor(promise, newState); + try { + Reflect.apply(then, value, [resolverFor(promise, newState), rejector]); + } catch (err) { + rejector(err); + } + return; + } + } + if (state.settled) { + return; + } + state.settled = true; + promise.resolve(value); + }; +} +function rejectorFor(promise, state) { + return (reason) => { + if (state.resolving) { + return; + } + state.resolving = true; + if (state.settled) { + try { + if (reason instanceof CancelError && state.reason instanceof CancelError && Object.is(reason.cause, state.reason.cause)) { + return; + } + } catch (e) { + } + void Promise.reject(new CancelledRejectionError(promise.promise, reason)); + } else { + state.settled = true; + promise.reject(reason); + } + }; +} +function cancelAll(parent, values, cause) { + const results = []; + for (const value of values) { + let cancel; + try { + if (!callable_default(value.then)) { + continue; + } + cancel = value.cancel; + if (!callable_default(cancel)) { + continue; + } + } catch (e) { + continue; + } + let result; + try { + result = Reflect.apply(cancel, value, [cause]); + } catch (err) { + Promise.reject(new CancelledRejectionError(parent, err, "Unhandled exception in cancel method.")); + continue; + } + if (!result) { + continue; + } + results.push( + (result instanceof Promise ? result : Promise.resolve(result)).catch((reason) => { + Promise.reject(new CancelledRejectionError(parent, reason, "Unhandled rejection in cancel method.")); + }) + ); + } + return Promise.all(results); +} +function identity(x) { + return x; +} +function thrower(reason) { + throw reason; +} +function errorMessage(err) { + try { + if (err instanceof Error || typeof err !== "object" || err.toString !== Object.prototype.toString) { + return "" + err; + } + } catch (e) { + } + try { + return JSON.stringify(err); + } catch (e) { + } + try { + return Object.prototype.toString.call(err); + } catch (e) { + } + return ""; +} +function currentBarrier(promise) { + var _a2; + let pwr = (_a2 = promise[barrierSym]) != null ? _a2 : {}; + if (!("promise" in pwr)) { + Object.assign(pwr, promiseWithResolvers()); + } + if (promise[barrierSym] == null) { + pwr.resolve(); + promise[barrierSym] = pwr; + } + return pwr.promise; +} +var promiseWithResolvers = Promise.withResolvers; +if (promiseWithResolvers && typeof promiseWithResolvers === "function") { + promiseWithResolvers = promiseWithResolvers.bind(Promise); +} else { + promiseWithResolvers = function() { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; + }; +} + +// desktop/@wailsio/runtime/src/calls.ts +window._wails = window._wails || {}; +var call7 = newRuntimeCaller(objectNames.Call); +var cancelCall = newRuntimeCaller(objectNames.CancelCall); +var callResponses = /* @__PURE__ */ new Map(); +var CallBinding = 0; +var CancelMethod = 0; +var RuntimeError = class extends Error { + /** + * Constructs a new RuntimeError instance. + * @param message - The error message. + * @param options - Options to be forwarded to the Error constructor. + */ + constructor(message, options) { + super(message, options); + this.name = "RuntimeError"; + } +}; +function generateID() { + let result; + do { + result = nanoid(); + } while (callResponses.has(result)); + return result; +} +function Call(options) { + const id = generateID(); + const result = CancellablePromise.withResolvers(); + callResponses.set(id, { resolve: result.resolve, reject: result.reject }); + const request = call7(CallBinding, Object.assign({ "call-id": id }, options)); + let running = true; + request.then((res) => { + running = false; + callResponses.delete(id); + result.resolve(res); + }, (err) => { + running = false; + callResponses.delete(id); + result.reject(err); + }); + const cancel = () => { + callResponses.delete(id); + return cancelCall(CancelMethod, { "call-id": id }).catch((err) => { + console.error("Error while requesting binding call cancellation:", err); + }); + }; + result.oncancelled = () => { + if (running) { + return cancel(); + } else { + return request.then(cancel); + } + }; + return result.promise; +} +function ByName(methodName, ...args) { + return Call({ methodName, args }); +} +function ByID(methodID, ...args) { + return Call({ methodID, args }); +} + +// desktop/@wailsio/runtime/src/clipboard.ts +var clipboard_exports = {}; +__export(clipboard_exports, { + SetText: () => SetText, + Text: () => Text +}); +var call8 = newRuntimeCaller(objectNames.Clipboard); +var ClipboardSetText = 0; +var ClipboardText = 1; +function SetText(text) { + return call8(ClipboardSetText, { text }); +} +function Text() { + return call8(ClipboardText); +} + +// desktop/@wailsio/runtime/src/screens.ts +var screens_exports = {}; +__export(screens_exports, { + GetAll: () => GetAll, + GetCurrent: () => GetCurrent, + GetPrimary: () => GetPrimary +}); +var call9 = newRuntimeCaller(objectNames.Screens); +var getAll = 0; +var getPrimary = 1; +var getCurrent = 2; +function GetAll() { + return call9(getAll); +} +function GetPrimary() { + return call9(getPrimary); +} +function GetCurrent() { + return call9(getCurrent); +} + +// desktop/@wailsio/runtime/src/ios.ts +var ios_exports = {}; +__export(ios_exports, { + Device: () => Device, + Haptics: () => Haptics +}); +var call10 = newRuntimeCaller(objectNames.IOS); +var HapticsImpact = 0; +var DeviceInfo = 1; +var Haptics; +((Haptics2) => { + function Impact(style = "medium") { + return call10(HapticsImpact, { style }); + } + Haptics2.Impact = Impact; +})(Haptics || (Haptics = {})); +var Device; +((Device2) => { + function Info2() { + return call10(DeviceInfo); + } + Device2.Info = Info2; +})(Device || (Device = {})); + +// desktop/@wailsio/runtime/src/index.ts +window._wails = window._wails || {}; +window._wails.invoke = invoke; +window._wails.clientId = clientId; +window._wails.handlePlatformFileDrop = window_default.HandlePlatformFileDrop.bind(window_default); +window._wails.handleDragEnter = handleDragEnter; +window._wails.handleDragLeave = handleDragLeave; +window._wails.handleDragOver = handleDragOver; +invoke("wails:runtime:ready"); +function loadOptionalScript(url) { + return fetch(url, { method: "HEAD" }).then((response) => { + if (response.ok) { + const script = document.createElement("script"); + script.src = url; + document.head.appendChild(script); + } + }).catch(() => { + }); +} +loadOptionalScript("/wails/custom.js"); +export { + application_exports as Application, + browser_exports as Browser, + calls_exports as Call, + CancelError, + CancellablePromise, + CancelledRejectionError, + clipboard_exports as Clipboard, + create_exports as Create, + dialogs_exports as Dialogs, + events_exports as Events, + flags_exports as Flags, + ios_exports as IOS, + screens_exports as Screens, + system_exports as System, + wml_exports as WML, + window_default as Window, + clientId, + getTransport, + loadOptionalScript, + objectNames, + setTransport +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2luZGV4LnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy93bWwudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2Jyb3dzZXIudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL25hbm9pZC50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvcnVudGltZS50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvZGlhbG9ncy50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvZXZlbnRzLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9saXN0ZW5lci50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvY3JlYXRlLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9ldmVudF90eXBlcy50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvdXRpbHMudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL3dpbmRvdy50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvY29tcGlsZWQvbWFpbi5qcyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvc3lzdGVtLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9jb250ZXh0bWVudS50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvZmxhZ3MudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2RyYWcudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2FwcGxpY2F0aW9uLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9jYWxscy50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvY2FsbGFibGUudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2NhbmNlbGxhYmxlLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9jbGlwYm9hcmQudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL3NjcmVlbnMudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2lvcy50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8vIFNldHVwXG53aW5kb3cuX3dhaWxzID0gd2luZG93Ll93YWlscyB8fCB7fTtcblxuaW1wb3J0IFwiLi9jb250ZXh0bWVudS5qc1wiO1xuaW1wb3J0IFwiLi9kcmFnLmpzXCI7XG5cbi8vIFJlLWV4cG9ydCBwdWJsaWMgQVBJXG5pbXBvcnQgKiBhcyBBcHBsaWNhdGlvbiBmcm9tIFwiLi9hcHBsaWNhdGlvbi5qc1wiO1xuaW1wb3J0ICogYXMgQnJvd3NlciBmcm9tIFwiLi9icm93c2VyLmpzXCI7XG5pbXBvcnQgKiBhcyBDYWxsIGZyb20gXCIuL2NhbGxzLmpzXCI7XG5pbXBvcnQgKiBhcyBDbGlwYm9hcmQgZnJvbSBcIi4vY2xpcGJvYXJkLmpzXCI7XG5pbXBvcnQgKiBhcyBDcmVhdGUgZnJvbSBcIi4vY3JlYXRlLmpzXCI7XG5pbXBvcnQgKiBhcyBEaWFsb2dzIGZyb20gXCIuL2RpYWxvZ3MuanNcIjtcbmltcG9ydCAqIGFzIEV2ZW50cyBmcm9tIFwiLi9ldmVudHMuanNcIjtcbmltcG9ydCAqIGFzIEZsYWdzIGZyb20gXCIuL2ZsYWdzLmpzXCI7XG5pbXBvcnQgKiBhcyBTY3JlZW5zIGZyb20gXCIuL3NjcmVlbnMuanNcIjtcbmltcG9ydCAqIGFzIFN5c3RlbSBmcm9tIFwiLi9zeXN0ZW0uanNcIjtcbmltcG9ydCAqIGFzIElPUyBmcm9tIFwiLi9pb3MuanNcIjtcbmltcG9ydCBXaW5kb3csIHsgaGFuZGxlRHJhZ0VudGVyLCBoYW5kbGVEcmFnTGVhdmUsIGhhbmRsZURyYWdPdmVyIH0gZnJvbSBcIi4vd2luZG93LmpzXCI7XG5pbXBvcnQgKiBhcyBXTUwgZnJvbSBcIi4vd21sLmpzXCI7XG5cbmV4cG9ydCB7XG4gICAgQXBwbGljYXRpb24sXG4gICAgQnJvd3NlcixcbiAgICBDYWxsLFxuICAgIENsaXBib2FyZCxcbiAgICBEaWFsb2dzLFxuICAgIEV2ZW50cyxcbiAgICBGbGFncyxcbiAgICBTY3JlZW5zLFxuICAgIFN5c3RlbSxcbiAgICBJT1MsXG4gICAgV2luZG93LFxuICAgIFdNTFxufTtcblxuLyoqXG4gKiBBbiBpbnRlcm5hbCB1dGlsaXR5IGNvbnN1bWVkIGJ5IHRoZSBiaW5kaW5nIGdlbmVyYXRvci5cbiAqXG4gKiBAaWdub3JlXG4gKi9cbmV4cG9ydCB7IENyZWF0ZSB9O1xuXG5leHBvcnQgKiBmcm9tIFwiLi9jYW5jZWxsYWJsZS5qc1wiO1xuXG4vLyBFeHBvcnQgdHJhbnNwb3J0IGludGVyZmFjZXMgYW5kIHV0aWxpdGllc1xuZXhwb3J0IHtcbiAgICBzZXRUcmFuc3BvcnQsXG4gICAgZ2V0VHJhbnNwb3J0LFxuICAgIHR5cGUgUnVudGltZVRyYW5zcG9ydCxcbiAgICBvYmplY3ROYW1lcyxcbiAgICBjbGllbnRJZCxcbn0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuXG5pbXBvcnQgeyBjbGllbnRJZCB9IGZyb20gXCIuL3J1bnRpbWUuanNcIjtcblxuLy8gTm90aWZ5IGJhY2tlbmRcbndpbmRvdy5fd2FpbHMuaW52b2tlID0gU3lzdGVtLmludm9rZTtcbndpbmRvdy5fd2FpbHMuY2xpZW50SWQgPSBjbGllbnRJZDtcblxuLy8gUmVnaXN0ZXIgcGxhdGZvcm0gaGFuZGxlcnMgKGludGVybmFsIEFQSSlcbi8vIE5vdGU6IFdpbmRvdyBpcyB0aGUgdGhpc1dpbmRvdyBpbnN0YW5jZSAoZGVmYXVsdCBleHBvcnQgZnJvbSB3aW5kb3cudHMpXG4vLyBCaW5kaW5nIGVuc3VyZXMgJ3RoaXMnIGNvcnJlY3RseSByZWZlcnMgdG8gdGhlIGN1cnJlbnQgd2luZG93IGluc3RhbmNlXG53aW5kb3cuX3dhaWxzLmhhbmRsZVBsYXRmb3JtRmlsZURyb3AgPSBXaW5kb3cuSGFuZGxlUGxhdGZvcm1GaWxlRHJvcC5iaW5kKFdpbmRvdyk7XG5cbi8vIExpbnV4LXNwZWNpZmljIGRyYWcgaGFuZGxlcnMgKEdUSyBpbnRlcmNlcHRzIERPTSBkcmFnIGV2ZW50cylcbndpbmRvdy5fd2FpbHMuaGFuZGxlRHJhZ0VudGVyID0gaGFuZGxlRHJhZ0VudGVyO1xud2luZG93Ll93YWlscy5oYW5kbGVEcmFnTGVhdmUgPSBoYW5kbGVEcmFnTGVhdmU7XG53aW5kb3cuX3dhaWxzLmhhbmRsZURyYWdPdmVyID0gaGFuZGxlRHJhZ092ZXI7XG5cblN5c3RlbS5pbnZva2UoXCJ3YWlsczpydW50aW1lOnJlYWR5XCIpO1xuXG4vKipcbiAqIExvYWRzIGEgc2NyaXB0IGZyb20gdGhlIGdpdmVuIFVSTCBpZiBpdCBleGlzdHMuXG4gKiBVc2VzIEhFQUQgcmVxdWVzdCB0byBjaGVjayBleGlzdGVuY2UsIHRoZW4gaW5qZWN0cyBhIHNjcmlwdCB0YWcuXG4gKiBTaWxlbnRseSBpZ25vcmVzIGlmIHRoZSBzY3JpcHQgZG9lc24ndCBleGlzdC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGxvYWRPcHRpb25hbFNjcmlwdCh1cmw6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBmZXRjaCh1cmwsIHsgbWV0aG9kOiAnSEVBRCcgfSlcbiAgICAgICAgLnRoZW4ocmVzcG9uc2UgPT4ge1xuICAgICAgICAgICAgaWYgKHJlc3BvbnNlLm9rKSB7XG4gICAgICAgICAgICAgICAgY29uc3Qgc2NyaXB0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc2NyaXB0Jyk7XG4gICAgICAgICAgICAgICAgc2NyaXB0LnNyYyA9IHVybDtcbiAgICAgICAgICAgICAgICBkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHNjcmlwdCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pXG4gICAgICAgIC5jYXRjaCgoKSA9PiB7fSk7IC8vIFNpbGVudGx5IGlnbm9yZSAtIHNjcmlwdCBpcyBvcHRpb25hbFxufVxuXG4vLyBMb2FkIGN1c3RvbS5qcyBpZiBhdmFpbGFibGUgKHVzZWQgYnkgc2VydmVyIG1vZGUgZm9yIFdlYlNvY2tldCBldmVudHMsIGV0Yy4pXG5sb2FkT3B0aW9uYWxTY3JpcHQoJy93YWlscy9jdXN0b20uanMnKTtcbiIsICIvKlxuIF8gICAgIF9fICAgICBfIF9fXG58IHwgIC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHsgT3BlblVSTCB9IGZyb20gXCIuL2Jyb3dzZXIuanNcIjtcbmltcG9ydCB7IFF1ZXN0aW9uIH0gZnJvbSBcIi4vZGlhbG9ncy5qc1wiO1xuaW1wb3J0IHsgRW1pdCB9IGZyb20gXCIuL2V2ZW50cy5qc1wiO1xuaW1wb3J0IHsgY2FuQWJvcnRMaXN0ZW5lcnMsIHdoZW5SZWFkeSB9IGZyb20gXCIuL3V0aWxzLmpzXCI7XG5pbXBvcnQgV2luZG93IGZyb20gXCIuL3dpbmRvdy5qc1wiO1xuXG4vKipcbiAqIFNlbmRzIGFuIGV2ZW50IHdpdGggdGhlIGdpdmVuIG5hbWUgYW5kIG9wdGlvbmFsIGRhdGEuXG4gKlxuICogQHBhcmFtIGV2ZW50TmFtZSAtIC0gVGhlIG5hbWUgb2YgdGhlIGV2ZW50IHRvIHNlbmQuXG4gKiBAcGFyYW0gW2RhdGE9bnVsbF0gLSAtIE9wdGlvbmFsIGRhdGEgdG8gc2VuZCBhbG9uZyB3aXRoIHRoZSBldmVudC5cbiAqL1xuZnVuY3Rpb24gc2VuZEV2ZW50KGV2ZW50TmFtZTogc3RyaW5nLCBkYXRhOiBhbnkgPSBudWxsKTogdm9pZCB7XG4gICAgRW1pdChldmVudE5hbWUsIGRhdGEpO1xufVxuXG4vKipcbiAqIENhbGxzIGEgbWV0aG9kIG9uIGEgc3BlY2lmaWVkIHdpbmRvdy5cbiAqXG4gKiBAcGFyYW0gd2luZG93TmFtZSAtIFRoZSBuYW1lIG9mIHRoZSB3aW5kb3cgdG8gY2FsbCB0aGUgbWV0aG9kIG9uLlxuICogQHBhcmFtIG1ldGhvZE5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgbWV0aG9kIHRvIGNhbGwuXG4gKi9cbmZ1bmN0aW9uIGNhbGxXaW5kb3dNZXRob2Qod2luZG93TmFtZTogc3RyaW5nLCBtZXRob2ROYW1lOiBzdHJpbmcpIHtcbiAgICBjb25zdCB0YXJnZXRXaW5kb3cgPSBXaW5kb3cuR2V0KHdpbmRvd05hbWUpO1xuICAgIGNvbnN0IG1ldGhvZCA9ICh0YXJnZXRXaW5kb3cgYXMgYW55KVttZXRob2ROYW1lXTtcblxuICAgIGlmICh0eXBlb2YgbWV0aG9kICE9PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgICAgY29uc29sZS5lcnJvcihgV2luZG93IG1ldGhvZCAnJHttZXRob2ROYW1lfScgbm90IGZvdW5kYCk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB0cnkge1xuICAgICAgICBtZXRob2QuY2FsbCh0YXJnZXRXaW5kb3cpO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgY29uc29sZS5lcnJvcihgRXJyb3IgY2FsbGluZyB3aW5kb3cgbWV0aG9kICcke21ldGhvZE5hbWV9JzogYCwgZSk7XG4gICAgfVxufVxuXG4vKipcbiAqIFJlc3BvbmRzIHRvIGEgdHJpZ2dlcmluZyBldmVudCBieSBydW5uaW5nIGFwcHJvcHJpYXRlIFdNTCBhY3Rpb25zIGZvciB0aGUgY3VycmVudCB0YXJnZXQuXG4gKi9cbmZ1bmN0aW9uIG9uV01MVHJpZ2dlcmVkKGV2OiBFdmVudCk6IHZvaWQge1xuICAgIGNvbnN0IGVsZW1lbnQgPSBldi5jdXJyZW50VGFyZ2V0IGFzIEVsZW1lbnQ7XG5cbiAgICBmdW5jdGlvbiBydW5FZmZlY3QoY2hvaWNlID0gXCJZZXNcIikge1xuICAgICAgICBpZiAoY2hvaWNlICE9PSBcIlllc1wiKVxuICAgICAgICAgICAgcmV0dXJuO1xuXG4gICAgICAgIGNvbnN0IGV2ZW50VHlwZSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtZXZlbnQnKSB8fCBlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS13bWwtZXZlbnQnKTtcbiAgICAgICAgY29uc3QgdGFyZ2V0V2luZG93ID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ3dtbC10YXJnZXQtd2luZG93JykgfHwgZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtd21sLXRhcmdldC13aW5kb3cnKSB8fCBcIlwiO1xuICAgICAgICBjb25zdCB3aW5kb3dNZXRob2QgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLXdpbmRvdycpIHx8IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC13aW5kb3cnKTtcbiAgICAgICAgY29uc3QgdXJsID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ3dtbC1vcGVudXJsJykgfHwgZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtd21sLW9wZW51cmwnKTtcblxuICAgICAgICBpZiAoZXZlbnRUeXBlICE9PSBudWxsKVxuICAgICAgICAgICAgc2VuZEV2ZW50KGV2ZW50VHlwZSk7XG4gICAgICAgIGlmICh3aW5kb3dNZXRob2QgIT09IG51bGwpXG4gICAgICAgICAgICBjYWxsV2luZG93TWV0aG9kKHRhcmdldFdpbmRvdywgd2luZG93TWV0aG9kKTtcbiAgICAgICAgaWYgKHVybCAhPT0gbnVsbClcbiAgICAgICAgICAgIHZvaWQgT3BlblVSTCh1cmwpO1xuICAgIH1cblxuICAgIGNvbnN0IGNvbmZpcm0gPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLWNvbmZpcm0nKSB8fCBlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS13bWwtY29uZmlybScpO1xuXG4gICAgaWYgKGNvbmZpcm0pIHtcbiAgICAgICAgUXVlc3Rpb24oe1xuICAgICAgICAgICAgVGl0bGU6IFwiQ29uZmlybVwiLFxuICAgICAgICAgICAgTWVzc2FnZTogY29uZmlybSxcbiAgICAgICAgICAgIERldGFjaGVkOiBmYWxzZSxcbiAgICAgICAgICAgIEJ1dHRvbnM6IFtcbiAgICAgICAgICAgICAgICB7IExhYmVsOiBcIlllc1wiIH0sXG4gICAgICAgICAgICAgICAgeyBMYWJlbDogXCJOb1wiLCBJc0RlZmF1bHQ6IHRydWUgfVxuICAgICAgICAgICAgXVxuICAgICAgICB9KS50aGVuKHJ1bkVmZmVjdCk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgcnVuRWZmZWN0KCk7XG4gICAgfVxufVxuXG4vLyBQcml2YXRlIGZpZWxkIG5hbWVzLlxuY29uc3QgY29udHJvbGxlclN5bSA9IFN5bWJvbChcImNvbnRyb2xsZXJcIik7XG5jb25zdCB0cmlnZ2VyTWFwU3ltID0gU3ltYm9sKFwidHJpZ2dlck1hcFwiKTtcbmNvbnN0IGVsZW1lbnRDb3VudFN5bSA9IFN5bWJvbChcImVsZW1lbnRDb3VudFwiKTtcblxuLyoqXG4gKiBBYm9ydENvbnRyb2xsZXJSZWdpc3RyeSBkb2VzIG5vdCBhY3R1YWxseSByZW1lbWJlciBhY3RpdmUgZXZlbnQgbGlzdGVuZXJzOiBpbnN0ZWFkXG4gKiBpdCB0aWVzIHRoZW0gdG8gYW4gQWJvcnRTaWduYWwgYW5kIHVzZXMgYW4gQWJvcnRDb250cm9sbGVyIHRvIHJlbW92ZSB0aGVtIGFsbCBhdCBvbmNlLlxuICovXG5jbGFzcyBBYm9ydENvbnRyb2xsZXJSZWdpc3RyeSB7XG4gICAgLy8gUHJpdmF0ZSBmaWVsZHMuXG4gICAgW2NvbnRyb2xsZXJTeW1dOiBBYm9ydENvbnRyb2xsZXI7XG5cbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpc1tjb250cm9sbGVyU3ltXSA9IG5ldyBBYm9ydENvbnRyb2xsZXIoKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIGFuIG9wdGlvbnMgb2JqZWN0IGZvciBhZGRFdmVudExpc3RlbmVyIHRoYXQgdGllcyB0aGUgbGlzdGVuZXJcbiAgICAgKiB0byB0aGUgQWJvcnRTaWduYWwgZnJvbSB0aGUgY3VycmVudCBBYm9ydENvbnRyb2xsZXIuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gZWxlbWVudCAtIEFuIEhUTUwgZWxlbWVudFxuICAgICAqIEBwYXJhbSB0cmlnZ2VycyAtIFRoZSBsaXN0IG9mIGFjdGl2ZSBXTUwgdHJpZ2dlciBldmVudHMgZm9yIHRoZSBzcGVjaWZpZWQgZWxlbWVudHNcbiAgICAgKi9cbiAgICBzZXQoZWxlbWVudDogRWxlbWVudCwgdHJpZ2dlcnM6IHN0cmluZ1tdKTogQWRkRXZlbnRMaXN0ZW5lck9wdGlvbnMge1xuICAgICAgICByZXR1cm4geyBzaWduYWw6IHRoaXNbY29udHJvbGxlclN5bV0uc2lnbmFsIH07XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVtb3ZlcyBhbGwgcmVnaXN0ZXJlZCBldmVudCBsaXN0ZW5lcnMgYW5kIHJlc2V0cyB0aGUgcmVnaXN0cnkuXG4gICAgICovXG4gICAgcmVzZXQoKTogdm9pZCB7XG4gICAgICAgIHRoaXNbY29udHJvbGxlclN5bV0uYWJvcnQoKTtcbiAgICAgICAgdGhpc1tjb250cm9sbGVyU3ltXSA9IG5ldyBBYm9ydENvbnRyb2xsZXIoKTtcbiAgICB9XG59XG5cbi8qKlxuICogV2Vha01hcFJlZ2lzdHJ5IG1hcHMgYWN0aXZlIHRyaWdnZXIgZXZlbnRzIHRvIGVhY2ggRE9NIGVsZW1lbnQgdGhyb3VnaCBhIFdlYWtNYXAuXG4gKiBUaGlzIGVuc3VyZXMgdGhhdCB0aGUgbWFwcGluZyByZW1haW5zIHByaXZhdGUgdG8gdGhpcyBtb2R1bGUsIHdoaWxlIHN0aWxsIGFsbG93aW5nIGdhcmJhZ2VcbiAqIGNvbGxlY3Rpb24gb2YgdGhlIGludm9sdmVkIGVsZW1lbnRzLlxuICovXG5jbGFzcyBXZWFrTWFwUmVnaXN0cnkge1xuICAgIC8qKiBTdG9yZXMgdGhlIGN1cnJlbnQgZWxlbWVudC10by10cmlnZ2VyIG1hcHBpbmcuICovXG4gICAgW3RyaWdnZXJNYXBTeW1dOiBXZWFrTWFwPEVsZW1lbnQsIHN0cmluZ1tdPjtcbiAgICAvKiogQ291bnRzIHRoZSBudW1iZXIgb2YgZWxlbWVudHMgd2l0aCBhY3RpdmUgV01MIHRyaWdnZXJzLiAqL1xuICAgIFtlbGVtZW50Q291bnRTeW1dOiBudW1iZXI7XG5cbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpc1t0cmlnZ2VyTWFwU3ltXSA9IG5ldyBXZWFrTWFwKCk7XG4gICAgICAgIHRoaXNbZWxlbWVudENvdW50U3ltXSA9IDA7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0cyBhY3RpdmUgdHJpZ2dlcnMgZm9yIHRoZSBzcGVjaWZpZWQgZWxlbWVudC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBlbGVtZW50IC0gQW4gSFRNTCBlbGVtZW50XG4gICAgICogQHBhcmFtIHRyaWdnZXJzIC0gVGhlIGxpc3Qgb2YgYWN0aXZlIFdNTCB0cmlnZ2VyIGV2ZW50cyBmb3IgdGhlIHNwZWNpZmllZCBlbGVtZW50XG4gICAgICovXG4gICAgc2V0KGVsZW1lbnQ6IEVsZW1lbnQsIHRyaWdnZXJzOiBzdHJpbmdbXSk6IEFkZEV2ZW50TGlzdGVuZXJPcHRpb25zIHtcbiAgICAgICAgaWYgKCF0aGlzW3RyaWdnZXJNYXBTeW1dLmhhcyhlbGVtZW50KSkgeyB0aGlzW2VsZW1lbnRDb3VudFN5bV0rKzsgfVxuICAgICAgICB0aGlzW3RyaWdnZXJNYXBTeW1dLnNldChlbGVtZW50LCB0cmlnZ2Vycyk7XG4gICAgICAgIHJldHVybiB7fTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZW1vdmVzIGFsbCByZWdpc3RlcmVkIGV2ZW50IGxpc3RlbmVycy5cbiAgICAgKi9cbiAgICByZXNldCgpOiB2b2lkIHtcbiAgICAgICAgaWYgKHRoaXNbZWxlbWVudENvdW50U3ltXSA8PSAwKVxuICAgICAgICAgICAgcmV0dXJuO1xuXG4gICAgICAgIGZvciAoY29uc3QgZWxlbWVudCBvZiBkb2N1bWVudC5ib2R5LnF1ZXJ5U2VsZWN0b3JBbGwoJyonKSkge1xuICAgICAgICAgICAgaWYgKHRoaXNbZWxlbWVudENvdW50U3ltXSA8PSAwKVxuICAgICAgICAgICAgICAgIGJyZWFrO1xuXG4gICAgICAgICAgICBjb25zdCB0cmlnZ2VycyA9IHRoaXNbdHJpZ2dlck1hcFN5bV0uZ2V0KGVsZW1lbnQpO1xuICAgICAgICAgICAgaWYgKHRyaWdnZXJzICE9IG51bGwpIHsgdGhpc1tlbGVtZW50Q291bnRTeW1dLS07IH1cblxuICAgICAgICAgICAgZm9yIChjb25zdCB0cmlnZ2VyIG9mIHRyaWdnZXJzIHx8IFtdKVxuICAgICAgICAgICAgICAgIGVsZW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcih0cmlnZ2VyLCBvbldNTFRyaWdnZXJlZCk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzW3RyaWdnZXJNYXBTeW1dID0gbmV3IFdlYWtNYXAoKTtcbiAgICAgICAgdGhpc1tlbGVtZW50Q291bnRTeW1dID0gMDtcbiAgICB9XG59XG5cbmNvbnN0IHRyaWdnZXJSZWdpc3RyeSA9IGNhbkFib3J0TGlzdGVuZXJzKCkgPyBuZXcgQWJvcnRDb250cm9sbGVyUmVnaXN0cnkoKSA6IG5ldyBXZWFrTWFwUmVnaXN0cnkoKTtcblxuLyoqXG4gKiBBZGRzIGV2ZW50IGxpc3RlbmVycyB0byB0aGUgc3BlY2lmaWVkIGVsZW1lbnQuXG4gKi9cbmZ1bmN0aW9uIGFkZFdNTExpc3RlbmVycyhlbGVtZW50OiBFbGVtZW50KTogdm9pZCB7XG4gICAgY29uc3QgdHJpZ2dlclJlZ0V4cCA9IC9cXFMrL2c7XG4gICAgY29uc3QgdHJpZ2dlckF0dHIgPSAoZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ3dtbC10cmlnZ2VyJykgfHwgZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtd21sLXRyaWdnZXInKSB8fCBcImNsaWNrXCIpO1xuICAgIGNvbnN0IHRyaWdnZXJzOiBzdHJpbmdbXSA9IFtdO1xuXG4gICAgbGV0IG1hdGNoO1xuICAgIHdoaWxlICgobWF0Y2ggPSB0cmlnZ2VyUmVnRXhwLmV4ZWModHJpZ2dlckF0dHIpKSAhPT0gbnVsbClcbiAgICAgICAgdHJpZ2dlcnMucHVzaChtYXRjaFswXSk7XG5cbiAgICBjb25zdCBvcHRpb25zID0gdHJpZ2dlclJlZ2lzdHJ5LnNldChlbGVtZW50LCB0cmlnZ2Vycyk7XG4gICAgZm9yIChjb25zdCB0cmlnZ2VyIG9mIHRyaWdnZXJzKVxuICAgICAgICBlbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIodHJpZ2dlciwgb25XTUxUcmlnZ2VyZWQsIG9wdGlvbnMpO1xufVxuXG4vKipcbiAqIFNjaGVkdWxlcyBhbiBhdXRvbWF0aWMgcmVsb2FkIG9mIFdNTCB0byBiZSBwZXJmb3JtZWQgYXMgc29vbiBhcyB0aGUgZG9jdW1lbnQgaXMgZnVsbHkgbG9hZGVkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gRW5hYmxlKCk6IHZvaWQge1xuICAgIHdoZW5SZWFkeShSZWxvYWQpO1xufVxuXG4vKipcbiAqIFJlbG9hZHMgdGhlIFdNTCBwYWdlIGJ5IGFkZGluZyBuZWNlc3NhcnkgZXZlbnQgbGlzdGVuZXJzIGFuZCBicm93c2VyIGxpc3RlbmVycy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFJlbG9hZCgpOiB2b2lkIHtcbiAgICB0cmlnZ2VyUmVnaXN0cnkucmVzZXQoKTtcbiAgICBkb2N1bWVudC5ib2R5LnF1ZXJ5U2VsZWN0b3JBbGwoJ1t3bWwtZXZlbnRdLCBbd21sLXdpbmRvd10sIFt3bWwtb3BlbnVybF0sIFtkYXRhLXdtbC1ldmVudF0sIFtkYXRhLXdtbC13aW5kb3ddLCBbZGF0YS13bWwtb3BlbnVybF0nKS5mb3JFYWNoKGFkZFdNTExpc3RlbmVycyk7XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IG5ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzIH0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuXG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5Ccm93c2VyKTtcblxuY29uc3QgQnJvd3Nlck9wZW5VUkwgPSAwO1xuXG4vKipcbiAqIE9wZW4gYSBicm93c2VyIHdpbmRvdyB0byB0aGUgZ2l2ZW4gVVJMLlxuICpcbiAqIEBwYXJhbSB1cmwgLSBUaGUgVVJMIHRvIG9wZW5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9wZW5VUkwodXJsOiBzdHJpbmcgfCBVUkwpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICByZXR1cm4gY2FsbChCcm93c2VyT3BlblVSTCwge3VybDogdXJsLnRvU3RyaW5nKCl9KTtcbn1cbiIsICIvLyBTb3VyY2U6IGh0dHBzOi8vZ2l0aHViLmNvbS9haS9uYW5vaWRcblxuLy8gVGhlIE1JVCBMaWNlbnNlIChNSVQpXG4vL1xuLy8gQ29weXJpZ2h0IDIwMTcgQW5kcmV5IFNpdG5payA8YW5kcmV5QHNpdG5pay5ydT5cbi8vXG4vLyBQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5IG9mXG4vLyB0aGlzIHNvZnR3YXJlIGFuZCBhc3NvY2lhdGVkIGRvY3VtZW50YXRpb24gZmlsZXMgKHRoZSBcIlNvZnR3YXJlXCIpLCB0byBkZWFsIGluXG4vLyB0aGUgU29mdHdhcmUgd2l0aG91dCByZXN0cmljdGlvbiwgaW5jbHVkaW5nIHdpdGhvdXQgbGltaXRhdGlvbiB0aGUgcmlnaHRzIHRvXG4vLyB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsIGNvcGllcyBvZlxuLy8gdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLFxuLy8gICAgIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOlxuLy9cbi8vICAgICBUaGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBzaGFsbCBiZSBpbmNsdWRlZCBpbiBhbGxcbi8vIGNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuXG4vL1xuLy8gICAgIFRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1Jcbi8vIElNUExJRUQsIElOQ0xVRElORyBCVVQgTk9UIExJTUlURUQgVE8gVEhFIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZLCBGSVRORVNTXG4vLyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUlMgT1Jcbi8vIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSIExJQUJJTElUWSwgV0hFVEhFUlxuLy8gSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBUT1JUIE9SIE9USEVSV0lTRSwgQVJJU0lORyBGUk9NLCBPVVQgT0YgT1IgSU5cbi8vIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUgU09GVFdBUkUuXG5cbi8vIFRoaXMgYWxwaGFiZXQgdXNlcyBgQS1aYS16MC05Xy1gIHN5bWJvbHMuXG4vLyBUaGUgb3JkZXIgb2YgY2hhcmFjdGVycyBpcyBvcHRpbWl6ZWQgZm9yIGJldHRlciBnemlwIGFuZCBicm90bGkgY29tcHJlc3Npb24uXG4vLyBSZWZlcmVuY2VzIHRvIHRoZSBzYW1lIGZpbGUgKHdvcmtzIGJvdGggZm9yIGd6aXAgYW5kIGJyb3RsaSk6XG4vLyBgJ3VzZWAsIGBhbmRvbWAsIGFuZCBgcmljdCdgXG4vLyBSZWZlcmVuY2VzIHRvIHRoZSBicm90bGkgZGVmYXVsdCBkaWN0aW9uYXJ5OlxuLy8gYC0yNlRgLCBgMTk4M2AsIGA0MHB4YCwgYDc1cHhgLCBgYnVzaGAsIGBqYWNrYCwgYG1pbmRgLCBgdmVyeWAsIGFuZCBgd29sZmBcbmNvbnN0IHVybEFscGhhYmV0ID1cbiAgICAndXNlYW5kb20tMjZUMTk4MzQwUFg3NXB4SkFDS1ZFUllNSU5EQlVTSFdPTEZfR1FaYmZnaGprbHF2d3l6cmljdCdcblxuZXhwb3J0IGZ1bmN0aW9uIG5hbm9pZChzaXplOiBudW1iZXIgPSAyMSk6IHN0cmluZyB7XG4gICAgbGV0IGlkID0gJydcbiAgICAvLyBBIGNvbXBhY3QgYWx0ZXJuYXRpdmUgZm9yIGBmb3IgKHZhciBpID0gMDsgaSA8IHN0ZXA7IGkrKylgLlxuICAgIGxldCBpID0gc2l6ZSB8IDBcbiAgICB3aGlsZSAoaS0tKSB7XG4gICAgICAgIC8vIGB8IDBgIGlzIG1vcmUgY29tcGFjdCBhbmQgZmFzdGVyIHRoYW4gYE1hdGguZmxvb3IoKWAuXG4gICAgICAgIGlkICs9IHVybEFscGhhYmV0WyhNYXRoLnJhbmRvbSgpICogNjQpIHwgMF1cbiAgICB9XG4gICAgcmV0dXJuIGlkXG59XG4iLCAiLypcbiBfICAgICBfXyAgICAgXyBfX1xufCB8ICAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IG5hbm9pZCB9IGZyb20gXCIuL25hbm9pZC5qc1wiO1xuXG5jb25zdCBydW50aW1lVVJMID0gd2luZG93LmxvY2F0aW9uLm9yaWdpbiArIFwiL3dhaWxzL3J1bnRpbWVcIjtcblxuLy8gUmUtZXhwb3J0IG5hbm9pZCBmb3IgY3VzdG9tIHRyYW5zcG9ydCBpbXBsZW1lbnRhdGlvbnNcbmV4cG9ydCB7IG5hbm9pZCB9O1xuXG4vLyBPYmplY3QgTmFtZXNcbmV4cG9ydCBjb25zdCBvYmplY3ROYW1lcyA9IE9iamVjdC5mcmVlemUoe1xuICAgIENhbGw6IDAsXG4gICAgQ2xpcGJvYXJkOiAxLFxuICAgIEFwcGxpY2F0aW9uOiAyLFxuICAgIEV2ZW50czogMyxcbiAgICBDb250ZXh0TWVudTogNCxcbiAgICBEaWFsb2c6IDUsXG4gICAgV2luZG93OiA2LFxuICAgIFNjcmVlbnM6IDcsXG4gICAgU3lzdGVtOiA4LFxuICAgIEJyb3dzZXI6IDksXG4gICAgQ2FuY2VsQ2FsbDogMTAsXG4gICAgSU9TOiAxMSxcbn0pO1xuZXhwb3J0IGxldCBjbGllbnRJZCA9IG5hbm9pZCgpO1xuXG4vKipcbiAqIFJ1bnRpbWVUcmFuc3BvcnQgZGVmaW5lcyB0aGUgaW50ZXJmYWNlIGZvciBjdXN0b20gSVBDIHRyYW5zcG9ydCBpbXBsZW1lbnRhdGlvbnMuXG4gKiBJbXBsZW1lbnQgdGhpcyBpbnRlcmZhY2UgdG8gdXNlIFdlYlNvY2tldHMsIGN1c3RvbSBwcm90b2NvbHMsIG9yIGFueSBvdGhlclxuICogdHJhbnNwb3J0IG1lY2hhbmlzbSBpbnN0ZWFkIG9mIHRoZSBkZWZhdWx0IEhUVFAgZmV0Y2guXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUnVudGltZVRyYW5zcG9ydCB7XG4gICAgLyoqXG4gICAgICogU2VuZCBhIHJ1bnRpbWUgY2FsbCBhbmQgcmV0dXJuIHRoZSByZXNwb25zZS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBvYmplY3RJRCAtIFRoZSBXYWlscyBvYmplY3QgSUQgKDA9Q2FsbCwgMT1DbGlwYm9hcmQsIGV0Yy4pXG4gICAgICogQHBhcmFtIG1ldGhvZCAtIFRoZSBtZXRob2QgSUQgdG8gY2FsbFxuICAgICAqIEBwYXJhbSB3aW5kb3dOYW1lIC0gT3B0aW9uYWwgd2luZG93IG5hbWVcbiAgICAgKiBAcGFyYW0gYXJncyAtIEFyZ3VtZW50cyB0byBwYXNzICh3aWxsIGJlIEpTT04gc3RyaW5naWZpZWQgaWYgcHJlc2VudClcbiAgICAgKiBAcmV0dXJucyBQcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCB0aGUgcmVzcG9uc2UgZGF0YVxuICAgICAqL1xuICAgIGNhbGwob2JqZWN0SUQ6IG51bWJlciwgbWV0aG9kOiBudW1iZXIsIHdpbmRvd05hbWU6IHN0cmluZywgYXJnczogYW55KTogUHJvbWlzZTxhbnk+O1xufVxuXG4vKipcbiAqIEN1c3RvbSB0cmFuc3BvcnQgaW1wbGVtZW50YXRpb24gKGNhbiBiZSBzZXQgYnkgdXNlcilcbiAqL1xubGV0IGN1c3RvbVRyYW5zcG9ydDogUnVudGltZVRyYW5zcG9ydCB8IG51bGwgPSBudWxsO1xuXG4vKipcbiAqIFNldCBhIGN1c3RvbSB0cmFuc3BvcnQgZm9yIGFsbCBXYWlscyBydW50aW1lIGNhbGxzLlxuICogVGhpcyBhbGxvd3MgeW91IHRvIHJlcGxhY2UgdGhlIGRlZmF1bHQgSFRUUCBmZXRjaCB0cmFuc3BvcnQgd2l0aFxuICogV2ViU29ja2V0cywgY3VzdG9tIHByb3RvY29scywgb3IgYW55IG90aGVyIG1lY2hhbmlzbS5cbiAqXG4gKiBAcGFyYW0gdHJhbnNwb3J0IC0gWW91ciBjdXN0b20gdHJhbnNwb3J0IGltcGxlbWVudGF0aW9uXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGltcG9ydCB7IHNldFRyYW5zcG9ydCB9IGZyb20gJy93YWlscy9ydW50aW1lLmpzJztcbiAqXG4gKiBjb25zdCB3c1RyYW5zcG9ydCA9IHtcbiAqICAgY2FsbDogYXN5bmMgKG9iamVjdElELCBtZXRob2QsIHdpbmRvd05hbWUsIGFyZ3MpID0+IHtcbiAqICAgICAvLyBZb3VyIFdlYlNvY2tldCBpbXBsZW1lbnRhdGlvblxuICogICB9XG4gKiB9O1xuICpcbiAqIHNldFRyYW5zcG9ydCh3c1RyYW5zcG9ydCk7XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNldFRyYW5zcG9ydCh0cmFuc3BvcnQ6IFJ1bnRpbWVUcmFuc3BvcnQgfCBudWxsKTogdm9pZCB7XG4gICAgY3VzdG9tVHJhbnNwb3J0ID0gdHJhbnNwb3J0O1xufVxuXG4vKipcbiAqIEdldCB0aGUgY3VycmVudCB0cmFuc3BvcnQgKHVzZWZ1bCBmb3IgZXh0ZW5kaW5nL3dyYXBwaW5nKVxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0VHJhbnNwb3J0KCk6IFJ1bnRpbWVUcmFuc3BvcnQgfCBudWxsIHtcbiAgICByZXR1cm4gY3VzdG9tVHJhbnNwb3J0O1xufVxuXG4vKipcbiAqIENyZWF0ZXMgYSBuZXcgcnVudGltZSBjYWxsZXIgd2l0aCBzcGVjaWZpZWQgSUQuXG4gKlxuICogQHBhcmFtIG9iamVjdCAtIFRoZSBvYmplY3QgdG8gaW52b2tlIHRoZSBtZXRob2Qgb24uXG4gKiBAcGFyYW0gd2luZG93TmFtZSAtIFRoZSBuYW1lIG9mIHRoZSB3aW5kb3cuXG4gKiBAcmV0dXJuIFRoZSBuZXcgcnVudGltZSBjYWxsZXIgZnVuY3Rpb24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdDogbnVtYmVyLCB3aW5kb3dOYW1lOiBzdHJpbmcgPSAnJykge1xuICAgIHJldHVybiBmdW5jdGlvbiAobWV0aG9kOiBudW1iZXIsIGFyZ3M6IGFueSA9IG51bGwpIHtcbiAgICAgICAgcmV0dXJuIHJ1bnRpbWVDYWxsV2l0aElEKG9iamVjdCwgbWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKTtcbiAgICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBydW50aW1lQ2FsbFdpdGhJRChvYmplY3RJRDogbnVtYmVyLCBtZXRob2Q6IG51bWJlciwgd2luZG93TmFtZTogc3RyaW5nLCBhcmdzOiBhbnkpOiBQcm9taXNlPGFueT4ge1xuICAgIC8vIFVzZSBjdXN0b20gdHJhbnNwb3J0IGlmIGF2YWlsYWJsZVxuICAgIGlmIChjdXN0b21UcmFuc3BvcnQpIHtcbiAgICAgICAgcmV0dXJuIGN1c3RvbVRyYW5zcG9ydC5jYWxsKG9iamVjdElELCBtZXRob2QsIHdpbmRvd05hbWUsIGFyZ3MpO1xuICAgIH1cblxuICAgIC8vIERlZmF1bHQgSFRUUCBmZXRjaCB0cmFuc3BvcnRcbiAgICBsZXQgdXJsID0gbmV3IFVSTChydW50aW1lVVJMKTtcblxuICAgIGxldCBib2R5OiB7IG9iamVjdDogbnVtYmVyOyBtZXRob2Q6IG51bWJlciwgYXJncz86IGFueSB9ID0ge1xuICAgICAgb2JqZWN0OiBvYmplY3RJRCxcbiAgICAgIG1ldGhvZFxuICAgIH1cbiAgICBpZiAoYXJncyAhPT0gbnVsbCAmJiBhcmdzICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIGJvZHkuYXJncyA9IGFyZ3M7XG4gICAgfVxuXG4gICAgbGV0IGhlYWRlcnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7XG4gICAgICAgIFtcIngtd2FpbHMtY2xpZW50LWlkXCJdOiBjbGllbnRJZCxcbiAgICAgICAgW1wiQ29udGVudC1UeXBlXCJdOiBcImFwcGxpY2F0aW9uL2pzb25cIlxuICAgIH1cbiAgICBpZiAod2luZG93TmFtZSkge1xuICAgICAgICBoZWFkZXJzW1wieC13YWlscy13aW5kb3ctbmFtZVwiXSA9IHdpbmRvd05hbWU7XG4gICAgfVxuXG4gICAgbGV0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godXJsLCB7XG4gICAgICBtZXRob2Q6ICdQT1NUJyxcbiAgICAgIGhlYWRlcnMsXG4gICAgICBib2R5OiBKU09OLnN0cmluZ2lmeShib2R5KVxuICAgIH0pO1xuICAgIGlmICghcmVzcG9uc2Uub2spIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGF3YWl0IHJlc3BvbnNlLnRleHQoKSk7XG4gICAgfVxuXG4gICAgaWYgKChyZXNwb25zZS5oZWFkZXJzLmdldChcIkNvbnRlbnQtVHlwZVwiKT8uaW5kZXhPZihcImFwcGxpY2F0aW9uL2pzb25cIikgPz8gLTEpICE9PSAtMSkge1xuICAgICAgICByZXR1cm4gcmVzcG9uc2UuanNvbigpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiByZXNwb25zZS50ZXh0KCk7XG4gICAgfVxufVxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5pbXBvcnQge25ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzfSBmcm9tIFwiLi9ydW50aW1lLmpzXCI7XG5cbi8vIHNldHVwXG53aW5kb3cuX3dhaWxzID0gd2luZG93Ll93YWlscyB8fCB7fTtcblxuY29uc3QgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0TmFtZXMuRGlhbG9nKTtcblxuLy8gRGVmaW5lIGNvbnN0YW50cyBmcm9tIHRoZSBgbWV0aG9kc2Agb2JqZWN0IGluIFRpdGxlIENhc2VcbmNvbnN0IERpYWxvZ0luZm8gPSAwO1xuY29uc3QgRGlhbG9nV2FybmluZyA9IDE7XG5jb25zdCBEaWFsb2dFcnJvciA9IDI7XG5jb25zdCBEaWFsb2dRdWVzdGlvbiA9IDM7XG5jb25zdCBEaWFsb2dPcGVuRmlsZSA9IDQ7XG5jb25zdCBEaWFsb2dTYXZlRmlsZSA9IDU7XG5cbmV4cG9ydCBpbnRlcmZhY2UgT3BlbkZpbGVEaWFsb2dPcHRpb25zIHtcbiAgICAvKiogSW5kaWNhdGVzIGlmIGRpcmVjdG9yaWVzIGNhbiBiZSBjaG9zZW4uICovXG4gICAgQ2FuQ2hvb3NlRGlyZWN0b3JpZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgZmlsZXMgY2FuIGJlIGNob3Nlbi4gKi9cbiAgICBDYW5DaG9vc2VGaWxlcz86IGJvb2xlYW47XG4gICAgLyoqIEluZGljYXRlcyBpZiBkaXJlY3RvcmllcyBjYW4gYmUgY3JlYXRlZC4gKi9cbiAgICBDYW5DcmVhdGVEaXJlY3Rvcmllcz86IGJvb2xlYW47XG4gICAgLyoqIEluZGljYXRlcyBpZiBoaWRkZW4gZmlsZXMgc2hvdWxkIGJlIHNob3duLiAqL1xuICAgIFNob3dIaWRkZW5GaWxlcz86IGJvb2xlYW47XG4gICAgLyoqIEluZGljYXRlcyBpZiBhbGlhc2VzIHNob3VsZCBiZSByZXNvbHZlZC4gKi9cbiAgICBSZXNvbHZlc0FsaWFzZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgbXVsdGlwbGUgc2VsZWN0aW9uIGlzIGFsbG93ZWQuICovXG4gICAgQWxsb3dzTXVsdGlwbGVTZWxlY3Rpb24/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgdGhlIGV4dGVuc2lvbiBzaG91bGQgYmUgaGlkZGVuLiAqL1xuICAgIEhpZGVFeHRlbnNpb24/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgaGlkZGVuIGV4dGVuc2lvbnMgY2FuIGJlIHNlbGVjdGVkLiAqL1xuICAgIENhblNlbGVjdEhpZGRlbkV4dGVuc2lvbj86IGJvb2xlYW47XG4gICAgLyoqIEluZGljYXRlcyBpZiBmaWxlIHBhY2thZ2VzIHNob3VsZCBiZSB0cmVhdGVkIGFzIGRpcmVjdG9yaWVzLiAqL1xuICAgIFRyZWF0c0ZpbGVQYWNrYWdlc0FzRGlyZWN0b3JpZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgb3RoZXIgZmlsZSB0eXBlcyBhcmUgYWxsb3dlZC4gKi9cbiAgICBBbGxvd3NPdGhlckZpbGV0eXBlcz86IGJvb2xlYW47XG4gICAgLyoqIEFycmF5IG9mIGZpbGUgZmlsdGVycy4gKi9cbiAgICBGaWx0ZXJzPzogRmlsZUZpbHRlcltdO1xuICAgIC8qKiBUaXRsZSBvZiB0aGUgZGlhbG9nLiAqL1xuICAgIFRpdGxlPzogc3RyaW5nO1xuICAgIC8qKiBNZXNzYWdlIHRvIHNob3cgaW4gdGhlIGRpYWxvZy4gKi9cbiAgICBNZXNzYWdlPzogc3RyaW5nO1xuICAgIC8qKiBUZXh0IHRvIGRpc3BsYXkgb24gdGhlIGJ1dHRvbi4gKi9cbiAgICBCdXR0b25UZXh0Pzogc3RyaW5nO1xuICAgIC8qKiBEaXJlY3RvcnkgdG8gb3BlbiBpbiB0aGUgZGlhbG9nLiAqL1xuICAgIERpcmVjdG9yeT86IHN0cmluZztcbiAgICAvKiogSW5kaWNhdGVzIGlmIHRoZSBkaWFsb2cgc2hvdWxkIGFwcGVhciBkZXRhY2hlZCBmcm9tIHRoZSBtYWluIHdpbmRvdy4gKi9cbiAgICBEZXRhY2hlZD86IGJvb2xlYW47XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgU2F2ZUZpbGVEaWFsb2dPcHRpb25zIHtcbiAgICAvKiogRGVmYXVsdCBmaWxlbmFtZSB0byB1c2UgaW4gdGhlIGRpYWxvZy4gKi9cbiAgICBGaWxlbmFtZT86IHN0cmluZztcbiAgICAvKiogSW5kaWNhdGVzIGlmIGRpcmVjdG9yaWVzIGNhbiBiZSBjaG9zZW4uICovXG4gICAgQ2FuQ2hvb3NlRGlyZWN0b3JpZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgZmlsZXMgY2FuIGJlIGNob3Nlbi4gKi9cbiAgICBDYW5DaG9vc2VGaWxlcz86IGJvb2xlYW47XG4gICAgLyoqIEluZGljYXRlcyBpZiBkaXJlY3RvcmllcyBjYW4gYmUgY3JlYXRlZC4gKi9cbiAgICBDYW5DcmVhdGVEaXJlY3Rvcmllcz86IGJvb2xlYW47XG4gICAgLyoqIEluZGljYXRlcyBpZiBoaWRkZW4gZmlsZXMgc2hvdWxkIGJlIHNob3duLiAqL1xuICAgIFNob3dIaWRkZW5GaWxlcz86IGJvb2xlYW47XG4gICAgLyoqIEluZGljYXRlcyBpZiBhbGlhc2VzIHNob3VsZCBiZSByZXNvbHZlZC4gKi9cbiAgICBSZXNvbHZlc0FsaWFzZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgdGhlIGV4dGVuc2lvbiBzaG91bGQgYmUgaGlkZGVuLiAqL1xuICAgIEhpZGVFeHRlbnNpb24/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgaGlkZGVuIGV4dGVuc2lvbnMgY2FuIGJlIHNlbGVjdGVkLiAqL1xuICAgIENhblNlbGVjdEhpZGRlbkV4dGVuc2lvbj86IGJvb2xlYW47XG4gICAgLyoqIEluZGljYXRlcyBpZiBmaWxlIHBhY2thZ2VzIHNob3VsZCBiZSB0cmVhdGVkIGFzIGRpcmVjdG9yaWVzLiAqL1xuICAgIFRyZWF0c0ZpbGVQYWNrYWdlc0FzRGlyZWN0b3JpZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgb3RoZXIgZmlsZSB0eXBlcyBhcmUgYWxsb3dlZC4gKi9cbiAgICBBbGxvd3NPdGhlckZpbGV0eXBlcz86IGJvb2xlYW47XG4gICAgLyoqIEFycmF5IG9mIGZpbGUgZmlsdGVycy4gKi9cbiAgICBGaWx0ZXJzPzogRmlsZUZpbHRlcltdO1xuICAgIC8qKiBUaXRsZSBvZiB0aGUgZGlhbG9nLiAqL1xuICAgIFRpdGxlPzogc3RyaW5nO1xuICAgIC8qKiBNZXNzYWdlIHRvIHNob3cgaW4gdGhlIGRpYWxvZy4gKi9cbiAgICBNZXNzYWdlPzogc3RyaW5nO1xuICAgIC8qKiBUZXh0IHRvIGRpc3BsYXkgb24gdGhlIGJ1dHRvbi4gKi9cbiAgICBCdXR0b25UZXh0Pzogc3RyaW5nO1xuICAgIC8qKiBEaXJlY3RvcnkgdG8gb3BlbiBpbiB0aGUgZGlhbG9nLiAqL1xuICAgIERpcmVjdG9yeT86IHN0cmluZztcbiAgICAvKiogSW5kaWNhdGVzIGlmIHRoZSBkaWFsb2cgc2hvdWxkIGFwcGVhciBkZXRhY2hlZCBmcm9tIHRoZSBtYWluIHdpbmRvdy4gKi9cbiAgICBEZXRhY2hlZD86IGJvb2xlYW47XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgTWVzc2FnZURpYWxvZ09wdGlvbnMge1xuICAgIC8qKiBUaGUgdGl0bGUgb2YgdGhlIGRpYWxvZyB3aW5kb3cuICovXG4gICAgVGl0bGU/OiBzdHJpbmc7XG4gICAgLyoqIFRoZSBtYWluIG1lc3NhZ2UgdG8gc2hvdyBpbiB0aGUgZGlhbG9nLiAqL1xuICAgIE1lc3NhZ2U/OiBzdHJpbmc7XG4gICAgLyoqIEFycmF5IG9mIGJ1dHRvbiBvcHRpb25zIHRvIHNob3cgaW4gdGhlIGRpYWxvZy4gKi9cbiAgICBCdXR0b25zPzogQnV0dG9uW107XG4gICAgLyoqIFRydWUgaWYgdGhlIGRpYWxvZyBzaG91bGQgYXBwZWFyIGRldGFjaGVkIGZyb20gdGhlIG1haW4gd2luZG93IChpZiBhcHBsaWNhYmxlKS4gKi9cbiAgICBEZXRhY2hlZD86IGJvb2xlYW47XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgQnV0dG9uIHtcbiAgICAvKiogVGV4dCB0aGF0IGFwcGVhcnMgd2l0aGluIHRoZSBidXR0b24uICovXG4gICAgTGFiZWw/OiBzdHJpbmc7XG4gICAgLyoqIFRydWUgaWYgdGhlIGJ1dHRvbiBzaG91bGQgY2FuY2VsIGFuIG9wZXJhdGlvbiB3aGVuIGNsaWNrZWQuICovXG4gICAgSXNDYW5jZWw/OiBib29sZWFuO1xuICAgIC8qKiBUcnVlIGlmIHRoZSBidXR0b24gc2hvdWxkIGJlIHRoZSBkZWZhdWx0IGFjdGlvbiB3aGVuIHRoZSB1c2VyIHByZXNzZXMgZW50ZXIuICovXG4gICAgSXNEZWZhdWx0PzogYm9vbGVhbjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBGaWxlRmlsdGVyIHtcbiAgICAvKiogRGlzcGxheSBuYW1lIGZvciB0aGUgZmlsdGVyLCBpdCBjb3VsZCBiZSBcIlRleHQgRmlsZXNcIiwgXCJJbWFnZXNcIiBldGMuICovXG4gICAgRGlzcGxheU5hbWU/OiBzdHJpbmc7XG4gICAgLyoqIFBhdHRlcm4gdG8gbWF0Y2ggZm9yIHRoZSBmaWx0ZXIsIGUuZy4gXCIqLnR4dDsqLm1kXCIgZm9yIHRleHQgbWFya2Rvd24gZmlsZXMuICovXG4gICAgUGF0dGVybj86IHN0cmluZztcbn1cblxuLyoqXG4gKiBQcmVzZW50cyBhIGRpYWxvZyBvZiBzcGVjaWZpZWQgdHlwZSB3aXRoIHRoZSBnaXZlbiBvcHRpb25zLlxuICpcbiAqIEBwYXJhbSB0eXBlIC0gRGlhbG9nIHR5cGUuXG4gKiBAcGFyYW0gb3B0aW9ucyAtIE9wdGlvbnMgZm9yIHRoZSBkaWFsb2cuXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aXRoIHJlc3VsdCBvZiBkaWFsb2cuXG4gKi9cbmZ1bmN0aW9uIGRpYWxvZyh0eXBlOiBudW1iZXIsIG9wdGlvbnM6IE1lc3NhZ2VEaWFsb2dPcHRpb25zIHwgT3BlbkZpbGVEaWFsb2dPcHRpb25zIHwgU2F2ZUZpbGVEaWFsb2dPcHRpb25zID0ge30pOiBQcm9taXNlPGFueT4ge1xuICAgIHJldHVybiBjYWxsKHR5cGUsIG9wdGlvbnMpO1xufVxuXG4vKipcbiAqIFByZXNlbnRzIGFuIGluZm8gZGlhbG9nLlxuICpcbiAqIEBwYXJhbSBvcHRpb25zIC0gRGlhbG9nIG9wdGlvbnNcbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdpdGggdGhlIGxhYmVsIG9mIHRoZSBjaG9zZW4gYnV0dG9uLlxuICovXG5leHBvcnQgZnVuY3Rpb24gSW5mbyhvcHRpb25zOiBNZXNzYWdlRGlhbG9nT3B0aW9ucyk6IFByb21pc2U8c3RyaW5nPiB7IHJldHVybiBkaWFsb2coRGlhbG9nSW5mbywgb3B0aW9ucyk7IH1cblxuLyoqXG4gKiBQcmVzZW50cyBhIHdhcm5pbmcgZGlhbG9nLlxuICpcbiAqIEBwYXJhbSBvcHRpb25zIC0gRGlhbG9nIG9wdGlvbnMuXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aXRoIHRoZSBsYWJlbCBvZiB0aGUgY2hvc2VuIGJ1dHRvbi5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdhcm5pbmcob3B0aW9uczogTWVzc2FnZURpYWxvZ09wdGlvbnMpOiBQcm9taXNlPHN0cmluZz4geyByZXR1cm4gZGlhbG9nKERpYWxvZ1dhcm5pbmcsIG9wdGlvbnMpOyB9XG5cbi8qKlxuICogUHJlc2VudHMgYW4gZXJyb3IgZGlhbG9nLlxuICpcbiAqIEBwYXJhbSBvcHRpb25zIC0gRGlhbG9nIG9wdGlvbnMuXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aXRoIHRoZSBsYWJlbCBvZiB0aGUgY2hvc2VuIGJ1dHRvbi5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEVycm9yKG9wdGlvbnM6IE1lc3NhZ2VEaWFsb2dPcHRpb25zKTogUHJvbWlzZTxzdHJpbmc+IHsgcmV0dXJuIGRpYWxvZyhEaWFsb2dFcnJvciwgb3B0aW9ucyk7IH1cblxuLyoqXG4gKiBQcmVzZW50cyBhIHF1ZXN0aW9uIGRpYWxvZy5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyAtIERpYWxvZyBvcHRpb25zLlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCB0aGUgbGFiZWwgb2YgdGhlIGNob3NlbiBidXR0b24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBRdWVzdGlvbihvcHRpb25zOiBNZXNzYWdlRGlhbG9nT3B0aW9ucyk6IFByb21pc2U8c3RyaW5nPiB7IHJldHVybiBkaWFsb2coRGlhbG9nUXVlc3Rpb24sIG9wdGlvbnMpOyB9XG5cbi8qKlxuICogUHJlc2VudHMgYSBmaWxlIHNlbGVjdGlvbiBkaWFsb2cgdG8gcGljayBvbmUgb3IgbW9yZSBmaWxlcyB0byBvcGVuLlxuICpcbiAqIEBwYXJhbSBvcHRpb25zIC0gRGlhbG9nIG9wdGlvbnMuXG4gKiBAcmV0dXJucyBTZWxlY3RlZCBmaWxlIG9yIGxpc3Qgb2YgZmlsZXMsIG9yIGEgYmxhbmsgc3RyaW5nL2VtcHR5IGxpc3QgaWYgbm8gZmlsZSBoYXMgYmVlbiBzZWxlY3RlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9wZW5GaWxlKG9wdGlvbnM6IE9wZW5GaWxlRGlhbG9nT3B0aW9ucyAmIHsgQWxsb3dzTXVsdGlwbGVTZWxlY3Rpb246IHRydWUgfSk6IFByb21pc2U8c3RyaW5nW10+O1xuZXhwb3J0IGZ1bmN0aW9uIE9wZW5GaWxlKG9wdGlvbnM6IE9wZW5GaWxlRGlhbG9nT3B0aW9ucyAmIHsgQWxsb3dzTXVsdGlwbGVTZWxlY3Rpb24/OiBmYWxzZSB8IHVuZGVmaW5lZCB9KTogUHJvbWlzZTxzdHJpbmc+O1xuZXhwb3J0IGZ1bmN0aW9uIE9wZW5GaWxlKG9wdGlvbnM6IE9wZW5GaWxlRGlhbG9nT3B0aW9ucyk6IFByb21pc2U8c3RyaW5nIHwgc3RyaW5nW10+O1xuZXhwb3J0IGZ1bmN0aW9uIE9wZW5GaWxlKG9wdGlvbnM6IE9wZW5GaWxlRGlhbG9nT3B0aW9ucyk6IFByb21pc2U8c3RyaW5nIHwgc3RyaW5nW10+IHsgcmV0dXJuIGRpYWxvZyhEaWFsb2dPcGVuRmlsZSwgb3B0aW9ucykgPz8gW107IH1cblxuLyoqXG4gKiBQcmVzZW50cyBhIGZpbGUgc2VsZWN0aW9uIGRpYWxvZyB0byBwaWNrIGEgZmlsZSB0byBzYXZlLlxuICpcbiAqIEBwYXJhbSBvcHRpb25zIC0gRGlhbG9nIG9wdGlvbnMuXG4gKiBAcmV0dXJucyBTZWxlY3RlZCBmaWxlLCBvciBhIGJsYW5rIHN0cmluZyBpZiBubyBmaWxlIGhhcyBiZWVuIHNlbGVjdGVkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gU2F2ZUZpbGUob3B0aW9uczogU2F2ZUZpbGVEaWFsb2dPcHRpb25zKTogUHJvbWlzZTxzdHJpbmc+IHsgcmV0dXJuIGRpYWxvZyhEaWFsb2dTYXZlRmlsZSwgb3B0aW9ucyk7IH1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHsgbmV3UnVudGltZUNhbGxlciwgb2JqZWN0TmFtZXMgfSBmcm9tIFwiLi9ydW50aW1lLmpzXCI7XG5pbXBvcnQgeyBldmVudExpc3RlbmVycywgTGlzdGVuZXIsIGxpc3RlbmVyT2ZmIH0gZnJvbSBcIi4vbGlzdGVuZXIuanNcIjtcbmltcG9ydCB7IEV2ZW50cyBhcyBDcmVhdGUgfSBmcm9tIFwiLi9jcmVhdGUuanNcIjtcbmltcG9ydCB7IFR5cGVzIH0gZnJvbSBcIi4vZXZlbnRfdHlwZXMuanNcIjtcblxuLy8gU2V0dXBcbndpbmRvdy5fd2FpbHMgPSB3aW5kb3cuX3dhaWxzIHx8IHt9O1xud2luZG93Ll93YWlscy5kaXNwYXRjaFdhaWxzRXZlbnQgPSBkaXNwYXRjaFdhaWxzRXZlbnQ7XG5cbmNvbnN0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdE5hbWVzLkV2ZW50cyk7XG5jb25zdCBFbWl0TWV0aG9kID0gMDtcblxuZXhwb3J0ICogZnJvbSBcIi4vZXZlbnRfdHlwZXMuanNcIjtcblxuLyoqXG4gKiBBIHRhYmxlIG9mIGRhdGEgdHlwZXMgZm9yIGFsbCBrbm93biBldmVudHMuXG4gKiBXaWxsIGJlIG1vbmtleS1wYXRjaGVkIGJ5IHRoZSBiaW5kaW5nIGdlbmVyYXRvci5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBDdXN0b21FdmVudHMge31cblxuLyoqXG4gKiBFaXRoZXIgYSBrbm93biBldmVudCBuYW1lIG9yIGFuIGFyYml0cmFyeSBzdHJpbmcuXG4gKi9cbmV4cG9ydCB0eXBlIFdhaWxzRXZlbnROYW1lPEUgZXh0ZW5kcyBrZXlvZiBDdXN0b21FdmVudHMgPSBrZXlvZiBDdXN0b21FdmVudHM+ID0gRSB8IChzdHJpbmcgJiB7fSk7XG5cbi8qKlxuICogVW5pb24gb2YgYWxsIGtub3duIHN5c3RlbSBldmVudCBuYW1lcy5cbiAqL1xudHlwZSBTeXN0ZW1FdmVudE5hbWUgPSB7XG4gICAgW0sgaW4ga2V5b2YgKHR5cGVvZiBUeXBlcyldOiAodHlwZW9mIFR5cGVzKVtLXVtrZXlvZiAoKHR5cGVvZiBUeXBlcylbS10pXVxufSBleHRlbmRzIChpbmZlciBNKSA/IE1ba2V5b2YgTV0gOiBuZXZlcjtcblxuLyoqXG4gKiBUaGUgZGF0YSB0eXBlIGFzc29jaWF0ZWQgdG8gYSBnaXZlbiBldmVudC5cbiAqL1xuZXhwb3J0IHR5cGUgV2FpbHNFdmVudERhdGE8RSBleHRlbmRzIFdhaWxzRXZlbnROYW1lID0gV2FpbHNFdmVudE5hbWU+ID1cbiAgICBFIGV4dGVuZHMga2V5b2YgQ3VzdG9tRXZlbnRzID8gQ3VzdG9tRXZlbnRzW0VdIDogKEUgZXh0ZW5kcyBTeXN0ZW1FdmVudE5hbWUgPyB2b2lkIDogYW55KTtcblxuLyoqXG4gKiBUaGUgdHlwZSBvZiBoYW5kbGVycyBmb3IgYSBnaXZlbiBldmVudC5cbiAqL1xuZXhwb3J0IHR5cGUgV2FpbHNFdmVudENhbGxiYWNrPEUgZXh0ZW5kcyBXYWlsc0V2ZW50TmFtZSA9IFdhaWxzRXZlbnROYW1lPiA9IChldjogV2FpbHNFdmVudDxFPikgPT4gdm9pZDtcblxuLyoqXG4gKiBSZXByZXNlbnRzIGEgc3lzdGVtIGV2ZW50IG9yIGEgY3VzdG9tIGV2ZW50IGVtaXR0ZWQgdGhyb3VnaCB3YWlscy1wcm92aWRlZCBmYWNpbGl0aWVzLlxuICovXG5leHBvcnQgY2xhc3MgV2FpbHNFdmVudDxFIGV4dGVuZHMgV2FpbHNFdmVudE5hbWUgPSBXYWlsc0V2ZW50TmFtZT4ge1xuICAgIC8qKlxuICAgICAqIFRoZSBuYW1lIG9mIHRoZSBldmVudC5cbiAgICAgKi9cbiAgICBuYW1lOiBFO1xuXG4gICAgLyoqXG4gICAgICogT3B0aW9uYWwgZGF0YSBhc3NvY2lhdGVkIHdpdGggdGhlIGVtaXR0ZWQgZXZlbnQuXG4gICAgICovXG4gICAgZGF0YTogV2FpbHNFdmVudERhdGE8RT47XG5cbiAgICAvKipcbiAgICAgKiBOYW1lIG9mIHRoZSBvcmlnaW5hdGluZyB3aW5kb3cuIE9taXR0ZWQgZm9yIGFwcGxpY2F0aW9uIGV2ZW50cy5cbiAgICAgKiBXaWxsIGJlIG92ZXJyaWRkZW4gaWYgc2V0IG1hbnVhbGx5LlxuICAgICAqL1xuICAgIHNlbmRlcj86IHN0cmluZztcblxuICAgIGNvbnN0cnVjdG9yKG5hbWU6IEUsIGRhdGE6IFdhaWxzRXZlbnREYXRhPEU+KTtcbiAgICBjb25zdHJ1Y3RvcihuYW1lOiBXYWlsc0V2ZW50RGF0YTxFPiBleHRlbmRzIG51bGwgfCB2b2lkID8gRSA6IG5ldmVyKVxuICAgIGNvbnN0cnVjdG9yKG5hbWU6IEUsIGRhdGE/OiBhbnkpIHtcbiAgICAgICAgdGhpcy5uYW1lID0gbmFtZTtcbiAgICAgICAgdGhpcy5kYXRhID0gZGF0YSA/PyBudWxsO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gZGlzcGF0Y2hXYWlsc0V2ZW50KGV2ZW50OiBhbnkpIHtcbiAgICBsZXQgbGlzdGVuZXJzID0gZXZlbnRMaXN0ZW5lcnMuZ2V0KGV2ZW50Lm5hbWUpO1xuICAgIGlmICghbGlzdGVuZXJzKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBsZXQgd2FpbHNFdmVudCA9IG5ldyBXYWlsc0V2ZW50KFxuICAgICAgICBldmVudC5uYW1lLFxuICAgICAgICAoZXZlbnQubmFtZSBpbiBDcmVhdGUpID8gQ3JlYXRlW2V2ZW50Lm5hbWVdKGV2ZW50LmRhdGEpIDogZXZlbnQuZGF0YVxuICAgICk7XG4gICAgaWYgKCdzZW5kZXInIGluIGV2ZW50KSB7XG4gICAgICAgIHdhaWxzRXZlbnQuc2VuZGVyID0gZXZlbnQuc2VuZGVyO1xuICAgIH1cblxuICAgIGxpc3RlbmVycyA9IGxpc3RlbmVycy5maWx0ZXIobGlzdGVuZXIgPT4gIWxpc3RlbmVyLmRpc3BhdGNoKHdhaWxzRXZlbnQpKTtcbiAgICBpZiAobGlzdGVuZXJzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICBldmVudExpc3RlbmVycy5kZWxldGUoZXZlbnQubmFtZSk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgZXZlbnRMaXN0ZW5lcnMuc2V0KGV2ZW50Lm5hbWUsIGxpc3RlbmVycyk7XG4gICAgfVxufVxuXG4vKipcbiAqIFJlZ2lzdGVyIGEgY2FsbGJhY2sgZnVuY3Rpb24gdG8gYmUgY2FsbGVkIG11bHRpcGxlIHRpbWVzIGZvciBhIHNwZWNpZmljIGV2ZW50LlxuICpcbiAqIEBwYXJhbSBldmVudE5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgZXZlbnQgdG8gcmVnaXN0ZXIgdGhlIGNhbGxiYWNrIGZvci5cbiAqIEBwYXJhbSBjYWxsYmFjayAtIFRoZSBjYWxsYmFjayBmdW5jdGlvbiB0byBiZSBjYWxsZWQgd2hlbiB0aGUgZXZlbnQgaXMgdHJpZ2dlcmVkLlxuICogQHBhcmFtIG1heENhbGxiYWNrcyAtIFRoZSBtYXhpbXVtIG51bWJlciBvZiB0aW1lcyB0aGUgY2FsbGJhY2sgY2FuIGJlIGNhbGxlZCBmb3IgdGhlIGV2ZW50LiBPbmNlIHRoZSBtYXhpbXVtIG51bWJlciBpcyByZWFjaGVkLCB0aGUgY2FsbGJhY2sgd2lsbCBubyBsb25nZXIgYmUgY2FsbGVkLlxuICogQHJldHVybnMgQSBmdW5jdGlvbiB0aGF0LCB3aGVuIGNhbGxlZCwgd2lsbCB1bnJlZ2lzdGVyIHRoZSBjYWxsYmFjayBmcm9tIHRoZSBldmVudC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9uTXVsdGlwbGU8RSBleHRlbmRzIFdhaWxzRXZlbnROYW1lID0gV2FpbHNFdmVudE5hbWU+KGV2ZW50TmFtZTogRSwgY2FsbGJhY2s6IFdhaWxzRXZlbnRDYWxsYmFjazxFPiwgbWF4Q2FsbGJhY2tzOiBudW1iZXIpIHtcbiAgICBsZXQgbGlzdGVuZXJzID0gZXZlbnRMaXN0ZW5lcnMuZ2V0KGV2ZW50TmFtZSkgfHwgW107XG4gICAgY29uc3QgdGhpc0xpc3RlbmVyID0gbmV3IExpc3RlbmVyKGV2ZW50TmFtZSwgY2FsbGJhY2ssIG1heENhbGxiYWNrcyk7XG4gICAgbGlzdGVuZXJzLnB1c2godGhpc0xpc3RlbmVyKTtcbiAgICBldmVudExpc3RlbmVycy5zZXQoZXZlbnROYW1lLCBsaXN0ZW5lcnMpO1xuICAgIHJldHVybiAoKSA9PiBsaXN0ZW5lck9mZih0aGlzTGlzdGVuZXIpO1xufVxuXG4vKipcbiAqIFJlZ2lzdGVycyBhIGNhbGxiYWNrIGZ1bmN0aW9uIHRvIGJlIGV4ZWN1dGVkIHdoZW4gdGhlIHNwZWNpZmllZCBldmVudCBvY2N1cnMuXG4gKlxuICogQHBhcmFtIGV2ZW50TmFtZSAtIFRoZSBuYW1lIG9mIHRoZSBldmVudCB0byByZWdpc3RlciB0aGUgY2FsbGJhY2sgZm9yLlxuICogQHBhcmFtIGNhbGxiYWNrIC0gVGhlIGNhbGxiYWNrIGZ1bmN0aW9uIHRvIGJlIGNhbGxlZCB3aGVuIHRoZSBldmVudCBpcyB0cmlnZ2VyZWQuXG4gKiBAcmV0dXJucyBBIGZ1bmN0aW9uIHRoYXQsIHdoZW4gY2FsbGVkLCB3aWxsIHVucmVnaXN0ZXIgdGhlIGNhbGxiYWNrIGZyb20gdGhlIGV2ZW50LlxuICovXG5leHBvcnQgZnVuY3Rpb24gT248RSBleHRlbmRzIFdhaWxzRXZlbnROYW1lID0gV2FpbHNFdmVudE5hbWU+KGV2ZW50TmFtZTogRSwgY2FsbGJhY2s6IFdhaWxzRXZlbnRDYWxsYmFjazxFPik6ICgpID0+IHZvaWQge1xuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIC0xKTtcbn1cblxuLyoqXG4gKiBSZWdpc3RlcnMgYSBjYWxsYmFjayBmdW5jdGlvbiB0byBiZSBleGVjdXRlZCBvbmx5IG9uY2UgZm9yIHRoZSBzcGVjaWZpZWQgZXZlbnQuXG4gKlxuICogQHBhcmFtIGV2ZW50TmFtZSAtIFRoZSBuYW1lIG9mIHRoZSBldmVudCB0byByZWdpc3RlciB0aGUgY2FsbGJhY2sgZm9yLlxuICogQHBhcmFtIGNhbGxiYWNrIC0gVGhlIGNhbGxiYWNrIGZ1bmN0aW9uIHRvIGJlIGNhbGxlZCB3aGVuIHRoZSBldmVudCBpcyB0cmlnZ2VyZWQuXG4gKiBAcmV0dXJucyBBIGZ1bmN0aW9uIHRoYXQsIHdoZW4gY2FsbGVkLCB3aWxsIHVucmVnaXN0ZXIgdGhlIGNhbGxiYWNrIGZyb20gdGhlIGV2ZW50LlxuICovXG5leHBvcnQgZnVuY3Rpb24gT25jZTxFIGV4dGVuZHMgV2FpbHNFdmVudE5hbWUgPSBXYWlsc0V2ZW50TmFtZT4oZXZlbnROYW1lOiBFLCBjYWxsYmFjazogV2FpbHNFdmVudENhbGxiYWNrPEU+KTogKCkgPT4gdm9pZCB7XG4gICAgcmV0dXJuIE9uTXVsdGlwbGUoZXZlbnROYW1lLCBjYWxsYmFjaywgMSk7XG59XG5cbi8qKlxuICogUmVtb3ZlcyBldmVudCBsaXN0ZW5lcnMgZm9yIHRoZSBzcGVjaWZpZWQgZXZlbnQgbmFtZXMuXG4gKlxuICogQHBhcmFtIGV2ZW50TmFtZXMgLSBUaGUgbmFtZSBvZiB0aGUgZXZlbnRzIHRvIHJlbW92ZSBsaXN0ZW5lcnMgZm9yLlxuICovXG5leHBvcnQgZnVuY3Rpb24gT2ZmKC4uLmV2ZW50TmFtZXM6IFtXYWlsc0V2ZW50TmFtZSwgLi4uV2FpbHNFdmVudE5hbWVbXV0pOiB2b2lkIHtcbiAgICBldmVudE5hbWVzLmZvckVhY2goZXZlbnROYW1lID0+IGV2ZW50TGlzdGVuZXJzLmRlbGV0ZShldmVudE5hbWUpKTtcbn1cblxuLyoqXG4gKiBSZW1vdmVzIGFsbCBldmVudCBsaXN0ZW5lcnMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPZmZBbGwoKTogdm9pZCB7XG4gICAgZXZlbnRMaXN0ZW5lcnMuY2xlYXIoKTtcbn1cblxuLyoqXG4gKiBFbWl0cyBhbiBldmVudC5cbiAqXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCB3aWxsIGJlIGZ1bGZpbGxlZCBvbmNlIHRoZSBldmVudCBoYXMgYmVlbiBlbWl0dGVkLiAgUmVzb2x2ZXMgdG8gdHJ1ZSBpZiB0aGUgZXZlbnQgd2FzIGNhbmNlbGxlZC5cbiAqIEBwYXJhbSBuYW1lIC0gVGhlIG5hbWUgb2YgdGhlIGV2ZW50IHRvIGVtaXRcbiAqIEBwYXJhbSBkYXRhIC0gVGhlIGRhdGEgdGhhdCB3aWxsIGJlIHNlbnQgd2l0aCB0aGUgZXZlbnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEVtaXQ8RSBleHRlbmRzIFdhaWxzRXZlbnROYW1lID0gV2FpbHNFdmVudE5hbWU+KG5hbWU6IEUsIGRhdGE6IFdhaWxzRXZlbnREYXRhPEU+KTogUHJvbWlzZTxib29sZWFuPlxuZXhwb3J0IGZ1bmN0aW9uIEVtaXQ8RSBleHRlbmRzIFdhaWxzRXZlbnROYW1lID0gV2FpbHNFdmVudE5hbWU+KG5hbWU6IFdhaWxzRXZlbnREYXRhPEU+IGV4dGVuZHMgbnVsbCB8IHZvaWQgPyBFIDogbmV2ZXIpOiBQcm9taXNlPGJvb2xlYW4+XG5leHBvcnQgZnVuY3Rpb24gRW1pdDxFIGV4dGVuZHMgV2FpbHNFdmVudE5hbWUgPSBXYWlsc0V2ZW50TmFtZT4obmFtZTogV2FpbHNFdmVudERhdGE8RT4sIGRhdGE/OiBhbnkpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgICByZXR1cm4gY2FsbChFbWl0TWV0aG9kLCAgbmV3IFdhaWxzRXZlbnQobmFtZSwgZGF0YSkpXG59XG5cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLy8gVGhlIGZvbGxvd2luZyB1dGlsaXRpZXMgaGF2ZSBiZWVuIGZhY3RvcmVkIG91dCBvZiAuL2V2ZW50cy50c1xuLy8gZm9yIHRlc3RpbmcgcHVycG9zZXMuXG5cbmV4cG9ydCBjb25zdCBldmVudExpc3RlbmVycyA9IG5ldyBNYXA8c3RyaW5nLCBMaXN0ZW5lcltdPigpO1xuXG5leHBvcnQgY2xhc3MgTGlzdGVuZXIge1xuICAgIGV2ZW50TmFtZTogc3RyaW5nO1xuICAgIGNhbGxiYWNrOiAoZGF0YTogYW55KSA9PiB2b2lkO1xuICAgIG1heENhbGxiYWNrczogbnVtYmVyO1xuXG4gICAgY29uc3RydWN0b3IoZXZlbnROYW1lOiBzdHJpbmcsIGNhbGxiYWNrOiAoZGF0YTogYW55KSA9PiB2b2lkLCBtYXhDYWxsYmFja3M6IG51bWJlcikge1xuICAgICAgICB0aGlzLmV2ZW50TmFtZSA9IGV2ZW50TmFtZTtcbiAgICAgICAgdGhpcy5jYWxsYmFjayA9IGNhbGxiYWNrO1xuICAgICAgICB0aGlzLm1heENhbGxiYWNrcyA9IG1heENhbGxiYWNrcyB8fCAtMTtcbiAgICB9XG5cbiAgICBkaXNwYXRjaChkYXRhOiBhbnkpOiBib29sZWFuIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHRoaXMuY2FsbGJhY2soZGF0YSk7XG4gICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgY29uc29sZS5lcnJvcihlcnIpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRoaXMubWF4Q2FsbGJhY2tzID09PSAtMSkgcmV0dXJuIGZhbHNlO1xuICAgICAgICB0aGlzLm1heENhbGxiYWNrcyAtPSAxO1xuICAgICAgICByZXR1cm4gdGhpcy5tYXhDYWxsYmFja3MgPT09IDA7XG4gICAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gbGlzdGVuZXJPZmYobGlzdGVuZXI6IExpc3RlbmVyKTogdm9pZCB7XG4gICAgbGV0IGxpc3RlbmVycyA9IGV2ZW50TGlzdGVuZXJzLmdldChsaXN0ZW5lci5ldmVudE5hbWUpO1xuICAgIGlmICghbGlzdGVuZXJzKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBsaXN0ZW5lcnMgPSBsaXN0ZW5lcnMuZmlsdGVyKGwgPT4gbCAhPT0gbGlzdGVuZXIpO1xuICAgIGlmIChsaXN0ZW5lcnMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLmRlbGV0ZShsaXN0ZW5lci5ldmVudE5hbWUpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLnNldChsaXN0ZW5lci5ldmVudE5hbWUsIGxpc3RlbmVycyk7XG4gICAgfVxufVxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG4vKipcbiAqIEFueSBpcyBhIGR1bW15IGNyZWF0aW9uIGZ1bmN0aW9uIGZvciBzaW1wbGUgb3IgdW5rbm93biB0eXBlcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEFueTxUID0gYW55Pihzb3VyY2U6IGFueSk6IFQge1xuICAgIHJldHVybiBzb3VyY2U7XG59XG5cbi8qKlxuICogQnl0ZVNsaWNlIGlzIGEgY3JlYXRpb24gZnVuY3Rpb24gdGhhdCByZXBsYWNlc1xuICogbnVsbCBzdHJpbmdzIHdpdGggZW1wdHkgc3RyaW5ncy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEJ5dGVTbGljZShzb3VyY2U6IGFueSk6IHN0cmluZyB7XG4gICAgcmV0dXJuICgoc291cmNlID09IG51bGwpID8gXCJcIiA6IHNvdXJjZSk7XG59XG5cbi8qKlxuICogQXJyYXkgdGFrZXMgYSBjcmVhdGlvbiBmdW5jdGlvbiBmb3IgYW4gYXJiaXRyYXJ5IHR5cGVcbiAqIGFuZCByZXR1cm5zIGFuIGluLXBsYWNlIGNyZWF0aW9uIGZ1bmN0aW9uIGZvciBhbiBhcnJheVxuICogd2hvc2UgZWxlbWVudHMgYXJlIG9mIHRoYXQgdHlwZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEFycmF5PFQgPSBhbnk+KGVsZW1lbnQ6IChzb3VyY2U6IGFueSkgPT4gVCk6IChzb3VyY2U6IGFueSkgPT4gVFtdIHtcbiAgICBpZiAoZWxlbWVudCA9PT0gQW55KSB7XG4gICAgICAgIHJldHVybiAoc291cmNlKSA9PiAoc291cmNlID09PSBudWxsID8gW10gOiBzb3VyY2UpO1xuICAgIH1cblxuICAgIHJldHVybiAoc291cmNlKSA9PiB7XG4gICAgICAgIGlmIChzb3VyY2UgPT09IG51bGwpIHtcbiAgICAgICAgICAgIHJldHVybiBbXTtcbiAgICAgICAgfVxuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHNvdXJjZS5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgc291cmNlW2ldID0gZWxlbWVudChzb3VyY2VbaV0pO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBzb3VyY2U7XG4gICAgfTtcbn1cblxuLyoqXG4gKiBNYXAgdGFrZXMgY3JlYXRpb24gZnVuY3Rpb25zIGZvciB0d28gYXJiaXRyYXJ5IHR5cGVzXG4gKiBhbmQgcmV0dXJucyBhbiBpbi1wbGFjZSBjcmVhdGlvbiBmdW5jdGlvbiBmb3IgYW4gb2JqZWN0XG4gKiB3aG9zZSBrZXlzIGFuZCB2YWx1ZXMgYXJlIG9mIHRob3NlIHR5cGVzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gTWFwPFYgPSBhbnk+KGtleTogKHNvdXJjZTogYW55KSA9PiBzdHJpbmcsIHZhbHVlOiAoc291cmNlOiBhbnkpID0+IFYpOiAoc291cmNlOiBhbnkpID0+IFJlY29yZDxzdHJpbmcsIFY+IHtcbiAgICBpZiAodmFsdWUgPT09IEFueSkge1xuICAgICAgICByZXR1cm4gKHNvdXJjZSkgPT4gKHNvdXJjZSA9PT0gbnVsbCA/IHt9IDogc291cmNlKTtcbiAgICB9XG5cbiAgICByZXR1cm4gKHNvdXJjZSkgPT4ge1xuICAgICAgICBpZiAoc291cmNlID09PSBudWxsKSB7XG4gICAgICAgICAgICByZXR1cm4ge307XG4gICAgICAgIH1cbiAgICAgICAgZm9yIChjb25zdCBrZXkgaW4gc291cmNlKSB7XG4gICAgICAgICAgICBzb3VyY2Vba2V5XSA9IHZhbHVlKHNvdXJjZVtrZXldKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gc291cmNlO1xuICAgIH07XG59XG5cbi8qKlxuICogTnVsbGFibGUgdGFrZXMgYSBjcmVhdGlvbiBmdW5jdGlvbiBmb3IgYW4gYXJiaXRyYXJ5IHR5cGVcbiAqIGFuZCByZXR1cm5zIGEgY3JlYXRpb24gZnVuY3Rpb24gZm9yIGEgbnVsbGFibGUgdmFsdWUgb2YgdGhhdCB0eXBlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gTnVsbGFibGU8VCA9IGFueT4oZWxlbWVudDogKHNvdXJjZTogYW55KSA9PiBUKTogKHNvdXJjZTogYW55KSA9PiAoVCB8IG51bGwpIHtcbiAgICBpZiAoZWxlbWVudCA9PT0gQW55KSB7XG4gICAgICAgIHJldHVybiBBbnk7XG4gICAgfVxuXG4gICAgcmV0dXJuIChzb3VyY2UpID0+IChzb3VyY2UgPT09IG51bGwgPyBudWxsIDogZWxlbWVudChzb3VyY2UpKTtcbn1cblxuLyoqXG4gKiBTdHJ1Y3QgdGFrZXMgYW4gb2JqZWN0IG1hcHBpbmcgZmllbGQgbmFtZXMgdG8gY3JlYXRpb24gZnVuY3Rpb25zXG4gKiBhbmQgcmV0dXJucyBhbiBpbi1wbGFjZSBjcmVhdGlvbiBmdW5jdGlvbiBmb3IgYSBzdHJ1Y3QuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBTdHJ1Y3QoY3JlYXRlRmllbGQ6IFJlY29yZDxzdHJpbmcsIChzb3VyY2U6IGFueSkgPT4gYW55Pik6XG4gICAgPFUgZXh0ZW5kcyBSZWNvcmQ8c3RyaW5nLCBhbnk+ID0gYW55Pihzb3VyY2U6IGFueSkgPT4gVVxue1xuICAgIGxldCBhbGxBbnkgPSB0cnVlO1xuICAgIGZvciAoY29uc3QgbmFtZSBpbiBjcmVhdGVGaWVsZCkge1xuICAgICAgICBpZiAoY3JlYXRlRmllbGRbbmFtZV0gIT09IEFueSkge1xuICAgICAgICAgICAgYWxsQW55ID0gZmFsc2U7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgIH1cbiAgICBpZiAoYWxsQW55KSB7XG4gICAgICAgIHJldHVybiBBbnk7XG4gICAgfVxuXG4gICAgcmV0dXJuIChzb3VyY2UpID0+IHtcbiAgICAgICAgZm9yIChjb25zdCBuYW1lIGluIGNyZWF0ZUZpZWxkKSB7XG4gICAgICAgICAgICBpZiAobmFtZSBpbiBzb3VyY2UpIHtcbiAgICAgICAgICAgICAgICBzb3VyY2VbbmFtZV0gPSBjcmVhdGVGaWVsZFtuYW1lXShzb3VyY2VbbmFtZV0pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBzb3VyY2U7XG4gICAgfTtcbn1cblxuLyoqXG4gKiBNYXBzIGtub3duIGV2ZW50IG5hbWVzIHRvIGNyZWF0aW9uIGZ1bmN0aW9ucyBmb3IgdGhlaXIgZGF0YSB0eXBlcy5cbiAqIFdpbGwgYmUgbW9ua2V5LXBhdGNoZWQgYnkgdGhlIGJpbmRpbmcgZ2VuZXJhdG9yLlxuICovXG5leHBvcnQgY29uc3QgRXZlbnRzOiBSZWNvcmQ8c3RyaW5nLCAoc291cmNlOiBhbnkpID0+IGFueT4gPSB7fTtcbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLy8gQ3luaHlyY2h3eWQgeSBmZmVpbCBob24geW4gYXd0b21hdGlnLiBQRUlESVdDSCBcdTAwQzIgTU9ESVdMXG4vLyBUaGlzIGZpbGUgaXMgYXV0b21hdGljYWxseSBnZW5lcmF0ZWQuIERPIE5PVCBFRElUXG5cbmV4cG9ydCBjb25zdCBUeXBlcyA9IE9iamVjdC5mcmVlemUoe1xuXHRXaW5kb3dzOiBPYmplY3QuZnJlZXplKHtcblx0XHRBUE1Qb3dlclNldHRpbmdDaGFuZ2U6IFwid2luZG93czpBUE1Qb3dlclNldHRpbmdDaGFuZ2VcIixcblx0XHRBUE1Qb3dlclN0YXR1c0NoYW5nZTogXCJ3aW5kb3dzOkFQTVBvd2VyU3RhdHVzQ2hhbmdlXCIsXG5cdFx0QVBNUmVzdW1lQXV0b21hdGljOiBcIndpbmRvd3M6QVBNUmVzdW1lQXV0b21hdGljXCIsXG5cdFx0QVBNUmVzdW1lU3VzcGVuZDogXCJ3aW5kb3dzOkFQTVJlc3VtZVN1c3BlbmRcIixcblx0XHRBUE1TdXNwZW5kOiBcIndpbmRvd3M6QVBNU3VzcGVuZFwiLFxuXHRcdEFwcGxpY2F0aW9uU3RhcnRlZDogXCJ3aW5kb3dzOkFwcGxpY2F0aW9uU3RhcnRlZFwiLFxuXHRcdFN5c3RlbVRoZW1lQ2hhbmdlZDogXCJ3aW5kb3dzOlN5c3RlbVRoZW1lQ2hhbmdlZFwiLFxuXHRcdFdlYlZpZXdOYXZpZ2F0aW9uQ29tcGxldGVkOiBcIndpbmRvd3M6V2ViVmlld05hdmlnYXRpb25Db21wbGV0ZWRcIixcblx0XHRXaW5kb3dBY3RpdmU6IFwid2luZG93czpXaW5kb3dBY3RpdmVcIixcblx0XHRXaW5kb3dCYWNrZ3JvdW5kRXJhc2U6IFwid2luZG93czpXaW5kb3dCYWNrZ3JvdW5kRXJhc2VcIixcblx0XHRXaW5kb3dDbGlja0FjdGl2ZTogXCJ3aW5kb3dzOldpbmRvd0NsaWNrQWN0aXZlXCIsXG5cdFx0V2luZG93Q2xvc2luZzogXCJ3aW5kb3dzOldpbmRvd0Nsb3NpbmdcIixcblx0XHRXaW5kb3dEaWRNb3ZlOiBcIndpbmRvd3M6V2luZG93RGlkTW92ZVwiLFxuXHRcdFdpbmRvd0RpZFJlc2l6ZTogXCJ3aW5kb3dzOldpbmRvd0RpZFJlc2l6ZVwiLFxuXHRcdFdpbmRvd0RQSUNoYW5nZWQ6IFwid2luZG93czpXaW5kb3dEUElDaGFuZ2VkXCIsXG5cdFx0V2luZG93RHJhZ0Ryb3A6IFwid2luZG93czpXaW5kb3dEcmFnRHJvcFwiLFxuXHRcdFdpbmRvd0RyYWdFbnRlcjogXCJ3aW5kb3dzOldpbmRvd0RyYWdFbnRlclwiLFxuXHRcdFdpbmRvd0RyYWdMZWF2ZTogXCJ3aW5kb3dzOldpbmRvd0RyYWdMZWF2ZVwiLFxuXHRcdFdpbmRvd0RyYWdPdmVyOiBcIndpbmRvd3M6V2luZG93RHJhZ092ZXJcIixcblx0XHRXaW5kb3dFbmRNb3ZlOiBcIndpbmRvd3M6V2luZG93RW5kTW92ZVwiLFxuXHRcdFdpbmRvd0VuZFJlc2l6ZTogXCJ3aW5kb3dzOldpbmRvd0VuZFJlc2l6ZVwiLFxuXHRcdFdpbmRvd0Z1bGxzY3JlZW46IFwid2luZG93czpXaW5kb3dGdWxsc2NyZWVuXCIsXG5cdFx0V2luZG93SGlkZTogXCJ3aW5kb3dzOldpbmRvd0hpZGVcIixcblx0XHRXaW5kb3dJbmFjdGl2ZTogXCJ3aW5kb3dzOldpbmRvd0luYWN0aXZlXCIsXG5cdFx0V2luZG93S2V5RG93bjogXCJ3aW5kb3dzOldpbmRvd0tleURvd25cIixcblx0XHRXaW5kb3dLZXlVcDogXCJ3aW5kb3dzOldpbmRvd0tleVVwXCIsXG5cdFx0V2luZG93S2lsbEZvY3VzOiBcIndpbmRvd3M6V2luZG93S2lsbEZvY3VzXCIsXG5cdFx0V2luZG93Tm9uQ2xpZW50SGl0OiBcIndpbmRvd3M6V2luZG93Tm9uQ2xpZW50SGl0XCIsXG5cdFx0V2luZG93Tm9uQ2xpZW50TW91c2VEb3duOiBcIndpbmRvd3M6V2luZG93Tm9uQ2xpZW50TW91c2VEb3duXCIsXG5cdFx0V2luZG93Tm9uQ2xpZW50TW91c2VMZWF2ZTogXCJ3aW5kb3dzOldpbmRvd05vbkNsaWVudE1vdXNlTGVhdmVcIixcblx0XHRXaW5kb3dOb25DbGllbnRNb3VzZU1vdmU6IFwid2luZG93czpXaW5kb3dOb25DbGllbnRNb3VzZU1vdmVcIixcblx0XHRXaW5kb3dOb25DbGllbnRNb3VzZVVwOiBcIndpbmRvd3M6V2luZG93Tm9uQ2xpZW50TW91c2VVcFwiLFxuXHRcdFdpbmRvd1BhaW50OiBcIndpbmRvd3M6V2luZG93UGFpbnRcIixcblx0XHRXaW5kb3dSZXN0b3JlOiBcIndpbmRvd3M6V2luZG93UmVzdG9yZVwiLFxuXHRcdFdpbmRvd1NldEZvY3VzOiBcIndpbmRvd3M6V2luZG93U2V0Rm9jdXNcIixcblx0XHRXaW5kb3dTaG93OiBcIndpbmRvd3M6V2luZG93U2hvd1wiLFxuXHRcdFdpbmRvd1N0YXJ0TW92ZTogXCJ3aW5kb3dzOldpbmRvd1N0YXJ0TW92ZVwiLFxuXHRcdFdpbmRvd1N0YXJ0UmVzaXplOiBcIndpbmRvd3M6V2luZG93U3RhcnRSZXNpemVcIixcblx0XHRXaW5kb3dVbkZ1bGxzY3JlZW46IFwid2luZG93czpXaW5kb3dVbkZ1bGxzY3JlZW5cIixcblx0XHRXaW5kb3daT3JkZXJDaGFuZ2VkOiBcIndpbmRvd3M6V2luZG93Wk9yZGVyQ2hhbmdlZFwiLFxuXHRcdFdpbmRvd01pbmltaXNlOiBcIndpbmRvd3M6V2luZG93TWluaW1pc2VcIixcblx0XHRXaW5kb3dVbk1pbmltaXNlOiBcIndpbmRvd3M6V2luZG93VW5NaW5pbWlzZVwiLFxuXHRcdFdpbmRvd01heGltaXNlOiBcIndpbmRvd3M6V2luZG93TWF4aW1pc2VcIixcblx0XHRXaW5kb3dVbk1heGltaXNlOiBcIndpbmRvd3M6V2luZG93VW5NYXhpbWlzZVwiLFxuXHR9KSxcblx0TWFjOiBPYmplY3QuZnJlZXplKHtcblx0XHRBcHBsaWNhdGlvbkRpZEJlY29tZUFjdGl2ZTogXCJtYWM6QXBwbGljYXRpb25EaWRCZWNvbWVBY3RpdmVcIixcblx0XHRBcHBsaWNhdGlvbkRpZENoYW5nZUJhY2tpbmdQcm9wZXJ0aWVzOiBcIm1hYzpBcHBsaWNhdGlvbkRpZENoYW5nZUJhY2tpbmdQcm9wZXJ0aWVzXCIsXG5cdFx0QXBwbGljYXRpb25EaWRDaGFuZ2VFZmZlY3RpdmVBcHBlYXJhbmNlOiBcIm1hYzpBcHBsaWNhdGlvbkRpZENoYW5nZUVmZmVjdGl2ZUFwcGVhcmFuY2VcIixcblx0XHRBcHBsaWNhdGlvbkRpZENoYW5nZUljb246IFwibWFjOkFwcGxpY2F0aW9uRGlkQ2hhbmdlSWNvblwiLFxuXHRcdEFwcGxpY2F0aW9uRGlkQ2hhbmdlT2NjbHVzaW9uU3RhdGU6IFwibWFjOkFwcGxpY2F0aW9uRGlkQ2hhbmdlT2NjbHVzaW9uU3RhdGVcIixcblx0XHRBcHBsaWNhdGlvbkRpZENoYW5nZVNjcmVlblBhcmFtZXRlcnM6IFwibWFjOkFwcGxpY2F0aW9uRGlkQ2hhbmdlU2NyZWVuUGFyYW1ldGVyc1wiLFxuXHRcdEFwcGxpY2F0aW9uRGlkQ2hhbmdlU3RhdHVzQmFyRnJhbWU6IFwibWFjOkFwcGxpY2F0aW9uRGlkQ2hhbmdlU3RhdHVzQmFyRnJhbWVcIixcblx0XHRBcHBsaWNhdGlvbkRpZENoYW5nZVN0YXR1c0Jhck9yaWVudGF0aW9uOiBcIm1hYzpBcHBsaWNhdGlvbkRpZENoYW5nZVN0YXR1c0Jhck9yaWVudGF0aW9uXCIsXG5cdFx0QXBwbGljYXRpb25EaWRDaGFuZ2VUaGVtZTogXCJtYWM6QXBwbGljYXRpb25EaWRDaGFuZ2VUaGVtZVwiLFxuXHRcdEFwcGxpY2F0aW9uRGlkRmluaXNoTGF1bmNoaW5nOiBcIm1hYzpBcHBsaWNhdGlvbkRpZEZpbmlzaExhdW5jaGluZ1wiLFxuXHRcdEFwcGxpY2F0aW9uRGlkSGlkZTogXCJtYWM6QXBwbGljYXRpb25EaWRIaWRlXCIsXG5cdFx0QXBwbGljYXRpb25EaWRSZXNpZ25BY3RpdmU6IFwibWFjOkFwcGxpY2F0aW9uRGlkUmVzaWduQWN0aXZlXCIsXG5cdFx0QXBwbGljYXRpb25EaWRVbmhpZGU6IFwibWFjOkFwcGxpY2F0aW9uRGlkVW5oaWRlXCIsXG5cdFx0QXBwbGljYXRpb25EaWRVcGRhdGU6IFwibWFjOkFwcGxpY2F0aW9uRGlkVXBkYXRlXCIsXG5cdFx0QXBwbGljYXRpb25TaG91bGRIYW5kbGVSZW9wZW46IFwibWFjOkFwcGxpY2F0aW9uU2hvdWxkSGFuZGxlUmVvcGVuXCIsXG5cdFx0QXBwbGljYXRpb25XaWxsQmVjb21lQWN0aXZlOiBcIm1hYzpBcHBsaWNhdGlvbldpbGxCZWNvbWVBY3RpdmVcIixcblx0XHRBcHBsaWNhdGlvbldpbGxGaW5pc2hMYXVuY2hpbmc6IFwibWFjOkFwcGxpY2F0aW9uV2lsbEZpbmlzaExhdW5jaGluZ1wiLFxuXHRcdEFwcGxpY2F0aW9uV2lsbEhpZGU6IFwibWFjOkFwcGxpY2F0aW9uV2lsbEhpZGVcIixcblx0XHRBcHBsaWNhdGlvbldpbGxSZXNpZ25BY3RpdmU6IFwibWFjOkFwcGxpY2F0aW9uV2lsbFJlc2lnbkFjdGl2ZVwiLFxuXHRcdEFwcGxpY2F0aW9uV2lsbFRlcm1pbmF0ZTogXCJtYWM6QXBwbGljYXRpb25XaWxsVGVybWluYXRlXCIsXG5cdFx0QXBwbGljYXRpb25XaWxsVW5oaWRlOiBcIm1hYzpBcHBsaWNhdGlvbldpbGxVbmhpZGVcIixcblx0XHRBcHBsaWNhdGlvbldpbGxVcGRhdGU6IFwibWFjOkFwcGxpY2F0aW9uV2lsbFVwZGF0ZVwiLFxuXHRcdE1lbnVEaWRBZGRJdGVtOiBcIm1hYzpNZW51RGlkQWRkSXRlbVwiLFxuXHRcdE1lbnVEaWRCZWdpblRyYWNraW5nOiBcIm1hYzpNZW51RGlkQmVnaW5UcmFja2luZ1wiLFxuXHRcdE1lbnVEaWRDbG9zZTogXCJtYWM6TWVudURpZENsb3NlXCIsXG5cdFx0TWVudURpZERpc3BsYXlJdGVtOiBcIm1hYzpNZW51RGlkRGlzcGxheUl0ZW1cIixcblx0XHRNZW51RGlkRW5kVHJhY2tpbmc6IFwibWFjOk1lbnVEaWRFbmRUcmFja2luZ1wiLFxuXHRcdE1lbnVEaWRIaWdobGlnaHRJdGVtOiBcIm1hYzpNZW51RGlkSGlnaGxpZ2h0SXRlbVwiLFxuXHRcdE1lbnVEaWRPcGVuOiBcIm1hYzpNZW51RGlkT3BlblwiLFxuXHRcdE1lbnVEaWRQb3BVcDogXCJtYWM6TWVudURpZFBvcFVwXCIsXG5cdFx0TWVudURpZFJlbW92ZUl0ZW06IFwibWFjOk1lbnVEaWRSZW1vdmVJdGVtXCIsXG5cdFx0TWVudURpZFNlbmRBY3Rpb246IFwibWFjOk1lbnVEaWRTZW5kQWN0aW9uXCIsXG5cdFx0TWVudURpZFNlbmRBY3Rpb25Ub0l0ZW06IFwibWFjOk1lbnVEaWRTZW5kQWN0aW9uVG9JdGVtXCIsXG5cdFx0TWVudURpZFVwZGF0ZTogXCJtYWM6TWVudURpZFVwZGF0ZVwiLFxuXHRcdE1lbnVXaWxsQWRkSXRlbTogXCJtYWM6TWVudVdpbGxBZGRJdGVtXCIsXG5cdFx0TWVudVdpbGxCZWdpblRyYWNraW5nOiBcIm1hYzpNZW51V2lsbEJlZ2luVHJhY2tpbmdcIixcblx0XHRNZW51V2lsbERpc3BsYXlJdGVtOiBcIm1hYzpNZW51V2lsbERpc3BsYXlJdGVtXCIsXG5cdFx0TWVudVdpbGxFbmRUcmFja2luZzogXCJtYWM6TWVudVdpbGxFbmRUcmFja2luZ1wiLFxuXHRcdE1lbnVXaWxsSGlnaGxpZ2h0SXRlbTogXCJtYWM6TWVudVdpbGxIaWdobGlnaHRJdGVtXCIsXG5cdFx0TWVudVdpbGxPcGVuOiBcIm1hYzpNZW51V2lsbE9wZW5cIixcblx0XHRNZW51V2lsbFBvcFVwOiBcIm1hYzpNZW51V2lsbFBvcFVwXCIsXG5cdFx0TWVudVdpbGxSZW1vdmVJdGVtOiBcIm1hYzpNZW51V2lsbFJlbW92ZUl0ZW1cIixcblx0XHRNZW51V2lsbFNlbmRBY3Rpb246IFwibWFjOk1lbnVXaWxsU2VuZEFjdGlvblwiLFxuXHRcdE1lbnVXaWxsU2VuZEFjdGlvblRvSXRlbTogXCJtYWM6TWVudVdpbGxTZW5kQWN0aW9uVG9JdGVtXCIsXG5cdFx0TWVudVdpbGxVcGRhdGU6IFwibWFjOk1lbnVXaWxsVXBkYXRlXCIsXG5cdFx0V2ViVmlld0RpZENvbW1pdE5hdmlnYXRpb246IFwibWFjOldlYlZpZXdEaWRDb21taXROYXZpZ2F0aW9uXCIsXG5cdFx0V2ViVmlld0RpZEZpbmlzaE5hdmlnYXRpb246IFwibWFjOldlYlZpZXdEaWRGaW5pc2hOYXZpZ2F0aW9uXCIsXG5cdFx0V2ViVmlld0RpZFJlY2VpdmVTZXJ2ZXJSZWRpcmVjdEZvclByb3Zpc2lvbmFsTmF2aWdhdGlvbjogXCJtYWM6V2ViVmlld0RpZFJlY2VpdmVTZXJ2ZXJSZWRpcmVjdEZvclByb3Zpc2lvbmFsTmF2aWdhdGlvblwiLFxuXHRcdFdlYlZpZXdEaWRTdGFydFByb3Zpc2lvbmFsTmF2aWdhdGlvbjogXCJtYWM6V2ViVmlld0RpZFN0YXJ0UHJvdmlzaW9uYWxOYXZpZ2F0aW9uXCIsXG5cdFx0V2luZG93RGlkQmVjb21lS2V5OiBcIm1hYzpXaW5kb3dEaWRCZWNvbWVLZXlcIixcblx0XHRXaW5kb3dEaWRCZWNvbWVNYWluOiBcIm1hYzpXaW5kb3dEaWRCZWNvbWVNYWluXCIsXG5cdFx0V2luZG93RGlkQmVnaW5TaGVldDogXCJtYWM6V2luZG93RGlkQmVnaW5TaGVldFwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZUFscGhhOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VBbHBoYVwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZUJhY2tpbmdMb2NhdGlvbjogXCJtYWM6V2luZG93RGlkQ2hhbmdlQmFja2luZ0xvY2F0aW9uXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlQmFja2luZ1Byb3BlcnRpZXM6IFwibWFjOldpbmRvd0RpZENoYW5nZUJhY2tpbmdQcm9wZXJ0aWVzXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlQ29sbGVjdGlvbkJlaGF2aW9yOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VDb2xsZWN0aW9uQmVoYXZpb3JcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VFZmZlY3RpdmVBcHBlYXJhbmNlOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VFZmZlY3RpdmVBcHBlYXJhbmNlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlT2NjbHVzaW9uU3RhdGU6IFwibWFjOldpbmRvd0RpZENoYW5nZU9jY2x1c2lvblN0YXRlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlT3JkZXJpbmdNb2RlOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VPcmRlcmluZ01vZGVcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VTY3JlZW46IFwibWFjOldpbmRvd0RpZENoYW5nZVNjcmVlblwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZVNjcmVlblBhcmFtZXRlcnM6IFwibWFjOldpbmRvd0RpZENoYW5nZVNjcmVlblBhcmFtZXRlcnNcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VTY3JlZW5Qcm9maWxlOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VTY3JlZW5Qcm9maWxlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlU2NyZWVuU3BhY2U6IFwibWFjOldpbmRvd0RpZENoYW5nZVNjcmVlblNwYWNlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlU2NyZWVuU3BhY2VQcm9wZXJ0aWVzOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VTY3JlZW5TcGFjZVByb3BlcnRpZXNcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VTaGFyaW5nVHlwZTogXCJtYWM6V2luZG93RGlkQ2hhbmdlU2hhcmluZ1R5cGVcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VTcGFjZTogXCJtYWM6V2luZG93RGlkQ2hhbmdlU3BhY2VcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VTcGFjZU9yZGVyaW5nTW9kZTogXCJtYWM6V2luZG93RGlkQ2hhbmdlU3BhY2VPcmRlcmluZ01vZGVcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VUaXRsZTogXCJtYWM6V2luZG93RGlkQ2hhbmdlVGl0bGVcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VUb29sYmFyOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VUb29sYmFyXCIsXG5cdFx0V2luZG93RGlkRGVtaW5pYXR1cml6ZTogXCJtYWM6V2luZG93RGlkRGVtaW5pYXR1cml6ZVwiLFxuXHRcdFdpbmRvd0RpZEVuZFNoZWV0OiBcIm1hYzpXaW5kb3dEaWRFbmRTaGVldFwiLFxuXHRcdFdpbmRvd0RpZEVudGVyRnVsbFNjcmVlbjogXCJtYWM6V2luZG93RGlkRW50ZXJGdWxsU2NyZWVuXCIsXG5cdFx0V2luZG93RGlkRW50ZXJWZXJzaW9uQnJvd3NlcjogXCJtYWM6V2luZG93RGlkRW50ZXJWZXJzaW9uQnJvd3NlclwiLFxuXHRcdFdpbmRvd0RpZEV4aXRGdWxsU2NyZWVuOiBcIm1hYzpXaW5kb3dEaWRFeGl0RnVsbFNjcmVlblwiLFxuXHRcdFdpbmRvd0RpZEV4aXRWZXJzaW9uQnJvd3NlcjogXCJtYWM6V2luZG93RGlkRXhpdFZlcnNpb25Ccm93c2VyXCIsXG5cdFx0V2luZG93RGlkRXhwb3NlOiBcIm1hYzpXaW5kb3dEaWRFeHBvc2VcIixcblx0XHRXaW5kb3dEaWRGb2N1czogXCJtYWM6V2luZG93RGlkRm9jdXNcIixcblx0XHRXaW5kb3dEaWRNaW5pYXR1cml6ZTogXCJtYWM6V2luZG93RGlkTWluaWF0dXJpemVcIixcblx0XHRXaW5kb3dEaWRNb3ZlOiBcIm1hYzpXaW5kb3dEaWRNb3ZlXCIsXG5cdFx0V2luZG93RGlkT3JkZXJPZmZTY3JlZW46IFwibWFjOldpbmRvd0RpZE9yZGVyT2ZmU2NyZWVuXCIsXG5cdFx0V2luZG93RGlkT3JkZXJPblNjcmVlbjogXCJtYWM6V2luZG93RGlkT3JkZXJPblNjcmVlblwiLFxuXHRcdFdpbmRvd0RpZFJlc2lnbktleTogXCJtYWM6V2luZG93RGlkUmVzaWduS2V5XCIsXG5cdFx0V2luZG93RGlkUmVzaWduTWFpbjogXCJtYWM6V2luZG93RGlkUmVzaWduTWFpblwiLFxuXHRcdFdpbmRvd0RpZFJlc2l6ZTogXCJtYWM6V2luZG93RGlkUmVzaXplXCIsXG5cdFx0V2luZG93RGlkVXBkYXRlOiBcIm1hYzpXaW5kb3dEaWRVcGRhdGVcIixcblx0XHRXaW5kb3dEaWRVcGRhdGVBbHBoYTogXCJtYWM6V2luZG93RGlkVXBkYXRlQWxwaGFcIixcblx0XHRXaW5kb3dEaWRVcGRhdGVDb2xsZWN0aW9uQmVoYXZpb3I6IFwibWFjOldpbmRvd0RpZFVwZGF0ZUNvbGxlY3Rpb25CZWhhdmlvclwiLFxuXHRcdFdpbmRvd0RpZFVwZGF0ZUNvbGxlY3Rpb25Qcm9wZXJ0aWVzOiBcIm1hYzpXaW5kb3dEaWRVcGRhdGVDb2xsZWN0aW9uUHJvcGVydGllc1wiLFxuXHRcdFdpbmRvd0RpZFVwZGF0ZVNoYWRvdzogXCJtYWM6V2luZG93RGlkVXBkYXRlU2hhZG93XCIsXG5cdFx0V2luZG93RGlkVXBkYXRlVGl0bGU6IFwibWFjOldpbmRvd0RpZFVwZGF0ZVRpdGxlXCIsXG5cdFx0V2luZG93RGlkVXBkYXRlVG9vbGJhcjogXCJtYWM6V2luZG93RGlkVXBkYXRlVG9vbGJhclwiLFxuXHRcdFdpbmRvd0RpZFpvb206IFwibWFjOldpbmRvd0RpZFpvb21cIixcblx0XHRXaW5kb3dGaWxlRHJhZ2dpbmdFbnRlcmVkOiBcIm1hYzpXaW5kb3dGaWxlRHJhZ2dpbmdFbnRlcmVkXCIsXG5cdFx0V2luZG93RmlsZURyYWdnaW5nRXhpdGVkOiBcIm1hYzpXaW5kb3dGaWxlRHJhZ2dpbmdFeGl0ZWRcIixcblx0XHRXaW5kb3dGaWxlRHJhZ2dpbmdQZXJmb3JtZWQ6IFwibWFjOldpbmRvd0ZpbGVEcmFnZ2luZ1BlcmZvcm1lZFwiLFxuXHRcdFdpbmRvd0hpZGU6IFwibWFjOldpbmRvd0hpZGVcIixcblx0XHRXaW5kb3dNYXhpbWlzZTogXCJtYWM6V2luZG93TWF4aW1pc2VcIixcblx0XHRXaW5kb3dVbk1heGltaXNlOiBcIm1hYzpXaW5kb3dVbk1heGltaXNlXCIsXG5cdFx0V2luZG93TWluaW1pc2U6IFwibWFjOldpbmRvd01pbmltaXNlXCIsXG5cdFx0V2luZG93VW5NaW5pbWlzZTogXCJtYWM6V2luZG93VW5NaW5pbWlzZVwiLFxuXHRcdFdpbmRvd1Nob3VsZENsb3NlOiBcIm1hYzpXaW5kb3dTaG91bGRDbG9zZVwiLFxuXHRcdFdpbmRvd1Nob3c6IFwibWFjOldpbmRvd1Nob3dcIixcblx0XHRXaW5kb3dXaWxsQmVjb21lS2V5OiBcIm1hYzpXaW5kb3dXaWxsQmVjb21lS2V5XCIsXG5cdFx0V2luZG93V2lsbEJlY29tZU1haW46IFwibWFjOldpbmRvd1dpbGxCZWNvbWVNYWluXCIsXG5cdFx0V2luZG93V2lsbEJlZ2luU2hlZXQ6IFwibWFjOldpbmRvd1dpbGxCZWdpblNoZWV0XCIsXG5cdFx0V2luZG93V2lsbENoYW5nZU9yZGVyaW5nTW9kZTogXCJtYWM6V2luZG93V2lsbENoYW5nZU9yZGVyaW5nTW9kZVwiLFxuXHRcdFdpbmRvd1dpbGxDbG9zZTogXCJtYWM6V2luZG93V2lsbENsb3NlXCIsXG5cdFx0V2luZG93V2lsbERlbWluaWF0dXJpemU6IFwibWFjOldpbmRvd1dpbGxEZW1pbmlhdHVyaXplXCIsXG5cdFx0V2luZG93V2lsbEVudGVyRnVsbFNjcmVlbjogXCJtYWM6V2luZG93V2lsbEVudGVyRnVsbFNjcmVlblwiLFxuXHRcdFdpbmRvd1dpbGxFbnRlclZlcnNpb25Ccm93c2VyOiBcIm1hYzpXaW5kb3dXaWxsRW50ZXJWZXJzaW9uQnJvd3NlclwiLFxuXHRcdFdpbmRvd1dpbGxFeGl0RnVsbFNjcmVlbjogXCJtYWM6V2luZG93V2lsbEV4aXRGdWxsU2NyZWVuXCIsXG5cdFx0V2luZG93V2lsbEV4aXRWZXJzaW9uQnJvd3NlcjogXCJtYWM6V2luZG93V2lsbEV4aXRWZXJzaW9uQnJvd3NlclwiLFxuXHRcdFdpbmRvd1dpbGxGb2N1czogXCJtYWM6V2luZG93V2lsbEZvY3VzXCIsXG5cdFx0V2luZG93V2lsbE1pbmlhdHVyaXplOiBcIm1hYzpXaW5kb3dXaWxsTWluaWF0dXJpemVcIixcblx0XHRXaW5kb3dXaWxsTW92ZTogXCJtYWM6V2luZG93V2lsbE1vdmVcIixcblx0XHRXaW5kb3dXaWxsT3JkZXJPZmZTY3JlZW46IFwibWFjOldpbmRvd1dpbGxPcmRlck9mZlNjcmVlblwiLFxuXHRcdFdpbmRvd1dpbGxPcmRlck9uU2NyZWVuOiBcIm1hYzpXaW5kb3dXaWxsT3JkZXJPblNjcmVlblwiLFxuXHRcdFdpbmRvd1dpbGxSZXNpZ25NYWluOiBcIm1hYzpXaW5kb3dXaWxsUmVzaWduTWFpblwiLFxuXHRcdFdpbmRvd1dpbGxSZXNpemU6IFwibWFjOldpbmRvd1dpbGxSZXNpemVcIixcblx0XHRXaW5kb3dXaWxsVW5mb2N1czogXCJtYWM6V2luZG93V2lsbFVuZm9jdXNcIixcblx0XHRXaW5kb3dXaWxsVXBkYXRlOiBcIm1hYzpXaW5kb3dXaWxsVXBkYXRlXCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZUFscGhhOiBcIm1hYzpXaW5kb3dXaWxsVXBkYXRlQWxwaGFcIixcblx0XHRXaW5kb3dXaWxsVXBkYXRlQ29sbGVjdGlvbkJlaGF2aW9yOiBcIm1hYzpXaW5kb3dXaWxsVXBkYXRlQ29sbGVjdGlvbkJlaGF2aW9yXCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZUNvbGxlY3Rpb25Qcm9wZXJ0aWVzOiBcIm1hYzpXaW5kb3dXaWxsVXBkYXRlQ29sbGVjdGlvblByb3BlcnRpZXNcIixcblx0XHRXaW5kb3dXaWxsVXBkYXRlU2hhZG93OiBcIm1hYzpXaW5kb3dXaWxsVXBkYXRlU2hhZG93XCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZVRpdGxlOiBcIm1hYzpXaW5kb3dXaWxsVXBkYXRlVGl0bGVcIixcblx0XHRXaW5kb3dXaWxsVXBkYXRlVG9vbGJhcjogXCJtYWM6V2luZG93V2lsbFVwZGF0ZVRvb2xiYXJcIixcblx0XHRXaW5kb3dXaWxsVXBkYXRlVmlzaWJpbGl0eTogXCJtYWM6V2luZG93V2lsbFVwZGF0ZVZpc2liaWxpdHlcIixcblx0XHRXaW5kb3dXaWxsVXNlU3RhbmRhcmRGcmFtZTogXCJtYWM6V2luZG93V2lsbFVzZVN0YW5kYXJkRnJhbWVcIixcblx0XHRXaW5kb3dab29tSW46IFwibWFjOldpbmRvd1pvb21JblwiLFxuXHRcdFdpbmRvd1pvb21PdXQ6IFwibWFjOldpbmRvd1pvb21PdXRcIixcblx0XHRXaW5kb3dab29tUmVzZXQ6IFwibWFjOldpbmRvd1pvb21SZXNldFwiLFxuXHR9KSxcblx0TGludXg6IE9iamVjdC5mcmVlemUoe1xuXHRcdEFwcGxpY2F0aW9uU3RhcnR1cDogXCJsaW51eDpBcHBsaWNhdGlvblN0YXJ0dXBcIixcblx0XHRTeXN0ZW1UaGVtZUNoYW5nZWQ6IFwibGludXg6U3lzdGVtVGhlbWVDaGFuZ2VkXCIsXG5cdFx0V2luZG93RGVsZXRlRXZlbnQ6IFwibGludXg6V2luZG93RGVsZXRlRXZlbnRcIixcblx0XHRXaW5kb3dEaWRNb3ZlOiBcImxpbnV4OldpbmRvd0RpZE1vdmVcIixcblx0XHRXaW5kb3dEaWRSZXNpemU6IFwibGludXg6V2luZG93RGlkUmVzaXplXCIsXG5cdFx0V2luZG93Rm9jdXNJbjogXCJsaW51eDpXaW5kb3dGb2N1c0luXCIsXG5cdFx0V2luZG93Rm9jdXNPdXQ6IFwibGludXg6V2luZG93Rm9jdXNPdXRcIixcblx0XHRXaW5kb3dMb2FkU3RhcnRlZDogXCJsaW51eDpXaW5kb3dMb2FkU3RhcnRlZFwiLFxuXHRcdFdpbmRvd0xvYWRSZWRpcmVjdGVkOiBcImxpbnV4OldpbmRvd0xvYWRSZWRpcmVjdGVkXCIsXG5cdFx0V2luZG93TG9hZENvbW1pdHRlZDogXCJsaW51eDpXaW5kb3dMb2FkQ29tbWl0dGVkXCIsXG5cdFx0V2luZG93TG9hZEZpbmlzaGVkOiBcImxpbnV4OldpbmRvd0xvYWRGaW5pc2hlZFwiLFxuXHR9KSxcblx0aU9TOiBPYmplY3QuZnJlZXplKHtcblx0XHRBcHBsaWNhdGlvbkRpZEJlY29tZUFjdGl2ZTogXCJpb3M6QXBwbGljYXRpb25EaWRCZWNvbWVBY3RpdmVcIixcblx0XHRBcHBsaWNhdGlvbkRpZEVudGVyQmFja2dyb3VuZDogXCJpb3M6QXBwbGljYXRpb25EaWRFbnRlckJhY2tncm91bmRcIixcblx0XHRBcHBsaWNhdGlvbkRpZEZpbmlzaExhdW5jaGluZzogXCJpb3M6QXBwbGljYXRpb25EaWRGaW5pc2hMYXVuY2hpbmdcIixcblx0XHRBcHBsaWNhdGlvbkRpZFJlY2VpdmVNZW1vcnlXYXJuaW5nOiBcImlvczpBcHBsaWNhdGlvbkRpZFJlY2VpdmVNZW1vcnlXYXJuaW5nXCIsXG5cdFx0QXBwbGljYXRpb25XaWxsRW50ZXJGb3JlZ3JvdW5kOiBcImlvczpBcHBsaWNhdGlvbldpbGxFbnRlckZvcmVncm91bmRcIixcblx0XHRBcHBsaWNhdGlvbldpbGxSZXNpZ25BY3RpdmU6IFwiaW9zOkFwcGxpY2F0aW9uV2lsbFJlc2lnbkFjdGl2ZVwiLFxuXHRcdEFwcGxpY2F0aW9uV2lsbFRlcm1pbmF0ZTogXCJpb3M6QXBwbGljYXRpb25XaWxsVGVybWluYXRlXCIsXG5cdFx0V2luZG93RGlkTG9hZDogXCJpb3M6V2luZG93RGlkTG9hZFwiLFxuXHRcdFdpbmRvd1dpbGxBcHBlYXI6IFwiaW9zOldpbmRvd1dpbGxBcHBlYXJcIixcblx0XHRXaW5kb3dEaWRBcHBlYXI6IFwiaW9zOldpbmRvd0RpZEFwcGVhclwiLFxuXHRcdFdpbmRvd1dpbGxEaXNhcHBlYXI6IFwiaW9zOldpbmRvd1dpbGxEaXNhcHBlYXJcIixcblx0XHRXaW5kb3dEaWREaXNhcHBlYXI6IFwiaW9zOldpbmRvd0RpZERpc2FwcGVhclwiLFxuXHRcdFdpbmRvd1NhZmVBcmVhSW5zZXRzQ2hhbmdlZDogXCJpb3M6V2luZG93U2FmZUFyZWFJbnNldHNDaGFuZ2VkXCIsXG5cdFx0V2luZG93T3JpZW50YXRpb25DaGFuZ2VkOiBcImlvczpXaW5kb3dPcmllbnRhdGlvbkNoYW5nZWRcIixcblx0XHRXaW5kb3dUb3VjaEJlZ2FuOiBcImlvczpXaW5kb3dUb3VjaEJlZ2FuXCIsXG5cdFx0V2luZG93VG91Y2hNb3ZlZDogXCJpb3M6V2luZG93VG91Y2hNb3ZlZFwiLFxuXHRcdFdpbmRvd1RvdWNoRW5kZWQ6IFwiaW9zOldpbmRvd1RvdWNoRW5kZWRcIixcblx0XHRXaW5kb3dUb3VjaENhbmNlbGxlZDogXCJpb3M6V2luZG93VG91Y2hDYW5jZWxsZWRcIixcblx0XHRXZWJWaWV3RGlkU3RhcnROYXZpZ2F0aW9uOiBcImlvczpXZWJWaWV3RGlkU3RhcnROYXZpZ2F0aW9uXCIsXG5cdFx0V2ViVmlld0RpZEZpbmlzaE5hdmlnYXRpb246IFwiaW9zOldlYlZpZXdEaWRGaW5pc2hOYXZpZ2F0aW9uXCIsXG5cdFx0V2ViVmlld0RpZEZhaWxOYXZpZ2F0aW9uOiBcImlvczpXZWJWaWV3RGlkRmFpbE5hdmlnYXRpb25cIixcblx0XHRXZWJWaWV3RGVjaWRlUG9saWN5Rm9yTmF2aWdhdGlvbkFjdGlvbjogXCJpb3M6V2ViVmlld0RlY2lkZVBvbGljeUZvck5hdmlnYXRpb25BY3Rpb25cIixcblx0fSksXG5cdENvbW1vbjogT2JqZWN0LmZyZWV6ZSh7XG5cdFx0QXBwbGljYXRpb25PcGVuZWRXaXRoRmlsZTogXCJjb21tb246QXBwbGljYXRpb25PcGVuZWRXaXRoRmlsZVwiLFxuXHRcdEFwcGxpY2F0aW9uU3RhcnRlZDogXCJjb21tb246QXBwbGljYXRpb25TdGFydGVkXCIsXG5cdFx0QXBwbGljYXRpb25MYXVuY2hlZFdpdGhVcmw6IFwiY29tbW9uOkFwcGxpY2F0aW9uTGF1bmNoZWRXaXRoVXJsXCIsXG5cdFx0VGhlbWVDaGFuZ2VkOiBcImNvbW1vbjpUaGVtZUNoYW5nZWRcIixcblx0XHRXaW5kb3dDbG9zaW5nOiBcImNvbW1vbjpXaW5kb3dDbG9zaW5nXCIsXG5cdFx0V2luZG93RGlkTW92ZTogXCJjb21tb246V2luZG93RGlkTW92ZVwiLFxuXHRcdFdpbmRvd0RpZFJlc2l6ZTogXCJjb21tb246V2luZG93RGlkUmVzaXplXCIsXG5cdFx0V2luZG93RFBJQ2hhbmdlZDogXCJjb21tb246V2luZG93RFBJQ2hhbmdlZFwiLFxuXHRcdFdpbmRvd0ZpbGVzRHJvcHBlZDogXCJjb21tb246V2luZG93RmlsZXNEcm9wcGVkXCIsXG5cdFx0V2luZG93Rm9jdXM6IFwiY29tbW9uOldpbmRvd0ZvY3VzXCIsXG5cdFx0V2luZG93RnVsbHNjcmVlbjogXCJjb21tb246V2luZG93RnVsbHNjcmVlblwiLFxuXHRcdFdpbmRvd0hpZGU6IFwiY29tbW9uOldpbmRvd0hpZGVcIixcblx0XHRXaW5kb3dMb3N0Rm9jdXM6IFwiY29tbW9uOldpbmRvd0xvc3RGb2N1c1wiLFxuXHRcdFdpbmRvd01heGltaXNlOiBcImNvbW1vbjpXaW5kb3dNYXhpbWlzZVwiLFxuXHRcdFdpbmRvd01pbmltaXNlOiBcImNvbW1vbjpXaW5kb3dNaW5pbWlzZVwiLFxuXHRcdFdpbmRvd1RvZ2dsZUZyYW1lbGVzczogXCJjb21tb246V2luZG93VG9nZ2xlRnJhbWVsZXNzXCIsXG5cdFx0V2luZG93UmVzdG9yZTogXCJjb21tb246V2luZG93UmVzdG9yZVwiLFxuXHRcdFdpbmRvd1J1bnRpbWVSZWFkeTogXCJjb21tb246V2luZG93UnVudGltZVJlYWR5XCIsXG5cdFx0V2luZG93U2hvdzogXCJjb21tb246V2luZG93U2hvd1wiLFxuXHRcdFdpbmRvd1VuRnVsbHNjcmVlbjogXCJjb21tb246V2luZG93VW5GdWxsc2NyZWVuXCIsXG5cdFx0V2luZG93VW5NYXhpbWlzZTogXCJjb21tb246V2luZG93VW5NYXhpbWlzZVwiLFxuXHRcdFdpbmRvd1VuTWluaW1pc2U6IFwiY29tbW9uOldpbmRvd1VuTWluaW1pc2VcIixcblx0XHRXaW5kb3dab29tOiBcImNvbW1vbjpXaW5kb3dab29tXCIsXG5cdFx0V2luZG93Wm9vbUluOiBcImNvbW1vbjpXaW5kb3dab29tSW5cIixcblx0XHRXaW5kb3dab29tT3V0OiBcImNvbW1vbjpXaW5kb3dab29tT3V0XCIsXG5cdFx0V2luZG93Wm9vbVJlc2V0OiBcImNvbW1vbjpXaW5kb3dab29tUmVzZXRcIixcblx0fSksXG59KTtcbiIsICIvKlxuIF8gICAgIF9fICAgICBfIF9fXG58IHwgIC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoqXG4gKiBMb2dzIGEgbWVzc2FnZSB0byB0aGUgY29uc29sZSB3aXRoIGN1c3RvbSBmb3JtYXR0aW5nLlxuICpcbiAqIEBwYXJhbSBtZXNzYWdlIC0gVGhlIG1lc3NhZ2UgdG8gYmUgbG9nZ2VkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZGVidWdMb2cobWVzc2FnZTogYW55KSB7XG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lXG4gICAgY29uc29sZS5sb2coXG4gICAgICAgICclYyB3YWlsczMgJWMgJyArIG1lc3NhZ2UgKyAnICcsXG4gICAgICAgICdiYWNrZ3JvdW5kOiAjYWEwMDAwOyBjb2xvcjogI2ZmZjsgYm9yZGVyLXJhZGl1czogM3B4IDBweCAwcHggM3B4OyBwYWRkaW5nOiAxcHg7IGZvbnQtc2l6ZTogMC43cmVtJyxcbiAgICAgICAgJ2JhY2tncm91bmQ6ICMwMDk5MDA7IGNvbG9yOiAjZmZmOyBib3JkZXItcmFkaXVzOiAwcHggM3B4IDNweCAwcHg7IHBhZGRpbmc6IDFweDsgZm9udC1zaXplOiAwLjdyZW0nXG4gICAgKTtcbn1cblxuLyoqXG4gKiBDaGVja3Mgd2hldGhlciB0aGUgd2VidmlldyBzdXBwb3J0cyB0aGUge0BsaW5rIE1vdXNlRXZlbnQjYnV0dG9uc30gcHJvcGVydHkuXG4gKiBMb29raW5nIGF0IHlvdSBtYWNPUyBIaWdoIFNpZXJyYSFcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNhblRyYWNrQnV0dG9ucygpOiBib29sZWFuIHtcbiAgICByZXR1cm4gKG5ldyBNb3VzZUV2ZW50KCdtb3VzZWRvd24nKSkuYnV0dG9ucyA9PT0gMDtcbn1cblxuLyoqXG4gKiBDaGVja3Mgd2hldGhlciB0aGUgYnJvd3NlciBzdXBwb3J0cyByZW1vdmluZyBsaXN0ZW5lcnMgYnkgdHJpZ2dlcmluZyBhbiBBYm9ydFNpZ25hbFxuICogKHNlZSBodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9BUEkvRXZlbnRUYXJnZXQvYWRkRXZlbnRMaXN0ZW5lciNzaWduYWwpLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY2FuQWJvcnRMaXN0ZW5lcnMoKSB7XG4gICAgaWYgKCFFdmVudFRhcmdldCB8fCAhQWJvcnRTaWduYWwgfHwgIUFib3J0Q29udHJvbGxlcilcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuXG4gICAgbGV0IHJlc3VsdCA9IHRydWU7XG5cbiAgICBjb25zdCB0YXJnZXQgPSBuZXcgRXZlbnRUYXJnZXQoKTtcbiAgICBjb25zdCBjb250cm9sbGVyID0gbmV3IEFib3J0Q29udHJvbGxlcigpO1xuICAgIHRhcmdldC5hZGRFdmVudExpc3RlbmVyKCd0ZXN0JywgKCkgPT4geyByZXN1bHQgPSBmYWxzZTsgfSwgeyBzaWduYWw6IGNvbnRyb2xsZXIuc2lnbmFsIH0pO1xuICAgIGNvbnRyb2xsZXIuYWJvcnQoKTtcbiAgICB0YXJnZXQuZGlzcGF0Y2hFdmVudChuZXcgQ3VzdG9tRXZlbnQoJ3Rlc3QnKSk7XG5cbiAgICByZXR1cm4gcmVzdWx0O1xufVxuXG4vKipcbiAqIFJlc29sdmVzIHRoZSBjbG9zZXN0IEhUTUxFbGVtZW50IGFuY2VzdG9yIG9mIGFuIGV2ZW50J3MgdGFyZ2V0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gZXZlbnRUYXJnZXQoZXZlbnQ6IEV2ZW50KTogSFRNTEVsZW1lbnQge1xuICAgIGlmIChldmVudC50YXJnZXQgaW5zdGFuY2VvZiBIVE1MRWxlbWVudCkge1xuICAgICAgICByZXR1cm4gZXZlbnQudGFyZ2V0O1xuICAgIH0gZWxzZSBpZiAoIShldmVudC50YXJnZXQgaW5zdGFuY2VvZiBIVE1MRWxlbWVudCkgJiYgZXZlbnQudGFyZ2V0IGluc3RhbmNlb2YgTm9kZSkge1xuICAgICAgICByZXR1cm4gZXZlbnQudGFyZ2V0LnBhcmVudEVsZW1lbnQgPz8gZG9jdW1lbnQuYm9keTtcbiAgICB9IGVsc2Uge1xuICAgICAgICByZXR1cm4gZG9jdW1lbnQuYm9keTtcbiAgICB9XG59XG5cbi8qKipcbiBUaGlzIHRlY2huaXF1ZSBmb3IgcHJvcGVyIGxvYWQgZGV0ZWN0aW9uIGlzIHRha2VuIGZyb20gSFRNWDpcblxuIEJTRCAyLUNsYXVzZSBMaWNlbnNlXG5cbiBDb3B5cmlnaHQgKGMpIDIwMjAsIEJpZyBTa3kgU29mdHdhcmVcbiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuXG4gUmVkaXN0cmlidXRpb24gYW5kIHVzZSBpbiBzb3VyY2UgYW5kIGJpbmFyeSBmb3Jtcywgd2l0aCBvciB3aXRob3V0XG4gbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zIGFyZSBtZXQ6XG5cbiAxLiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UsIHRoaXNcbiBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lci5cblxuIDIuIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSxcbiB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyIGluIHRoZSBkb2N1bWVudGF0aW9uXG4gYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZCB3aXRoIHRoZSBkaXN0cmlidXRpb24uXG5cbiBUSElTIFNPRlRXQVJFIElTIFBST1ZJREVEIEJZIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQgQ09OVFJJQlVUT1JTIFwiQVMgSVNcIlxuIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVCBMSU1JVEVEIFRPLCBUSEVcbiBJTVBMSUVEIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZIEFORCBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkVcbiBESVNDTEFJTUVELiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQ09QWVJJR0hUIEhPTERFUiBPUiBDT05UUklCVVRPUlMgQkUgTElBQkxFXG4gRk9SIEFOWSBESVJFQ1QsIElORElSRUNULCBJTkNJREVOVEFMLCBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUxcbiBEQU1BR0VTIChJTkNMVURJTkcsIEJVVCBOT1QgTElNSVRFRCBUTywgUFJPQ1VSRU1FTlQgT0YgU1VCU1RJVFVURSBHT09EUyBPUlxuIFNFUlZJQ0VTOyBMT1NTIE9GIFVTRSwgREFUQSwgT1IgUFJPRklUUzsgT1IgQlVTSU5FU1MgSU5URVJSVVBUSU9OKSBIT1dFVkVSXG4gQ0FVU0VEIEFORCBPTiBBTlkgVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUIExJQUJJTElUWSxcbiBPUiBUT1JUIChJTkNMVURJTkcgTkVHTElHRU5DRSBPUiBPVEhFUldJU0UpIEFSSVNJTkcgSU4gQU5ZIFdBWSBPVVQgT0YgVEhFIFVTRVxuIE9GIFRISVMgU09GVFdBUkUsIEVWRU4gSUYgQURWSVNFRCBPRiBUSEUgUE9TU0lCSUxJVFkgT0YgU1VDSCBEQU1BR0UuXG5cbiAqKiovXG5cbmxldCBpc1JlYWR5ID0gZmFsc2U7XG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgKCkgPT4geyBpc1JlYWR5ID0gdHJ1ZSB9KTtcblxuZXhwb3J0IGZ1bmN0aW9uIHdoZW5SZWFkeShjYWxsYmFjazogKCkgPT4gdm9pZCkge1xuICAgIGlmIChpc1JlYWR5IHx8IGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICdjb21wbGV0ZScpIHtcbiAgICAgICAgY2FsbGJhY2soKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgY2FsbGJhY2spO1xuICAgIH1cbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyLCBvYmplY3ROYW1lc30gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuaW1wb3J0IHR5cGUgeyBTY3JlZW4gfSBmcm9tIFwiLi9zY3JlZW5zLmpzXCI7XG5cbi8vIERyb3AgdGFyZ2V0IGNvbnN0YW50c1xuY29uc3QgRFJPUF9UQVJHRVRfQVRUUklCVVRFID0gJ2RhdGEtZmlsZS1kcm9wLXRhcmdldCc7XG5jb25zdCBEUk9QX1RBUkdFVF9BQ1RJVkVfQ0xBU1MgPSAnZmlsZS1kcm9wLXRhcmdldC1hY3RpdmUnO1xubGV0IGN1cnJlbnREcm9wVGFyZ2V0OiBFbGVtZW50IHwgbnVsbCA9IG51bGw7XG5cbmNvbnN0IFBvc2l0aW9uTWV0aG9kICAgICAgICAgICAgICAgICAgICA9IDA7XG5jb25zdCBDZW50ZXJNZXRob2QgICAgICAgICAgICAgICAgICAgICAgPSAxO1xuY29uc3QgQ2xvc2VNZXRob2QgICAgICAgICAgICAgICAgICAgICAgID0gMjtcbmNvbnN0IERpc2FibGVTaXplQ29uc3RyYWludHNNZXRob2QgICAgICA9IDM7XG5jb25zdCBFbmFibGVTaXplQ29uc3RyYWludHNNZXRob2QgICAgICAgPSA0O1xuY29uc3QgRm9jdXNNZXRob2QgICAgICAgICAgICAgICAgICAgICAgID0gNTtcbmNvbnN0IEZvcmNlUmVsb2FkTWV0aG9kICAgICAgICAgICAgICAgICA9IDY7XG5jb25zdCBGdWxsc2NyZWVuTWV0aG9kICAgICAgICAgICAgICAgICAgPSA3O1xuY29uc3QgR2V0U2NyZWVuTWV0aG9kICAgICAgICAgICAgICAgICAgID0gODtcbmNvbnN0IEdldFpvb21NZXRob2QgICAgICAgICAgICAgICAgICAgICA9IDk7XG5jb25zdCBIZWlnaHRNZXRob2QgICAgICAgICAgICAgICAgICAgICAgPSAxMDtcbmNvbnN0IEhpZGVNZXRob2QgICAgICAgICAgICAgICAgICAgICAgICA9IDExO1xuY29uc3QgSXNGb2N1c2VkTWV0aG9kICAgICAgICAgICAgICAgICAgID0gMTI7XG5jb25zdCBJc0Z1bGxzY3JlZW5NZXRob2QgICAgICAgICAgICAgICAgPSAxMztcbmNvbnN0IElzTWF4aW1pc2VkTWV0aG9kICAgICAgICAgICAgICAgICA9IDE0O1xuY29uc3QgSXNNaW5pbWlzZWRNZXRob2QgICAgICAgICAgICAgICAgID0gMTU7XG5jb25zdCBNYXhpbWlzZU1ldGhvZCAgICAgICAgICAgICAgICAgICAgPSAxNjtcbmNvbnN0IE1pbmltaXNlTWV0aG9kICAgICAgICAgICAgICAgICAgICA9IDE3O1xuY29uc3QgTmFtZU1ldGhvZCAgICAgICAgICAgICAgICAgICAgICAgID0gMTg7XG5jb25zdCBPcGVuRGV2VG9vbHNNZXRob2QgICAgICAgICAgICAgICAgPSAxOTtcbmNvbnN0IFJlbGF0aXZlUG9zaXRpb25NZXRob2QgICAgICAgICAgICA9IDIwO1xuY29uc3QgUmVsb2FkTWV0aG9kICAgICAgICAgICAgICAgICAgICAgID0gMjE7XG5jb25zdCBSZXNpemFibGVNZXRob2QgICAgICAgICAgICAgICAgICAgPSAyMjtcbmNvbnN0IFJlc3RvcmVNZXRob2QgICAgICAgICAgICAgICAgICAgICA9IDIzO1xuY29uc3QgU2V0UG9zaXRpb25NZXRob2QgICAgICAgICAgICAgICAgID0gMjQ7XG5jb25zdCBTZXRBbHdheXNPblRvcE1ldGhvZCAgICAgICAgICAgICAgPSAyNTtcbmNvbnN0IFNldEJhY2tncm91bmRDb2xvdXJNZXRob2QgICAgICAgICA9IDI2O1xuY29uc3QgU2V0RnJhbWVsZXNzTWV0aG9kICAgICAgICAgICAgICAgID0gMjc7XG5jb25zdCBTZXRGdWxsc2NyZWVuQnV0dG9uRW5hYmxlZE1ldGhvZCAgPSAyODtcbmNvbnN0IFNldE1heFNpemVNZXRob2QgICAgICAgICAgICAgICAgICA9IDI5O1xuY29uc3QgU2V0TWluU2l6ZU1ldGhvZCAgICAgICAgICAgICAgICAgID0gMzA7XG5jb25zdCBTZXRSZWxhdGl2ZVBvc2l0aW9uTWV0aG9kICAgICAgICAgPSAzMTtcbmNvbnN0IFNldFJlc2l6YWJsZU1ldGhvZCAgICAgICAgICAgICAgICA9IDMyO1xuY29uc3QgU2V0U2l6ZU1ldGhvZCAgICAgICAgICAgICAgICAgICAgID0gMzM7XG5jb25zdCBTZXRUaXRsZU1ldGhvZCAgICAgICAgICAgICAgICAgICAgPSAzNDtcbmNvbnN0IFNldFpvb21NZXRob2QgICAgICAgICAgICAgICAgICAgICA9IDM1O1xuY29uc3QgU2hvd01ldGhvZCAgICAgICAgICAgICAgICAgICAgICAgID0gMzY7XG5jb25zdCBTaXplTWV0aG9kICAgICAgICAgICAgICAgICAgICAgICAgPSAzNztcbmNvbnN0IFRvZ2dsZUZ1bGxzY3JlZW5NZXRob2QgICAgICAgICAgICA9IDM4O1xuY29uc3QgVG9nZ2xlTWF4aW1pc2VNZXRob2QgICAgICAgICAgICAgID0gMzk7XG5jb25zdCBUb2dnbGVGcmFtZWxlc3NNZXRob2QgICAgICAgICAgICAgPSA0MDsgXG5jb25zdCBVbkZ1bGxzY3JlZW5NZXRob2QgICAgICAgICAgICAgICAgPSA0MTtcbmNvbnN0IFVuTWF4aW1pc2VNZXRob2QgICAgICAgICAgICAgICAgICA9IDQyO1xuY29uc3QgVW5NaW5pbWlzZU1ldGhvZCAgICAgICAgICAgICAgICAgID0gNDM7XG5jb25zdCBXaWR0aE1ldGhvZCAgICAgICAgICAgICAgICAgICAgICAgPSA0NDtcbmNvbnN0IFpvb21NZXRob2QgICAgICAgICAgICAgICAgICAgICAgICA9IDQ1O1xuY29uc3QgWm9vbUluTWV0aG9kICAgICAgICAgICAgICAgICAgICAgID0gNDY7XG5jb25zdCBab29tT3V0TWV0aG9kICAgICAgICAgICAgICAgICAgICAgPSA0NztcbmNvbnN0IFpvb21SZXNldE1ldGhvZCAgICAgICAgICAgICAgICAgICA9IDQ4O1xuY29uc3QgU25hcEFzc2lzdE1ldGhvZCAgICAgICAgICAgICAgICAgID0gNDk7XG5jb25zdCBGaWxlc0Ryb3BwZWQgICAgICAgICAgICAgICAgICAgICAgPSA1MDtcbmNvbnN0IFByaW50TWV0aG9kICAgICAgICAgICAgICAgICAgICAgICA9IDUxO1xuXG4vKipcbiAqIEZpbmRzIHRoZSBuZWFyZXN0IGRyb3AgdGFyZ2V0IGVsZW1lbnQgYnkgd2Fsa2luZyB1cCB0aGUgRE9NIHRyZWUuXG4gKi9cbmZ1bmN0aW9uIGdldERyb3BUYXJnZXRFbGVtZW50KGVsZW1lbnQ6IEVsZW1lbnQgfCBudWxsKTogRWxlbWVudCB8IG51bGwge1xuICAgIGlmICghZWxlbWVudCkge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgcmV0dXJuIGVsZW1lbnQuY2xvc2VzdChgWyR7RFJPUF9UQVJHRVRfQVRUUklCVVRFfV1gKTtcbn1cblxuLyoqXG4gKiBDaGVjayBpZiB3ZSBjYW4gdXNlIFdlYlZpZXcyJ3MgcG9zdE1lc3NhZ2VXaXRoQWRkaXRpb25hbE9iamVjdHMgKFdpbmRvd3MpXG4gKiBBbHNvIGNoZWNrcyB0aGF0IEVuYWJsZUZpbGVEcm9wIGlzIHRydWUgZm9yIHRoaXMgd2luZG93LlxuICovXG5mdW5jdGlvbiBjYW5SZXNvbHZlRmlsZVBhdGhzKCk6IGJvb2xlYW4ge1xuICAgIC8vIE11c3QgaGF2ZSBXZWJWaWV3MidzIHBvc3RNZXNzYWdlV2l0aEFkZGl0aW9uYWxPYmplY3RzIEFQSSAoV2luZG93cyBvbmx5KVxuICAgIGlmICgod2luZG93IGFzIGFueSkuY2hyb21lPy53ZWJ2aWV3Py5wb3N0TWVzc2FnZVdpdGhBZGRpdGlvbmFsT2JqZWN0cyA9PSBudWxsKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgLy8gTXVzdCBoYXZlIEVuYWJsZUZpbGVEcm9wIHNldCB0byB0cnVlIGZvciB0aGlzIHdpbmRvd1xuICAgIC8vIFRoaXMgZmxhZyBpcyBzZXQgYnkgdGhlIEdvIGJhY2tlbmQgZHVyaW5nIHJ1bnRpbWUgaW5pdGlhbGl6YXRpb25cbiAgICByZXR1cm4gKHdpbmRvdyBhcyBhbnkpLl93YWlscz8uZmxhZ3M/LmVuYWJsZUZpbGVEcm9wID09PSB0cnVlO1xufVxuXG4vKipcbiAqIFNlbmQgZmlsZSBkcm9wIHRvIGJhY2tlbmQgdmlhIFdlYlZpZXcyIChXaW5kb3dzIG9ubHkpXG4gKi9cbmZ1bmN0aW9uIHJlc29sdmVGaWxlUGF0aHMoeDogbnVtYmVyLCB5OiBudW1iZXIsIGZpbGVzOiBGaWxlW10pOiB2b2lkIHtcbiAgICBpZiAoKHdpbmRvdyBhcyBhbnkpLmNocm9tZT8ud2Vidmlldz8ucG9zdE1lc3NhZ2VXaXRoQWRkaXRpb25hbE9iamVjdHMpIHtcbiAgICAgICAgKHdpbmRvdyBhcyBhbnkpLmNocm9tZS53ZWJ2aWV3LnBvc3RNZXNzYWdlV2l0aEFkZGl0aW9uYWxPYmplY3RzKGBmaWxlOmRyb3A6JHt4fToke3l9YCwgZmlsZXMpO1xuICAgIH1cbn1cblxuLy8gTmF0aXZlIGRyYWcgc3RhdGUgKExpbnV4L21hY09TIGludGVyY2VwdCBET00gZHJhZyBldmVudHMpXG5sZXQgbmF0aXZlRHJhZ0FjdGl2ZSA9IGZhbHNlO1xuXG4vKipcbiAqIENsZWFucyB1cCBuYXRpdmUgZHJhZyBzdGF0ZSBhbmQgaG92ZXIgZWZmZWN0cy5cbiAqIENhbGxlZCBvbiBkcm9wIG9yIHdoZW4gZHJhZyBsZWF2ZXMgdGhlIHdpbmRvdy5cbiAqL1xuZnVuY3Rpb24gY2xlYW51cE5hdGl2ZURyYWcoKTogdm9pZCB7XG4gICAgbmF0aXZlRHJhZ0FjdGl2ZSA9IGZhbHNlO1xuICAgIGlmIChjdXJyZW50RHJvcFRhcmdldCkge1xuICAgICAgICBjdXJyZW50RHJvcFRhcmdldC5jbGFzc0xpc3QucmVtb3ZlKERST1BfVEFSR0VUX0FDVElWRV9DTEFTUyk7XG4gICAgICAgIGN1cnJlbnREcm9wVGFyZ2V0ID0gbnVsbDtcbiAgICB9XG59XG5cbi8qKlxuICogQ2FsbGVkIGZyb20gR28gd2hlbiBhIGZpbGUgZHJhZyBlbnRlcnMgdGhlIHdpbmRvdyBvbiBMaW51eC9tYWNPUy5cbiAqL1xuZnVuY3Rpb24gaGFuZGxlRHJhZ0VudGVyKCk6IHZvaWQge1xuICAgIC8vIENoZWNrIGlmIGZpbGUgZHJvcHMgYXJlIGVuYWJsZWQgZm9yIHRoaXMgd2luZG93XG4gICAgaWYgKCh3aW5kb3cgYXMgYW55KS5fd2FpbHM/LmZsYWdzPy5lbmFibGVGaWxlRHJvcCA9PT0gZmFsc2UpIHtcbiAgICAgICAgcmV0dXJuOyAvLyBGaWxlIGRyb3BzIGRpc2FibGVkLCBkb24ndCBhY3RpdmF0ZSBkcmFnIHN0YXRlXG4gICAgfVxuICAgIG5hdGl2ZURyYWdBY3RpdmUgPSB0cnVlO1xufVxuXG4vKipcbiAqIENhbGxlZCBmcm9tIEdvIHdoZW4gYSBmaWxlIGRyYWcgbGVhdmVzIHRoZSB3aW5kb3cgb24gTGludXgvbWFjT1MuXG4gKi9cbmZ1bmN0aW9uIGhhbmRsZURyYWdMZWF2ZSgpOiB2b2lkIHtcbiAgICBjbGVhbnVwTmF0aXZlRHJhZygpO1xufVxuXG4vKipcbiAqIENhbGxlZCBmcm9tIEdvIGR1cmluZyBmaWxlIGRyYWcgdG8gdXBkYXRlIGhvdmVyIHN0YXRlIG9uIExpbnV4L21hY09TLlxuICogQHBhcmFtIHggLSBYIGNvb3JkaW5hdGUgaW4gQ1NTIHBpeGVsc1xuICogQHBhcmFtIHkgLSBZIGNvb3JkaW5hdGUgaW4gQ1NTIHBpeGVsc1xuICovXG5mdW5jdGlvbiBoYW5kbGVEcmFnT3Zlcih4OiBudW1iZXIsIHk6IG51bWJlcik6IHZvaWQge1xuICAgIGlmICghbmF0aXZlRHJhZ0FjdGl2ZSkgcmV0dXJuO1xuICAgIFxuICAgIC8vIENoZWNrIGlmIGZpbGUgZHJvcHMgYXJlIGVuYWJsZWQgZm9yIHRoaXMgd2luZG93XG4gICAgaWYgKCh3aW5kb3cgYXMgYW55KS5fd2FpbHM/LmZsYWdzPy5lbmFibGVGaWxlRHJvcCA9PT0gZmFsc2UpIHtcbiAgICAgICAgcmV0dXJuOyAvLyBGaWxlIGRyb3BzIGRpc2FibGVkLCBkb24ndCBzaG93IGhvdmVyIGVmZmVjdHNcbiAgICB9XG4gICAgXG4gICAgY29uc3QgdGFyZ2V0RWxlbWVudCA9IGRvY3VtZW50LmVsZW1lbnRGcm9tUG9pbnQoeCwgeSk7XG4gICAgY29uc3QgZHJvcFRhcmdldCA9IGdldERyb3BUYXJnZXRFbGVtZW50KHRhcmdldEVsZW1lbnQpO1xuICAgIFxuICAgIGlmIChjdXJyZW50RHJvcFRhcmdldCAmJiBjdXJyZW50RHJvcFRhcmdldCAhPT0gZHJvcFRhcmdldCkge1xuICAgICAgICBjdXJyZW50RHJvcFRhcmdldC5jbGFzc0xpc3QucmVtb3ZlKERST1BfVEFSR0VUX0FDVElWRV9DTEFTUyk7XG4gICAgfVxuICAgIFxuICAgIGlmIChkcm9wVGFyZ2V0KSB7XG4gICAgICAgIGRyb3BUYXJnZXQuY2xhc3NMaXN0LmFkZChEUk9QX1RBUkdFVF9BQ1RJVkVfQ0xBU1MpO1xuICAgICAgICBjdXJyZW50RHJvcFRhcmdldCA9IGRyb3BUYXJnZXQ7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgY3VycmVudERyb3BUYXJnZXQgPSBudWxsO1xuICAgIH1cbn1cblxuXG5cbi8vIEV4cG9ydCB0aGUgaGFuZGxlcnMgZm9yIHVzZSBieSBHbyB2aWEgaW5kZXgudHNcbmV4cG9ydCB7IGhhbmRsZURyYWdFbnRlciwgaGFuZGxlRHJhZ0xlYXZlLCBoYW5kbGVEcmFnT3ZlciB9O1xuXG4vKipcbiAqIEEgcmVjb3JkIGRlc2NyaWJpbmcgdGhlIHBvc2l0aW9uIG9mIGEgd2luZG93LlxuICovXG5pbnRlcmZhY2UgUG9zaXRpb24ge1xuICAgIC8qKiBUaGUgaG9yaXpvbnRhbCBwb3NpdGlvbiBvZiB0aGUgd2luZG93LiAqL1xuICAgIHg6IG51bWJlcjtcbiAgICAvKiogVGhlIHZlcnRpY2FsIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3cuICovXG4gICAgeTogbnVtYmVyO1xufVxuXG4vKipcbiAqIEEgcmVjb3JkIGRlc2NyaWJpbmcgdGhlIHNpemUgb2YgYSB3aW5kb3cuXG4gKi9cbmludGVyZmFjZSBTaXplIHtcbiAgICAvKiogVGhlIHdpZHRoIG9mIHRoZSB3aW5kb3cuICovXG4gICAgd2lkdGg6IG51bWJlcjtcbiAgICAvKiogVGhlIGhlaWdodCBvZiB0aGUgd2luZG93LiAqL1xuICAgIGhlaWdodDogbnVtYmVyO1xufVxuXG4vLyBQcml2YXRlIGZpZWxkIG5hbWVzLlxuY29uc3QgY2FsbGVyU3ltID0gU3ltYm9sKFwiY2FsbGVyXCIpO1xuXG5jbGFzcyBXaW5kb3cge1xuICAgIC8vIFByaXZhdGUgZmllbGRzLlxuICAgIHByaXZhdGUgW2NhbGxlclN5bV06IChtZXNzYWdlOiBudW1iZXIsIGFyZ3M/OiBhbnkpID0+IFByb21pc2U8YW55PjtcblxuICAgIC8qKlxuICAgICAqIEluaXRpYWxpc2VzIGEgd2luZG93IG9iamVjdCB3aXRoIHRoZSBzcGVjaWZpZWQgbmFtZS5cbiAgICAgKlxuICAgICAqIEBwcml2YXRlXG4gICAgICogQHBhcmFtIG5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgdGFyZ2V0IHdpbmRvdy5cbiAgICAgKi9cbiAgICBjb25zdHJ1Y3RvcihuYW1lOiBzdHJpbmcgPSAnJykge1xuICAgICAgICB0aGlzW2NhbGxlclN5bV0gPSBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdE5hbWVzLldpbmRvdywgbmFtZSlcblxuICAgICAgICAvLyBiaW5kIGluc3RhbmNlIG1ldGhvZCB0byBtYWtlIHRoZW0gZWFzaWx5IHVzYWJsZSBpbiBldmVudCBoYW5kbGVyc1xuICAgICAgICBmb3IgKGNvbnN0IG1ldGhvZCBvZiBPYmplY3QuZ2V0T3duUHJvcGVydHlOYW1lcyhXaW5kb3cucHJvdG90eXBlKSkge1xuICAgICAgICAgICAgaWYgKFxuICAgICAgICAgICAgICAgIG1ldGhvZCAhPT0gXCJjb25zdHJ1Y3RvclwiXG4gICAgICAgICAgICAgICAgJiYgdHlwZW9mICh0aGlzIGFzIGFueSlbbWV0aG9kXSA9PT0gXCJmdW5jdGlvblwiXG4gICAgICAgICAgICApIHtcbiAgICAgICAgICAgICAgICAodGhpcyBhcyBhbnkpW21ldGhvZF0gPSAodGhpcyBhcyBhbnkpW21ldGhvZF0uYmluZCh0aGlzKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEdldHMgdGhlIHNwZWNpZmllZCB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gbmFtZSAtIFRoZSBuYW1lIG9mIHRoZSB3aW5kb3cgdG8gZ2V0LlxuICAgICAqIEByZXR1cm5zIFRoZSBjb3JyZXNwb25kaW5nIHdpbmRvdyBvYmplY3QuXG4gICAgICovXG4gICAgR2V0KG5hbWU6IHN0cmluZyk6IFdpbmRvdyB7XG4gICAgICAgIHJldHVybiBuZXcgV2luZG93KG5hbWUpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIGFic29sdXRlIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGUgY3VycmVudCBhYnNvbHV0ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFBvc2l0aW9uKCk6IFByb21pc2U8UG9zaXRpb24+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShQb3NpdGlvbk1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ2VudGVycyB0aGUgd2luZG93IG9uIHRoZSBzY3JlZW4uXG4gICAgICovXG4gICAgQ2VudGVyKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKENlbnRlck1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ2xvc2VzIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgQ2xvc2UoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oQ2xvc2VNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIERpc2FibGVzIG1pbi9tYXggc2l6ZSBjb25zdHJhaW50cy5cbiAgICAgKi9cbiAgICBEaXNhYmxlU2l6ZUNvbnN0cmFpbnRzKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKERpc2FibGVTaXplQ29uc3RyYWludHNNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEVuYWJsZXMgbWluL21heCBzaXplIGNvbnN0cmFpbnRzLlxuICAgICAqL1xuICAgIEVuYWJsZVNpemVDb25zdHJhaW50cygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShFbmFibGVTaXplQ29uc3RyYWludHNNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEZvY3VzZXMgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBGb2N1cygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShGb2N1c01ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRm9yY2VzIHRoZSB3aW5kb3cgdG8gcmVsb2FkIHRoZSBwYWdlIGFzc2V0cy5cbiAgICAgKi9cbiAgICBGb3JjZVJlbG9hZCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShGb3JjZVJlbG9hZE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU3dpdGNoZXMgdGhlIHdpbmRvdyB0byBmdWxsc2NyZWVuIG1vZGUuXG4gICAgICovXG4gICAgRnVsbHNjcmVlbigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShGdWxsc2NyZWVuTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBzY3JlZW4gdGhhdCB0aGUgd2luZG93IGlzIG9uLlxuICAgICAqXG4gICAgICogQHJldHVybnMgVGhlIHNjcmVlbiB0aGUgd2luZG93IGlzIGN1cnJlbnRseSBvbi5cbiAgICAgKi9cbiAgICBHZXRTY3JlZW4oKTogUHJvbWlzZTxTY3JlZW4+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShHZXRTY3JlZW5NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIGN1cnJlbnQgem9vbSBsZXZlbCBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHJldHVybnMgVGhlIGN1cnJlbnQgem9vbSBsZXZlbC5cbiAgICAgKi9cbiAgICBHZXRab29tKCk6IFByb21pc2U8bnVtYmVyPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oR2V0Wm9vbU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgaGVpZ2h0IG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGUgY3VycmVudCBoZWlnaHQgb2YgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBIZWlnaHQoKTogUHJvbWlzZTxudW1iZXI+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShIZWlnaHRNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEhpZGVzIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgSGlkZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShIaWRlTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRydWUgaWYgdGhlIHdpbmRvdyBpcyBmb2N1c2VkLlxuICAgICAqXG4gICAgICogQHJldHVybnMgV2hldGhlciB0aGUgd2luZG93IGlzIGN1cnJlbnRseSBmb2N1c2VkLlxuICAgICAqL1xuICAgIElzRm9jdXNlZCgpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShJc0ZvY3VzZWRNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdHJ1ZSBpZiB0aGUgd2luZG93IGlzIGZ1bGxzY3JlZW4uXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBXaGV0aGVyIHRoZSB3aW5kb3cgaXMgY3VycmVudGx5IGZ1bGxzY3JlZW4uXG4gICAgICovXG4gICAgSXNGdWxsc2NyZWVuKCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKElzRnVsbHNjcmVlbk1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0cnVlIGlmIHRoZSB3aW5kb3cgaXMgbWF4aW1pc2VkLlxuICAgICAqXG4gICAgICogQHJldHVybnMgV2hldGhlciB0aGUgd2luZG93IGlzIGN1cnJlbnRseSBtYXhpbWlzZWQuXG4gICAgICovXG4gICAgSXNNYXhpbWlzZWQoKTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oSXNNYXhpbWlzZWRNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdHJ1ZSBpZiB0aGUgd2luZG93IGlzIG1pbmltaXNlZC5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFdoZXRoZXIgdGhlIHdpbmRvdyBpcyBjdXJyZW50bHkgbWluaW1pc2VkLlxuICAgICAqL1xuICAgIElzTWluaW1pc2VkKCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKElzTWluaW1pc2VkTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBNYXhpbWlzZXMgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBNYXhpbWlzZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShNYXhpbWlzZU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogTWluaW1pc2VzIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgTWluaW1pc2UoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oTWluaW1pc2VNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIG5hbWUgb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBuYW1lIG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgTmFtZSgpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKE5hbWVNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIE9wZW5zIHRoZSBkZXZlbG9wbWVudCB0b29scyBwYW5lLlxuICAgICAqL1xuICAgIE9wZW5EZXZUb29scygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShPcGVuRGV2VG9vbHNNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIHJlbGF0aXZlIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3cgdG8gdGhlIHNjcmVlbi5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBjdXJyZW50IHJlbGF0aXZlIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgUmVsYXRpdmVQb3NpdGlvbigpOiBQcm9taXNlPFBvc2l0aW9uPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oUmVsYXRpdmVQb3NpdGlvbk1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVsb2FkcyB0aGUgcGFnZSBhc3NldHMuXG4gICAgICovXG4gICAgUmVsb2FkKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFJlbG9hZE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0cnVlIGlmIHRoZSB3aW5kb3cgaXMgcmVzaXphYmxlLlxuICAgICAqXG4gICAgICogQHJldHVybnMgV2hldGhlciB0aGUgd2luZG93IGlzIGN1cnJlbnRseSByZXNpemFibGUuXG4gICAgICovXG4gICAgUmVzaXphYmxlKCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFJlc2l6YWJsZU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVzdG9yZXMgdGhlIHdpbmRvdyB0byBpdHMgcHJldmlvdXMgc3RhdGUgaWYgaXQgd2FzIHByZXZpb3VzbHkgbWluaW1pc2VkLCBtYXhpbWlzZWQgb3IgZnVsbHNjcmVlbi5cbiAgICAgKi9cbiAgICBSZXN0b3JlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFJlc3RvcmVNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgdGhlIGFic29sdXRlIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcGFyYW0geCAtIFRoZSBkZXNpcmVkIGhvcml6b250YWwgYWJzb2x1dGUgcG9zaXRpb24gb2YgdGhlIHdpbmRvdy5cbiAgICAgKiBAcGFyYW0geSAtIFRoZSBkZXNpcmVkIHZlcnRpY2FsIGFic29sdXRlIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgU2V0UG9zaXRpb24oeDogbnVtYmVyLCB5OiBudW1iZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRQb3NpdGlvbk1ldGhvZCwgeyB4LCB5IH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgdGhlIHdpbmRvdyB0byBiZSBhbHdheXMgb24gdG9wLlxuICAgICAqXG4gICAgICogQHBhcmFtIGFsd2F5c09uVG9wIC0gV2hldGhlciB0aGUgd2luZG93IHNob3VsZCBzdGF5IG9uIHRvcC5cbiAgICAgKi9cbiAgICBTZXRBbHdheXNPblRvcChhbHdheXNPblRvcDogYm9vbGVhbik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldEFsd2F5c09uVG9wTWV0aG9kLCB7IGFsd2F5c09uVG9wIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgdGhlIGJhY2tncm91bmQgY29sb3VyIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gciAtIFRoZSBkZXNpcmVkIHJlZCBjb21wb25lbnQgb2YgdGhlIHdpbmRvdyBiYWNrZ3JvdW5kLlxuICAgICAqIEBwYXJhbSBnIC0gVGhlIGRlc2lyZWQgZ3JlZW4gY29tcG9uZW50IG9mIHRoZSB3aW5kb3cgYmFja2dyb3VuZC5cbiAgICAgKiBAcGFyYW0gYiAtIFRoZSBkZXNpcmVkIGJsdWUgY29tcG9uZW50IG9mIHRoZSB3aW5kb3cgYmFja2dyb3VuZC5cbiAgICAgKiBAcGFyYW0gYSAtIFRoZSBkZXNpcmVkIGFscGhhIGNvbXBvbmVudCBvZiB0aGUgd2luZG93IGJhY2tncm91bmQuXG4gICAgICovXG4gICAgU2V0QmFja2dyb3VuZENvbG91cihyOiBudW1iZXIsIGc6IG51bWJlciwgYjogbnVtYmVyLCBhOiBudW1iZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRCYWNrZ3JvdW5kQ29sb3VyTWV0aG9kLCB7IHIsIGcsIGIsIGEgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVtb3ZlcyB0aGUgd2luZG93IGZyYW1lIGFuZCB0aXRsZSBiYXIuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gZnJhbWVsZXNzIC0gV2hldGhlciB0aGUgd2luZG93IHNob3VsZCBiZSBmcmFtZWxlc3MuXG4gICAgICovXG4gICAgU2V0RnJhbWVsZXNzKGZyYW1lbGVzczogYm9vbGVhbik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldEZyYW1lbGVzc01ldGhvZCwgeyBmcmFtZWxlc3MgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRGlzYWJsZXMgdGhlIHN5c3RlbSBmdWxsc2NyZWVuIGJ1dHRvbi5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBlbmFibGVkIC0gV2hldGhlciB0aGUgZnVsbHNjcmVlbiBidXR0b24gc2hvdWxkIGJlIGVuYWJsZWQuXG4gICAgICovXG4gICAgU2V0RnVsbHNjcmVlbkJ1dHRvbkVuYWJsZWQoZW5hYmxlZDogYm9vbGVhbik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldEZ1bGxzY3JlZW5CdXR0b25FbmFibGVkTWV0aG9kLCB7IGVuYWJsZWQgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0cyB0aGUgbWF4aW11bSBzaXplIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gd2lkdGggLSBUaGUgZGVzaXJlZCBtYXhpbXVtIHdpZHRoIG9mIHRoZSB3aW5kb3cuXG4gICAgICogQHBhcmFtIGhlaWdodCAtIFRoZSBkZXNpcmVkIG1heGltdW0gaGVpZ2h0IG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgU2V0TWF4U2l6ZSh3aWR0aDogbnVtYmVyLCBoZWlnaHQ6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldE1heFNpemVNZXRob2QsIHsgd2lkdGgsIGhlaWdodCB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSBtaW5pbXVtIHNpemUgb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSB3aWR0aCAtIFRoZSBkZXNpcmVkIG1pbmltdW0gd2lkdGggb2YgdGhlIHdpbmRvdy5cbiAgICAgKiBAcGFyYW0gaGVpZ2h0IC0gVGhlIGRlc2lyZWQgbWluaW11bSBoZWlnaHQgb2YgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBTZXRNaW5TaXplKHdpZHRoOiBudW1iZXIsIGhlaWdodDogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0TWluU2l6ZU1ldGhvZCwgeyB3aWR0aCwgaGVpZ2h0IH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgdGhlIHJlbGF0aXZlIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3cgdG8gdGhlIHNjcmVlbi5cbiAgICAgKlxuICAgICAqIEBwYXJhbSB4IC0gVGhlIGRlc2lyZWQgaG9yaXpvbnRhbCByZWxhdGl2ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93LlxuICAgICAqIEBwYXJhbSB5IC0gVGhlIGRlc2lyZWQgdmVydGljYWwgcmVsYXRpdmUgcG9zaXRpb24gb2YgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBTZXRSZWxhdGl2ZVBvc2l0aW9uKHg6IG51bWJlciwgeTogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0UmVsYXRpdmVQb3NpdGlvbk1ldGhvZCwgeyB4LCB5IH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgd2hldGhlciB0aGUgd2luZG93IGlzIHJlc2l6YWJsZS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSByZXNpemFibGUgLSBXaGV0aGVyIHRoZSB3aW5kb3cgc2hvdWxkIGJlIHJlc2l6YWJsZS5cbiAgICAgKi9cbiAgICBTZXRSZXNpemFibGUocmVzaXphYmxlOiBib29sZWFuKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0UmVzaXphYmxlTWV0aG9kLCB7IHJlc2l6YWJsZSB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSBzaXplIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gd2lkdGggLSBUaGUgZGVzaXJlZCB3aWR0aCBvZiB0aGUgd2luZG93LlxuICAgICAqIEBwYXJhbSBoZWlnaHQgLSBUaGUgZGVzaXJlZCBoZWlnaHQgb2YgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBTZXRTaXplKHdpZHRoOiBudW1iZXIsIGhlaWdodDogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0U2l6ZU1ldGhvZCwgeyB3aWR0aCwgaGVpZ2h0IH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgdGhlIHRpdGxlIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gdGl0bGUgLSBUaGUgZGVzaXJlZCB0aXRsZSBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFNldFRpdGxlKHRpdGxlOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRUaXRsZU1ldGhvZCwgeyB0aXRsZSB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSB6b29tIGxldmVsIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gem9vbSAtIFRoZSBkZXNpcmVkIHpvb20gbGV2ZWwuXG4gICAgICovXG4gICAgU2V0Wm9vbSh6b29tOiBudW1iZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRab29tTWV0aG9kLCB7IHpvb20gfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2hvd3MgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBTaG93KCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNob3dNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIHNpemUgb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBjdXJyZW50IHNpemUgb2YgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBTaXplKCk6IFByb21pc2U8U2l6ZT4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNpemVNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFRvZ2dsZXMgdGhlIHdpbmRvdyBiZXR3ZWVuIGZ1bGxzY3JlZW4gYW5kIG5vcm1hbC5cbiAgICAgKi9cbiAgICBUb2dnbGVGdWxsc2NyZWVuKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFRvZ2dsZUZ1bGxzY3JlZW5NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFRvZ2dsZXMgdGhlIHdpbmRvdyBiZXR3ZWVuIG1heGltaXNlZCBhbmQgbm9ybWFsLlxuICAgICAqL1xuICAgIFRvZ2dsZU1heGltaXNlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFRvZ2dsZU1heGltaXNlTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBUb2dnbGVzIHRoZSB3aW5kb3cgYmV0d2VlbiBmcmFtZWxlc3MgYW5kIG5vcm1hbC5cbiAgICAgKi9cbiAgICBUb2dnbGVGcmFtZWxlc3MoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oVG9nZ2xlRnJhbWVsZXNzTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBVbi1mdWxsc2NyZWVucyB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFVuRnVsbHNjcmVlbigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShVbkZ1bGxzY3JlZW5NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFVuLW1heGltaXNlcyB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFVuTWF4aW1pc2UoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oVW5NYXhpbWlzZU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVW4tbWluaW1pc2VzIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgVW5NaW5pbWlzZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShVbk1pbmltaXNlTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSB3aWR0aCBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHJldHVybnMgVGhlIGN1cnJlbnQgd2lkdGggb2YgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBXaWR0aCgpOiBQcm9taXNlPG51bWJlcj4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFdpZHRoTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBab29tcyB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFpvb20oKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oWm9vbU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogSW5jcmVhc2VzIHRoZSB6b29tIGxldmVsIG9mIHRoZSB3ZWJ2aWV3IGNvbnRlbnQuXG4gICAgICovXG4gICAgWm9vbUluKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFpvb21Jbk1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRGVjcmVhc2VzIHRoZSB6b29tIGxldmVsIG9mIHRoZSB3ZWJ2aWV3IGNvbnRlbnQuXG4gICAgICovXG4gICAgWm9vbU91dCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShab29tT3V0TWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXNldHMgdGhlIHpvb20gbGV2ZWwgb2YgdGhlIHdlYnZpZXcgY29udGVudC5cbiAgICAgKi9cbiAgICBab29tUmVzZXQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oWm9vbVJlc2V0TWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBIYW5kbGVzIGZpbGUgZHJvcHMgb3JpZ2luYXRpbmcgZnJvbSBwbGF0Zm9ybS1zcGVjaWZpYyBjb2RlIChlLmcuLCBtYWNPUy9MaW51eCBuYXRpdmUgZHJhZy1hbmQtZHJvcCkuXG4gICAgICogR2F0aGVycyBpbmZvcm1hdGlvbiBhYm91dCB0aGUgZHJvcCB0YXJnZXQgZWxlbWVudCBhbmQgc2VuZHMgaXQgYmFjayB0byB0aGUgR28gYmFja2VuZC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBmaWxlbmFtZXMgLSBBbiBhcnJheSBvZiBmaWxlIHBhdGhzIChzdHJpbmdzKSB0aGF0IHdlcmUgZHJvcHBlZC5cbiAgICAgKiBAcGFyYW0geCAtIFRoZSB4LWNvb3JkaW5hdGUgb2YgdGhlIGRyb3AgZXZlbnQgKENTUyBwaXhlbHMpLlxuICAgICAqIEBwYXJhbSB5IC0gVGhlIHktY29vcmRpbmF0ZSBvZiB0aGUgZHJvcCBldmVudCAoQ1NTIHBpeGVscykuXG4gICAgICovXG4gICAgSGFuZGxlUGxhdGZvcm1GaWxlRHJvcChmaWxlbmFtZXM6IHN0cmluZ1tdLCB4OiBudW1iZXIsIHk6IG51bWJlcik6IHZvaWQge1xuICAgICAgICAvLyBDaGVjayBpZiBmaWxlIGRyb3BzIGFyZSBlbmFibGVkIGZvciB0aGlzIHdpbmRvd1xuICAgICAgICBpZiAoKHdpbmRvdyBhcyBhbnkpLl93YWlscz8uZmxhZ3M/LmVuYWJsZUZpbGVEcm9wID09PSBmYWxzZSkge1xuICAgICAgICAgICAgcmV0dXJuOyAvLyBGaWxlIGRyb3BzIGRpc2FibGVkLCBpZ25vcmUgdGhlIGRyb3BcbiAgICAgICAgfVxuICAgICAgICBcbiAgICAgICAgY29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmVsZW1lbnRGcm9tUG9pbnQoeCwgeSk7XG4gICAgICAgIGNvbnN0IGRyb3BUYXJnZXQgPSBnZXREcm9wVGFyZ2V0RWxlbWVudChlbGVtZW50KTtcblxuICAgICAgICBpZiAoIWRyb3BUYXJnZXQpIHtcbiAgICAgICAgICAgIC8vIERyb3Agd2FzIG5vdCBvbiBhIGRlc2lnbmF0ZWQgZHJvcCB0YXJnZXQgLSBpZ25vcmVcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IGVsZW1lbnREZXRhaWxzID0ge1xuICAgICAgICAgICAgaWQ6IGRyb3BUYXJnZXQuaWQsXG4gICAgICAgICAgICBjbGFzc0xpc3Q6IEFycmF5LmZyb20oZHJvcFRhcmdldC5jbGFzc0xpc3QpLFxuICAgICAgICAgICAgYXR0cmlidXRlczoge30gYXMgeyBba2V5OiBzdHJpbmddOiBzdHJpbmcgfSxcbiAgICAgICAgfTtcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBkcm9wVGFyZ2V0LmF0dHJpYnV0ZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIGNvbnN0IGF0dHIgPSBkcm9wVGFyZ2V0LmF0dHJpYnV0ZXNbaV07XG4gICAgICAgICAgICBlbGVtZW50RGV0YWlscy5hdHRyaWJ1dGVzW2F0dHIubmFtZV0gPSBhdHRyLnZhbHVlO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgICAgICAgIGZpbGVuYW1lcyxcbiAgICAgICAgICAgIHgsXG4gICAgICAgICAgICB5LFxuICAgICAgICAgICAgZWxlbWVudERldGFpbHMsXG4gICAgICAgIH07XG5cbiAgICAgICAgdGhpc1tjYWxsZXJTeW1dKEZpbGVzRHJvcHBlZCwgcGF5bG9hZCk7XG4gICAgICAgIFxuICAgICAgICAvLyBDbGVhbiB1cCBuYXRpdmUgZHJhZyBzdGF0ZSBhZnRlciBkcm9wXG4gICAgICAgIGNsZWFudXBOYXRpdmVEcmFnKCk7XG4gICAgfVxuICBcbiAgICAvKiBUcmlnZ2VycyBXaW5kb3dzIDExIFNuYXAgQXNzaXN0IGZlYXR1cmUgKFdpbmRvd3Mgb25seSkuXG4gICAgICogVGhpcyBpcyBlcXVpdmFsZW50IHRvIHByZXNzaW5nIFdpbitaIGFuZCBzaG93cyBzbmFwIGxheW91dCBvcHRpb25zLlxuICAgICAqL1xuICAgIFNuYXBBc3Npc3QoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU25hcEFzc2lzdE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogT3BlbnMgdGhlIHByaW50IGRpYWxvZyBmb3IgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBQcmludCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShQcmludE1ldGhvZCk7XG4gICAgfVxufVxuXG4vKipcbiAqIFRoZSB3aW5kb3cgd2l0aGluIHdoaWNoIHRoZSBzY3JpcHQgaXMgcnVubmluZy5cbiAqL1xuY29uc3QgdGhpc1dpbmRvdyA9IG5ldyBXaW5kb3coJycpO1xuXG4vKipcbiAqIFNldHMgdXAgZ2xvYmFsIGRyYWcgYW5kIGRyb3AgZXZlbnQgbGlzdGVuZXJzIGZvciBmaWxlIGRyb3BzLlxuICogSGFuZGxlcyB2aXN1YWwgZmVlZGJhY2sgKGhvdmVyIHN0YXRlKSBhbmQgZmlsZSBkcm9wIHByb2Nlc3NpbmcuXG4gKi9cbmZ1bmN0aW9uIHNldHVwRHJvcFRhcmdldExpc3RlbmVycygpIHtcbiAgICBjb25zdCBkb2NFbGVtZW50ID0gZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50O1xuICAgIGxldCBkcmFnRW50ZXJDb3VudGVyID0gMDtcblxuICAgIGRvY0VsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignZHJhZ2VudGVyJywgKGV2ZW50KSA9PiB7XG4gICAgICAgIGlmICghZXZlbnQuZGF0YVRyYW5zZmVyPy50eXBlcy5pbmNsdWRlcygnRmlsZXMnKSkge1xuICAgICAgICAgICAgcmV0dXJuOyAvLyBPbmx5IGhhbmRsZSBmaWxlIGRyYWdzLCBsZXQgb3RoZXIgZHJhZ3MgcGFzcyB0aHJvdWdoXG4gICAgICAgIH1cbiAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTsgLy8gQWx3YXlzIHByZXZlbnQgZGVmYXVsdCB0byBzdG9wIGJyb3dzZXIgbmF2aWdhdGlvblxuICAgICAgICAvLyBPbiBXaW5kb3dzLCBjaGVjayBpZiBmaWxlIGRyb3BzIGFyZSBlbmFibGVkIGZvciB0aGlzIHdpbmRvd1xuICAgICAgICBpZiAoKHdpbmRvdyBhcyBhbnkpLl93YWlscz8uZmxhZ3M/LmVuYWJsZUZpbGVEcm9wID09PSBmYWxzZSkge1xuICAgICAgICAgICAgZXZlbnQuZGF0YVRyYW5zZmVyLmRyb3BFZmZlY3QgPSAnbm9uZSc7IC8vIFNob3cgXCJubyBkcm9wXCIgY3Vyc29yXG4gICAgICAgICAgICByZXR1cm47IC8vIEZpbGUgZHJvcHMgZGlzYWJsZWQsIGRvbid0IHNob3cgaG92ZXIgZWZmZWN0c1xuICAgICAgICB9XG4gICAgICAgIGRyYWdFbnRlckNvdW50ZXIrKztcbiAgICAgICAgXG4gICAgICAgIGNvbnN0IHRhcmdldEVsZW1lbnQgPSBkb2N1bWVudC5lbGVtZW50RnJvbVBvaW50KGV2ZW50LmNsaWVudFgsIGV2ZW50LmNsaWVudFkpO1xuICAgICAgICBjb25zdCBkcm9wVGFyZ2V0ID0gZ2V0RHJvcFRhcmdldEVsZW1lbnQodGFyZ2V0RWxlbWVudCk7XG5cbiAgICAgICAgLy8gVXBkYXRlIGhvdmVyIHN0YXRlXG4gICAgICAgIGlmIChjdXJyZW50RHJvcFRhcmdldCAmJiBjdXJyZW50RHJvcFRhcmdldCAhPT0gZHJvcFRhcmdldCkge1xuICAgICAgICAgICAgY3VycmVudERyb3BUYXJnZXQuY2xhc3NMaXN0LnJlbW92ZShEUk9QX1RBUkdFVF9BQ1RJVkVfQ0xBU1MpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGRyb3BUYXJnZXQpIHtcbiAgICAgICAgICAgIGRyb3BUYXJnZXQuY2xhc3NMaXN0LmFkZChEUk9QX1RBUkdFVF9BQ1RJVkVfQ0xBU1MpO1xuICAgICAgICAgICAgZXZlbnQuZGF0YVRyYW5zZmVyLmRyb3BFZmZlY3QgPSAnY29weSc7XG4gICAgICAgICAgICBjdXJyZW50RHJvcFRhcmdldCA9IGRyb3BUYXJnZXQ7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBldmVudC5kYXRhVHJhbnNmZXIuZHJvcEVmZmVjdCA9ICdub25lJztcbiAgICAgICAgICAgIGN1cnJlbnREcm9wVGFyZ2V0ID0gbnVsbDtcbiAgICAgICAgfVxuICAgIH0sIGZhbHNlKTtcblxuICAgIGRvY0VsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignZHJhZ292ZXInLCAoZXZlbnQpID0+IHtcbiAgICAgICAgaWYgKCFldmVudC5kYXRhVHJhbnNmZXI/LnR5cGVzLmluY2x1ZGVzKCdGaWxlcycpKSB7XG4gICAgICAgICAgICByZXR1cm47IC8vIE9ubHkgaGFuZGxlIGZpbGUgZHJhZ3NcbiAgICAgICAgfVxuICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpOyAvLyBBbHdheXMgcHJldmVudCBkZWZhdWx0IHRvIHN0b3AgYnJvd3NlciBuYXZpZ2F0aW9uXG4gICAgICAgIC8vIE9uIFdpbmRvd3MsIGNoZWNrIGlmIGZpbGUgZHJvcHMgYXJlIGVuYWJsZWQgZm9yIHRoaXMgd2luZG93XG4gICAgICAgIGlmICgod2luZG93IGFzIGFueSkuX3dhaWxzPy5mbGFncz8uZW5hYmxlRmlsZURyb3AgPT09IGZhbHNlKSB7XG4gICAgICAgICAgICBldmVudC5kYXRhVHJhbnNmZXIuZHJvcEVmZmVjdCA9ICdub25lJzsgLy8gU2hvdyBcIm5vIGRyb3BcIiBjdXJzb3JcbiAgICAgICAgICAgIHJldHVybjsgLy8gRmlsZSBkcm9wcyBkaXNhYmxlZCwgZG9uJ3Qgc2hvdyBob3ZlciBlZmZlY3RzXG4gICAgICAgIH1cbiAgICAgICAgXG4gICAgICAgIC8vIFVwZGF0ZSBkcm9wIHRhcmdldCBhcyBjdXJzb3IgbW92ZXNcbiAgICAgICAgY29uc3QgdGFyZ2V0RWxlbWVudCA9IGRvY3VtZW50LmVsZW1lbnRGcm9tUG9pbnQoZXZlbnQuY2xpZW50WCwgZXZlbnQuY2xpZW50WSk7XG4gICAgICAgIGNvbnN0IGRyb3BUYXJnZXQgPSBnZXREcm9wVGFyZ2V0RWxlbWVudCh0YXJnZXRFbGVtZW50KTtcbiAgICAgICAgXG4gICAgICAgIGlmIChjdXJyZW50RHJvcFRhcmdldCAmJiBjdXJyZW50RHJvcFRhcmdldCAhPT0gZHJvcFRhcmdldCkge1xuICAgICAgICAgICAgY3VycmVudERyb3BUYXJnZXQuY2xhc3NMaXN0LnJlbW92ZShEUk9QX1RBUkdFVF9BQ1RJVkVfQ0xBU1MpO1xuICAgICAgICB9XG4gICAgICAgIFxuICAgICAgICBpZiAoZHJvcFRhcmdldCkge1xuICAgICAgICAgICAgaWYgKCFkcm9wVGFyZ2V0LmNsYXNzTGlzdC5jb250YWlucyhEUk9QX1RBUkdFVF9BQ1RJVkVfQ0xBU1MpKSB7XG4gICAgICAgICAgICAgICAgZHJvcFRhcmdldC5jbGFzc0xpc3QuYWRkKERST1BfVEFSR0VUX0FDVElWRV9DTEFTUyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBldmVudC5kYXRhVHJhbnNmZXIuZHJvcEVmZmVjdCA9ICdjb3B5JztcbiAgICAgICAgICAgIGN1cnJlbnREcm9wVGFyZ2V0ID0gZHJvcFRhcmdldDtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGV2ZW50LmRhdGFUcmFuc2Zlci5kcm9wRWZmZWN0ID0gJ25vbmUnO1xuICAgICAgICAgICAgY3VycmVudERyb3BUYXJnZXQgPSBudWxsO1xuICAgICAgICB9XG4gICAgfSwgZmFsc2UpO1xuXG4gICAgZG9jRWxlbWVudC5hZGRFdmVudExpc3RlbmVyKCdkcmFnbGVhdmUnLCAoZXZlbnQpID0+IHtcbiAgICAgICAgaWYgKCFldmVudC5kYXRhVHJhbnNmZXI/LnR5cGVzLmluY2x1ZGVzKCdGaWxlcycpKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTsgLy8gQWx3YXlzIHByZXZlbnQgZGVmYXVsdCB0byBzdG9wIGJyb3dzZXIgbmF2aWdhdGlvblxuICAgICAgICAvLyBPbiBXaW5kb3dzLCBjaGVjayBpZiBmaWxlIGRyb3BzIGFyZSBlbmFibGVkIGZvciB0aGlzIHdpbmRvd1xuICAgICAgICBpZiAoKHdpbmRvdyBhcyBhbnkpLl93YWlscz8uZmxhZ3M/LmVuYWJsZUZpbGVEcm9wID09PSBmYWxzZSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIFxuICAgICAgICAvLyBPbiBMaW51eC9XZWJLaXRHVEsgYW5kIG1hY09TLCBkcmFnbGVhdmUgZmlyZXMgaW1tZWRpYXRlbHkgd2l0aCByZWxhdGVkVGFyZ2V0PW51bGwgd2hlbiBuYXRpdmVcbiAgICAgICAgLy8gZHJhZyBoYW5kbGluZyBpcyBpbnZvbHZlZC4gSWdub3JlIHRoZXNlIHNwdXJpb3VzIGV2ZW50cyAtIHdlJ2xsIGNsZWFuIHVwIG9uIGRyb3AgaW5zdGVhZC5cbiAgICAgICAgaWYgKGV2ZW50LnJlbGF0ZWRUYXJnZXQgPT09IG51bGwpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBcbiAgICAgICAgZHJhZ0VudGVyQ291bnRlci0tO1xuICAgICAgICBcbiAgICAgICAgaWYgKGRyYWdFbnRlckNvdW50ZXIgPT09IDAgfHwgXG4gICAgICAgICAgICAoY3VycmVudERyb3BUYXJnZXQgJiYgIWN1cnJlbnREcm9wVGFyZ2V0LmNvbnRhaW5zKGV2ZW50LnJlbGF0ZWRUYXJnZXQgYXMgTm9kZSkpKSB7XG4gICAgICAgICAgICBpZiAoY3VycmVudERyb3BUYXJnZXQpIHtcbiAgICAgICAgICAgICAgICBjdXJyZW50RHJvcFRhcmdldC5jbGFzc0xpc3QucmVtb3ZlKERST1BfVEFSR0VUX0FDVElWRV9DTEFTUyk7XG4gICAgICAgICAgICAgICAgY3VycmVudERyb3BUYXJnZXQgPSBudWxsO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZHJhZ0VudGVyQ291bnRlciA9IDA7XG4gICAgICAgIH1cbiAgICB9LCBmYWxzZSk7XG5cbiAgICBkb2NFbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2Ryb3AnLCAoZXZlbnQpID0+IHtcbiAgICAgICAgaWYgKCFldmVudC5kYXRhVHJhbnNmZXI/LnR5cGVzLmluY2x1ZGVzKCdGaWxlcycpKSB7XG4gICAgICAgICAgICByZXR1cm47IC8vIE9ubHkgaGFuZGxlIGZpbGUgZHJvcHNcbiAgICAgICAgfVxuICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpOyAvLyBBbHdheXMgcHJldmVudCBkZWZhdWx0IHRvIHN0b3AgYnJvd3NlciBuYXZpZ2F0aW9uXG4gICAgICAgIC8vIE9uIFdpbmRvd3MsIGNoZWNrIGlmIGZpbGUgZHJvcHMgYXJlIGVuYWJsZWQgZm9yIHRoaXMgd2luZG93XG4gICAgICAgIGlmICgod2luZG93IGFzIGFueSkuX3dhaWxzPy5mbGFncz8uZW5hYmxlRmlsZURyb3AgPT09IGZhbHNlKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgZHJhZ0VudGVyQ291bnRlciA9IDA7XG4gICAgICAgIFxuICAgICAgICBpZiAoY3VycmVudERyb3BUYXJnZXQpIHtcbiAgICAgICAgICAgIGN1cnJlbnREcm9wVGFyZ2V0LmNsYXNzTGlzdC5yZW1vdmUoRFJPUF9UQVJHRVRfQUNUSVZFX0NMQVNTKTtcbiAgICAgICAgICAgIGN1cnJlbnREcm9wVGFyZ2V0ID0gbnVsbDtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIE9uIFdpbmRvd3MsIGhhbmRsZSBmaWxlIGRyb3BzIHZpYSBKYXZhU2NyaXB0XG4gICAgICAgIC8vIE9uIG1hY09TL0xpbnV4LCBuYXRpdmUgY29kZSB3aWxsIGNhbGwgSGFuZGxlUGxhdGZvcm1GaWxlRHJvcFxuICAgICAgICBpZiAoY2FuUmVzb2x2ZUZpbGVQYXRocygpKSB7XG4gICAgICAgICAgICBjb25zdCBmaWxlczogRmlsZVtdID0gW107XG4gICAgICAgICAgICBpZiAoZXZlbnQuZGF0YVRyYW5zZmVyLml0ZW1zKSB7XG4gICAgICAgICAgICAgICAgZm9yIChjb25zdCBpdGVtIG9mIGV2ZW50LmRhdGFUcmFuc2Zlci5pdGVtcykge1xuICAgICAgICAgICAgICAgICAgICBpZiAoaXRlbS5raW5kID09PSAnZmlsZScpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IGZpbGUgPSBpdGVtLmdldEFzRmlsZSgpO1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGZpbGUpIGZpbGVzLnB1c2goZmlsZSk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9IGVsc2UgaWYgKGV2ZW50LmRhdGFUcmFuc2Zlci5maWxlcykge1xuICAgICAgICAgICAgICAgIGZvciAoY29uc3QgZmlsZSBvZiBldmVudC5kYXRhVHJhbnNmZXIuZmlsZXMpIHtcbiAgICAgICAgICAgICAgICAgICAgZmlsZXMucHVzaChmaWxlKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBcbiAgICAgICAgICAgIGlmIChmaWxlcy5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZUZpbGVQYXRocyhldmVudC5jbGllbnRYLCBldmVudC5jbGllbnRZLCBmaWxlcyk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9LCBmYWxzZSk7XG59XG5cbi8vIEluaXRpYWxpemUgbGlzdGVuZXJzIHdoZW4gdGhlIHNjcmlwdCBsb2Fkc1xuaWYgKHR5cGVvZiB3aW5kb3cgIT09IFwidW5kZWZpbmVkXCIgJiYgdHlwZW9mIGRvY3VtZW50ICE9PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgc2V0dXBEcm9wVGFyZ2V0TGlzdGVuZXJzKCk7XG59XG5cbmV4cG9ydCBkZWZhdWx0IHRoaXNXaW5kb3c7XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCAqIGFzIFJ1bnRpbWUgZnJvbSBcIi4uL0B3YWlsc2lvL3J1bnRpbWUvc3JjXCI7XG5cbi8vIE5PVEU6IHRoZSBmb2xsb3dpbmcgbWV0aG9kcyBNVVNUIGJlIGltcG9ydGVkIGV4cGxpY2l0bHkgYmVjYXVzZSBvZiBob3cgZXNidWlsZCBpbmplY3Rpb24gd29ya3NcbmltcG9ydCB7IEVuYWJsZSBhcyBFbmFibGVXTUwgfSBmcm9tIFwiLi4vQHdhaWxzaW8vcnVudGltZS9zcmMvd21sXCI7XG5pbXBvcnQgeyBkZWJ1Z0xvZyB9IGZyb20gXCIuLi9Ad2FpbHNpby9ydW50aW1lL3NyYy91dGlsc1wiO1xuXG53aW5kb3cud2FpbHMgPSBSdW50aW1lO1xuRW5hYmxlV01MKCk7XG5cbmlmIChERUJVRykge1xuICAgIGRlYnVnTG9nKFwiV2FpbHMgUnVudGltZSBMb2FkZWRcIilcbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHsgbmV3UnVudGltZUNhbGxlciwgb2JqZWN0TmFtZXMgfSBmcm9tIFwiLi9ydW50aW1lLmpzXCI7XG5cbmNvbnN0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdE5hbWVzLlN5c3RlbSk7XG5cbmNvbnN0IFN5c3RlbUlzRGFya01vZGUgPSAwO1xuY29uc3QgU3lzdGVtRW52aXJvbm1lbnQgPSAxO1xuY29uc3QgU3lzdGVtQ2FwYWJpbGl0aWVzID0gMjtcblxuY29uc3QgX2ludm9rZSA9IChmdW5jdGlvbiAoKSB7XG4gICAgdHJ5IHtcbiAgICAgICAgLy8gV2luZG93cyBXZWJWaWV3MlxuICAgICAgICBpZiAoKHdpbmRvdyBhcyBhbnkpLmNocm9tZT8ud2Vidmlldz8ucG9zdE1lc3NhZ2UpIHtcbiAgICAgICAgICAgIHJldHVybiAod2luZG93IGFzIGFueSkuY2hyb21lLndlYnZpZXcucG9zdE1lc3NhZ2UuYmluZCgod2luZG93IGFzIGFueSkuY2hyb21lLndlYnZpZXcpO1xuICAgICAgICB9XG4gICAgICAgIC8vIG1hY09TL2lPUyBXS1dlYlZpZXdcbiAgICAgICAgZWxzZSBpZiAoKHdpbmRvdyBhcyBhbnkpLndlYmtpdD8ubWVzc2FnZUhhbmRsZXJzPy5bJ2V4dGVybmFsJ10/LnBvc3RNZXNzYWdlKSB7XG4gICAgICAgICAgICByZXR1cm4gKHdpbmRvdyBhcyBhbnkpLndlYmtpdC5tZXNzYWdlSGFuZGxlcnNbJ2V4dGVybmFsJ10ucG9zdE1lc3NhZ2UuYmluZCgod2luZG93IGFzIGFueSkud2Via2l0Lm1lc3NhZ2VIYW5kbGVyc1snZXh0ZXJuYWwnXSk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gQW5kcm9pZCBXZWJWaWV3IC0gdXNlcyBhZGRKYXZhc2NyaXB0SW50ZXJmYWNlIHdoaWNoIGV4cG9zZXMgd2luZG93LndhaWxzLmludm9rZVxuICAgICAgICBlbHNlIGlmICgod2luZG93IGFzIGFueSkud2FpbHM/Lmludm9rZSkge1xuICAgICAgICAgICAgcmV0dXJuIChtc2c6IGFueSkgPT4gKHdpbmRvdyBhcyBhbnkpLndhaWxzLmludm9rZSh0eXBlb2YgbXNnID09PSAnc3RyaW5nJyA/IG1zZyA6IEpTT04uc3RyaW5naWZ5KG1zZykpO1xuICAgICAgICB9XG4gICAgfSBjYXRjaChlKSB7fVxuXG4gICAgY29uc29sZS53YXJuKCdcXG4lY1x1MjZBMFx1RkUwRiBCcm93c2VyIEVudmlyb25tZW50IERldGVjdGVkICVjXFxuXFxuJWNPbmx5IFVJIHByZXZpZXdzIGFyZSBhdmFpbGFibGUgaW4gdGhlIGJyb3dzZXIuIEZvciBmdWxsIGZ1bmN0aW9uYWxpdHksIHBsZWFzZSBydW4gdGhlIGFwcGxpY2F0aW9uIGluIGRlc2t0b3AgbW9kZS5cXG5Nb3JlIGluZm9ybWF0aW9uIGF0OiBodHRwczovL3YzLndhaWxzLmlvL2xlYXJuL2J1aWxkLyN1c2luZy1hLWJyb3dzZXItZm9yLWRldmVsb3BtZW50XFxuJyxcbiAgICAgICAgJ2JhY2tncm91bmQ6ICNmZmZmZmY7IGNvbG9yOiAjMDAwMDAwOyBmb250LXdlaWdodDogYm9sZDsgcGFkZGluZzogNHB4IDhweDsgYm9yZGVyLXJhZGl1czogNHB4OyBib3JkZXI6IDJweCBzb2xpZCAjMDAwMDAwOycsXG4gICAgICAgICdiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDsnLFxuICAgICAgICAnY29sb3I6ICNmZmZmZmY7IGZvbnQtc3R5bGU6IGl0YWxpYzsgZm9udC13ZWlnaHQ6IGJvbGQ7Jyk7XG4gICAgcmV0dXJuIG51bGw7XG59KSgpO1xuXG5leHBvcnQgZnVuY3Rpb24gaW52b2tlKG1zZzogYW55KTogdm9pZCB7XG4gICAgX2ludm9rZT8uKG1zZyk7XG59XG5cbi8qKlxuICogUmV0cmlldmVzIHRoZSBzeXN0ZW0gZGFyayBtb2RlIHN0YXR1cy5cbiAqXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB0byBhIGJvb2xlYW4gdmFsdWUgaW5kaWNhdGluZyBpZiB0aGUgc3lzdGVtIGlzIGluIGRhcmsgbW9kZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzRGFya01vZGUoKTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgcmV0dXJuIGNhbGwoU3lzdGVtSXNEYXJrTW9kZSk7XG59XG5cbi8qKlxuICogRmV0Y2hlcyB0aGUgY2FwYWJpbGl0aWVzIG9mIHRoZSBhcHBsaWNhdGlvbiBmcm9tIHRoZSBzZXJ2ZXIuXG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgdG8gYW4gb2JqZWN0IGNvbnRhaW5pbmcgdGhlIGNhcGFiaWxpdGllcy5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIENhcGFiaWxpdGllcygpOiBQcm9taXNlPFJlY29yZDxzdHJpbmcsIGFueT4+IHtcbiAgICByZXR1cm4gY2FsbChTeXN0ZW1DYXBhYmlsaXRpZXMpO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIE9TSW5mbyB7XG4gICAgLyoqIFRoZSBicmFuZGluZyBvZiB0aGUgT1MuICovXG4gICAgQnJhbmRpbmc6IHN0cmluZztcbiAgICAvKiogVGhlIElEIG9mIHRoZSBPUy4gKi9cbiAgICBJRDogc3RyaW5nO1xuICAgIC8qKiBUaGUgbmFtZSBvZiB0aGUgT1MuICovXG4gICAgTmFtZTogc3RyaW5nO1xuICAgIC8qKiBUaGUgdmVyc2lvbiBvZiB0aGUgT1MuICovXG4gICAgVmVyc2lvbjogc3RyaW5nO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEVudmlyb25tZW50SW5mbyB7XG4gICAgLyoqIFRoZSBhcmNoaXRlY3R1cmUgb2YgdGhlIHN5c3RlbS4gKi9cbiAgICBBcmNoOiBzdHJpbmc7XG4gICAgLyoqIFRydWUgaWYgdGhlIGFwcGxpY2F0aW9uIGlzIHJ1bm5pbmcgaW4gZGVidWcgbW9kZSwgb3RoZXJ3aXNlIGZhbHNlLiAqL1xuICAgIERlYnVnOiBib29sZWFuO1xuICAgIC8qKiBUaGUgb3BlcmF0aW5nIHN5c3RlbSBpbiB1c2UuICovXG4gICAgT1M6IHN0cmluZztcbiAgICAvKiogRGV0YWlscyBvZiB0aGUgb3BlcmF0aW5nIHN5c3RlbS4gKi9cbiAgICBPU0luZm86IE9TSW5mbztcbiAgICAvKiogQWRkaXRpb25hbCBwbGF0Zm9ybSBpbmZvcm1hdGlvbi4gKi9cbiAgICBQbGF0Zm9ybUluZm86IFJlY29yZDxzdHJpbmcsIGFueT47XG59XG5cbi8qKlxuICogUmV0cmlldmVzIGVudmlyb25tZW50IGRldGFpbHMuXG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgdG8gYW4gb2JqZWN0IGNvbnRhaW5pbmcgT1MgYW5kIHN5c3RlbSBhcmNoaXRlY3R1cmUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFbnZpcm9ubWVudCgpOiBQcm9taXNlPEVudmlyb25tZW50SW5mbz4ge1xuICAgIHJldHVybiBjYWxsKFN5c3RlbUVudmlyb25tZW50KTtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgdGhlIGN1cnJlbnQgb3BlcmF0aW5nIHN5c3RlbSBpcyBXaW5kb3dzLlxuICpcbiAqIEByZXR1cm4gVHJ1ZSBpZiB0aGUgb3BlcmF0aW5nIHN5c3RlbSBpcyBXaW5kb3dzLCBvdGhlcndpc2UgZmFsc2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJc1dpbmRvd3MoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuICh3aW5kb3cgYXMgYW55KS5fd2FpbHM/LmVudmlyb25tZW50Py5PUyA9PT0gXCJ3aW5kb3dzXCI7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIHRoZSBjdXJyZW50IG9wZXJhdGluZyBzeXN0ZW0gaXMgTGludXguXG4gKlxuICogQHJldHVybnMgUmV0dXJucyB0cnVlIGlmIHRoZSBjdXJyZW50IG9wZXJhdGluZyBzeXN0ZW0gaXMgTGludXgsIGZhbHNlIG90aGVyd2lzZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzTGludXgoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuICh3aW5kb3cgYXMgYW55KS5fd2FpbHM/LmVudmlyb25tZW50Py5PUyA9PT0gXCJsaW51eFwiO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiB0aGUgY3VycmVudCBlbnZpcm9ubWVudCBpcyBhIG1hY09TIG9wZXJhdGluZyBzeXN0ZW0uXG4gKlxuICogQHJldHVybnMgVHJ1ZSBpZiB0aGUgZW52aXJvbm1lbnQgaXMgbWFjT1MsIGZhbHNlIG90aGVyd2lzZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzTWFjKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiAod2luZG93IGFzIGFueSkuX3dhaWxzPy5lbnZpcm9ubWVudD8uT1MgPT09IFwiZGFyd2luXCI7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIHRoZSBjdXJyZW50IGVudmlyb25tZW50IGFyY2hpdGVjdHVyZSBpcyBBTUQ2NC5cbiAqXG4gKiBAcmV0dXJucyBUcnVlIGlmIHRoZSBjdXJyZW50IGVudmlyb25tZW50IGFyY2hpdGVjdHVyZSBpcyBBTUQ2NCwgZmFsc2Ugb3RoZXJ3aXNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gSXNBTUQ2NCgpOiBib29sZWFuIHtcbiAgICByZXR1cm4gKHdpbmRvdyBhcyBhbnkpLl93YWlscz8uZW52aXJvbm1lbnQ/LkFyY2ggPT09IFwiYW1kNjRcIjtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgdGhlIGN1cnJlbnQgYXJjaGl0ZWN0dXJlIGlzIEFSTS5cbiAqXG4gKiBAcmV0dXJucyBUcnVlIGlmIHRoZSBjdXJyZW50IGFyY2hpdGVjdHVyZSBpcyBBUk0sIGZhbHNlIG90aGVyd2lzZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzQVJNKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiAod2luZG93IGFzIGFueSkuX3dhaWxzPy5lbnZpcm9ubWVudD8uQXJjaCA9PT0gXCJhcm1cIjtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgdGhlIGN1cnJlbnQgZW52aXJvbm1lbnQgaXMgQVJNNjQgYXJjaGl0ZWN0dXJlLlxuICpcbiAqIEByZXR1cm5zIFJldHVybnMgdHJ1ZSBpZiB0aGUgZW52aXJvbm1lbnQgaXMgQVJNNjQgYXJjaGl0ZWN0dXJlLCBvdGhlcndpc2UgcmV0dXJucyBmYWxzZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzQVJNNjQoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuICh3aW5kb3cgYXMgYW55KS5fd2FpbHM/LmVudmlyb25tZW50Py5BcmNoID09PSBcImFybTY0XCI7XG59XG5cbi8qKlxuICogUmVwb3J0cyB3aGV0aGVyIHRoZSBhcHAgaXMgYmVpbmcgcnVuIGluIGRlYnVnIG1vZGUuXG4gKlxuICogQHJldHVybnMgVHJ1ZSBpZiB0aGUgYXBwIGlzIGJlaW5nIHJ1biBpbiBkZWJ1ZyBtb2RlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gSXNEZWJ1ZygpOiBib29sZWFuIHtcbiAgICByZXR1cm4gQm9vbGVhbigod2luZG93IGFzIGFueSkuX3dhaWxzPy5lbnZpcm9ubWVudD8uRGVidWcpO1xufVxuXG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IG5ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzIH0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuaW1wb3J0IHsgSXNEZWJ1ZyB9IGZyb20gXCIuL3N5c3RlbS5qc1wiO1xuaW1wb3J0IHsgZXZlbnRUYXJnZXQgfSBmcm9tIFwiLi91dGlscy5qc1wiO1xuXG4vLyBzZXR1cFxud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ2NvbnRleHRtZW51JywgY29udGV4dE1lbnVIYW5kbGVyKTtcblxuY29uc3QgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0TmFtZXMuQ29udGV4dE1lbnUpO1xuXG5jb25zdCBDb250ZXh0TWVudU9wZW4gPSAwO1xuXG5mdW5jdGlvbiBvcGVuQ29udGV4dE1lbnUoaWQ6IHN0cmluZywgeDogbnVtYmVyLCB5OiBudW1iZXIsIGRhdGE6IGFueSk6IHZvaWQge1xuICAgIHZvaWQgY2FsbChDb250ZXh0TWVudU9wZW4sIHtpZCwgeCwgeSwgZGF0YX0pO1xufVxuXG5mdW5jdGlvbiBjb250ZXh0TWVudUhhbmRsZXIoZXZlbnQ6IE1vdXNlRXZlbnQpIHtcbiAgICBjb25zdCB0YXJnZXQgPSBldmVudFRhcmdldChldmVudCk7XG5cbiAgICAvLyBDaGVjayBmb3IgY3VzdG9tIGNvbnRleHQgbWVudVxuICAgIGNvbnN0IGN1c3RvbUNvbnRleHRNZW51ID0gd2luZG93LmdldENvbXB1dGVkU3R5bGUodGFyZ2V0KS5nZXRQcm9wZXJ0eVZhbHVlKFwiLS1jdXN0b20tY29udGV4dG1lbnVcIikudHJpbSgpO1xuXG4gICAgaWYgKGN1c3RvbUNvbnRleHRNZW51KSB7XG4gICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgIGNvbnN0IGRhdGEgPSB3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0YXJnZXQpLmdldFByb3BlcnR5VmFsdWUoXCItLWN1c3RvbS1jb250ZXh0bWVudS1kYXRhXCIpO1xuICAgICAgICBvcGVuQ29udGV4dE1lbnUoY3VzdG9tQ29udGV4dE1lbnUsIGV2ZW50LmNsaWVudFgsIGV2ZW50LmNsaWVudFksIGRhdGEpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHByb2Nlc3NEZWZhdWx0Q29udGV4dE1lbnUoZXZlbnQsIHRhcmdldCk7XG4gICAgfVxufVxuXG5cbi8qXG4tLWRlZmF1bHQtY29udGV4dG1lbnU6IGF1dG87IChkZWZhdWx0KSB3aWxsIHNob3cgdGhlIGRlZmF1bHQgY29udGV4dCBtZW51IGlmIGNvbnRlbnRFZGl0YWJsZSBpcyB0cnVlIE9SIHRleHQgaGFzIGJlZW4gc2VsZWN0ZWQgT1IgZWxlbWVudCBpcyBpbnB1dCBvciB0ZXh0YXJlYVxuLS1kZWZhdWx0LWNvbnRleHRtZW51OiBzaG93OyB3aWxsIGFsd2F5cyBzaG93IHRoZSBkZWZhdWx0IGNvbnRleHQgbWVudVxuLS1kZWZhdWx0LWNvbnRleHRtZW51OiBoaWRlOyB3aWxsIGFsd2F5cyBoaWRlIHRoZSBkZWZhdWx0IGNvbnRleHQgbWVudVxuXG5UaGlzIHJ1bGUgaXMgaW5oZXJpdGVkIGxpa2Ugbm9ybWFsIENTUyBydWxlcywgc28gbmVzdGluZyB3b3JrcyBhcyBleHBlY3RlZFxuKi9cbmZ1bmN0aW9uIHByb2Nlc3NEZWZhdWx0Q29udGV4dE1lbnUoZXZlbnQ6IE1vdXNlRXZlbnQsIHRhcmdldDogSFRNTEVsZW1lbnQpIHtcbiAgICAvLyBEZWJ1ZyBidWlsZHMgYWx3YXlzIHNob3cgdGhlIG1lbnVcbiAgICBpZiAoSXNEZWJ1ZygpKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBQcm9jZXNzIGRlZmF1bHQgY29udGV4dCBtZW51XG4gICAgc3dpdGNoICh3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0YXJnZXQpLmdldFByb3BlcnR5VmFsdWUoXCItLWRlZmF1bHQtY29udGV4dG1lbnVcIikudHJpbSgpKSB7XG4gICAgICAgIGNhc2UgJ3Nob3cnOlxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBjYXNlICdoaWRlJzpcbiAgICAgICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gQ2hlY2sgaWYgY29udGVudEVkaXRhYmxlIGlzIHRydWVcbiAgICBpZiAodGFyZ2V0LmlzQ29udGVudEVkaXRhYmxlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBDaGVjayBpZiB0ZXh0IGhhcyBiZWVuIHNlbGVjdGVkXG4gICAgY29uc3Qgc2VsZWN0aW9uID0gd2luZG93LmdldFNlbGVjdGlvbigpO1xuICAgIGNvbnN0IGhhc1NlbGVjdGlvbiA9IHNlbGVjdGlvbiAmJiBzZWxlY3Rpb24udG9TdHJpbmcoKS5sZW5ndGggPiAwO1xuICAgIGlmIChoYXNTZWxlY3Rpb24pIHtcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBzZWxlY3Rpb24ucmFuZ2VDb3VudDsgaSsrKSB7XG4gICAgICAgICAgICBjb25zdCByYW5nZSA9IHNlbGVjdGlvbi5nZXRSYW5nZUF0KGkpO1xuICAgICAgICAgICAgY29uc3QgcmVjdHMgPSByYW5nZS5nZXRDbGllbnRSZWN0cygpO1xuICAgICAgICAgICAgZm9yIChsZXQgaiA9IDA7IGogPCByZWN0cy5sZW5ndGg7IGorKykge1xuICAgICAgICAgICAgICAgIGNvbnN0IHJlY3QgPSByZWN0c1tqXTtcbiAgICAgICAgICAgICAgICBpZiAoZG9jdW1lbnQuZWxlbWVudEZyb21Qb2ludChyZWN0LmxlZnQsIHJlY3QudG9wKSA9PT0gdGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBDaGVjayBpZiB0YWcgaXMgaW5wdXQgb3IgdGV4dGFyZWEuXG4gICAgaWYgKHRhcmdldCBpbnN0YW5jZW9mIEhUTUxJbnB1dEVsZW1lbnQgfHwgdGFyZ2V0IGluc3RhbmNlb2YgSFRNTFRleHRBcmVhRWxlbWVudCkge1xuICAgICAgICBpZiAoaGFzU2VsZWN0aW9uIHx8ICghdGFyZ2V0LnJlYWRPbmx5ICYmICF0YXJnZXQuZGlzYWJsZWQpKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBoaWRlIGRlZmF1bHQgY29udGV4dCBtZW51XG4gICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoqXG4gKiBSZXRyaWV2ZXMgdGhlIHZhbHVlIGFzc29jaWF0ZWQgd2l0aCB0aGUgc3BlY2lmaWVkIGtleSBmcm9tIHRoZSBmbGFnIG1hcC5cbiAqXG4gKiBAcGFyYW0ga2V5IC0gVGhlIGtleSB0byByZXRyaWV2ZSB0aGUgdmFsdWUgZm9yLlxuICogQHJldHVybiBUaGUgdmFsdWUgYXNzb2NpYXRlZCB3aXRoIHRoZSBzcGVjaWZpZWQga2V5LlxuICovXG5leHBvcnQgZnVuY3Rpb24gR2V0RmxhZyhrZXk6IHN0cmluZyk6IGFueSB7XG4gICAgdHJ5IHtcbiAgICAgICAgcmV0dXJuIHdpbmRvdy5fd2FpbHMuZmxhZ3Nba2V5XTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcIlVuYWJsZSB0byByZXRyaWV2ZSBmbGFnICdcIiArIGtleSArIFwiJzogXCIgKyBlLCB7IGNhdXNlOiBlIH0pO1xuICAgIH1cbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHsgaW52b2tlLCBJc1dpbmRvd3MgfSBmcm9tIFwiLi9zeXN0ZW0uanNcIjtcbmltcG9ydCB7IEdldEZsYWcgfSBmcm9tIFwiLi9mbGFncy5qc1wiO1xuaW1wb3J0IHsgY2FuVHJhY2tCdXR0b25zLCBldmVudFRhcmdldCB9IGZyb20gXCIuL3V0aWxzLmpzXCI7XG5cbi8vIFNldHVwXG5sZXQgY2FuRHJhZyA9IGZhbHNlO1xubGV0IGRyYWdnaW5nID0gZmFsc2U7XG5cbmxldCByZXNpemFibGUgPSBmYWxzZTtcbmxldCBjYW5SZXNpemUgPSBmYWxzZTtcbmxldCByZXNpemluZyA9IGZhbHNlO1xubGV0IHJlc2l6ZUVkZ2U6IHN0cmluZyA9IFwiXCI7XG5sZXQgZGVmYXVsdEN1cnNvciA9IFwiYXV0b1wiO1xuXG5sZXQgYnV0dG9ucyA9IDA7XG5jb25zdCBidXR0b25zVHJhY2tlZCA9IGNhblRyYWNrQnV0dG9ucygpO1xuXG53aW5kb3cuX3dhaWxzID0gd2luZG93Ll93YWlscyB8fCB7fTtcbndpbmRvdy5fd2FpbHMuc2V0UmVzaXphYmxlID0gKHZhbHVlOiBib29sZWFuKTogdm9pZCA9PiB7XG4gICAgcmVzaXphYmxlID0gdmFsdWU7XG4gICAgaWYgKCFyZXNpemFibGUpIHtcbiAgICAgICAgLy8gU3RvcCByZXNpemluZyBpZiBpbiBwcm9ncmVzcy5cbiAgICAgICAgY2FuUmVzaXplID0gcmVzaXppbmcgPSBmYWxzZTtcbiAgICAgICAgc2V0UmVzaXplKCk7XG4gICAgfVxufTtcblxuLy8gRGVmZXIgYXR0YWNoaW5nIG1vdXNlIGxpc3RlbmVycyB1bnRpbCB3ZSBrbm93IHdlJ3JlIG5vdCBvbiBtb2JpbGUuXG5sZXQgZHJhZ0luaXREb25lID0gZmFsc2U7XG5mdW5jdGlvbiBpc01vYmlsZSgpOiBib29sZWFuIHtcbiAgICBjb25zdCBvcyA9ICh3aW5kb3cgYXMgYW55KS5fd2FpbHM/LmVudmlyb25tZW50Py5PUztcbiAgICBpZiAob3MgPT09IFwiaW9zXCIgfHwgb3MgPT09IFwiYW5kcm9pZFwiKSByZXR1cm4gdHJ1ZTtcbiAgICAvLyBGYWxsYmFjayBoZXVyaXN0aWMgaWYgZW52aXJvbm1lbnQgbm90IHlldCBzZXRcbiAgICBjb25zdCB1YSA9IG5hdmlnYXRvci51c2VyQWdlbnQgfHwgbmF2aWdhdG9yLnZlbmRvciB8fCAod2luZG93IGFzIGFueSkub3BlcmEgfHwgXCJcIjtcbiAgICByZXR1cm4gL2FuZHJvaWR8aXBob25lfGlwYWR8aXBvZHxpZW1vYmlsZXx3cGRlc2t0b3AvaS50ZXN0KHVhKTtcbn1cbmZ1bmN0aW9uIHRyeUluaXREcmFnSGFuZGxlcnMoKTogdm9pZCB7XG4gICAgaWYgKGRyYWdJbml0RG9uZSkgcmV0dXJuO1xuICAgIGlmIChpc01vYmlsZSgpKSByZXR1cm47XG4gICAgd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ21vdXNlZG93bicsIHVwZGF0ZSwgeyBjYXB0dXJlOiB0cnVlIH0pO1xuICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtb3VzZW1vdmUnLCB1cGRhdGUsIHsgY2FwdHVyZTogdHJ1ZSB9KTtcbiAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbW91c2V1cCcsIHVwZGF0ZSwgeyBjYXB0dXJlOiB0cnVlIH0pO1xuICAgIGZvciAoY29uc3QgZXYgb2YgWydjbGljaycsICdjb250ZXh0bWVudScsICdkYmxjbGljayddKSB7XG4gICAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKGV2LCBzdXBwcmVzc0V2ZW50LCB7IGNhcHR1cmU6IHRydWUgfSk7XG4gICAgfVxuICAgIGRyYWdJbml0RG9uZSA9IHRydWU7XG59XG4vLyBBdHRlbXB0IGltbWVkaWF0ZSBpbml0IChpbiBjYXNlIGVudmlyb25tZW50IGFscmVhZHkgcHJlc2VudClcbnRyeUluaXREcmFnSGFuZGxlcnMoKTtcbi8vIEFsc28gYXR0ZW1wdCBvbiBET00gcmVhZHlcbmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCB0cnlJbml0RHJhZ0hhbmRsZXJzLCB7IG9uY2U6IHRydWUgfSk7XG4vLyBBcyBhIGxhc3QgcmVzb3J0LCBwb2xsIGZvciBlbnZpcm9ubWVudCBmb3IgYSBzaG9ydCBwZXJpb2RcbmxldCBkcmFnRW52UG9sbHMgPSAwO1xuY29uc3QgZHJhZ0VudlBvbGwgPSB3aW5kb3cuc2V0SW50ZXJ2YWwoKCkgPT4ge1xuICAgIGlmIChkcmFnSW5pdERvbmUpIHsgd2luZG93LmNsZWFySW50ZXJ2YWwoZHJhZ0VudlBvbGwpOyByZXR1cm47IH1cbiAgICB0cnlJbml0RHJhZ0hhbmRsZXJzKCk7XG4gICAgaWYgKCsrZHJhZ0VudlBvbGxzID4gMTAwKSB7IHdpbmRvdy5jbGVhckludGVydmFsKGRyYWdFbnZQb2xsKTsgfVxufSwgNTApO1xuXG5mdW5jdGlvbiBzdXBwcmVzc0V2ZW50KGV2ZW50OiBFdmVudCkge1xuICAgIC8vIFN1cHByZXNzIGNsaWNrIGV2ZW50cyB3aGlsZSByZXNpemluZyBvciBkcmFnZ2luZy5cbiAgICBpZiAoZHJhZ2dpbmcgfHwgcmVzaXppbmcpIHtcbiAgICAgICAgZXZlbnQuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uKCk7XG4gICAgICAgIGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpO1xuICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuICAgIH1cbn1cblxuLy8gVXNlIGNvbnN0YW50cyB0byBhdm9pZCBjb21wYXJpbmcgc3RyaW5ncyBtdWx0aXBsZSB0aW1lcy5cbmNvbnN0IE1vdXNlRG93biA9IDA7XG5jb25zdCBNb3VzZVVwICAgPSAxO1xuY29uc3QgTW91c2VNb3ZlID0gMjtcblxuZnVuY3Rpb24gdXBkYXRlKGV2ZW50OiBNb3VzZUV2ZW50KSB7XG4gICAgLy8gV2luZG93cyBzdXBwcmVzc2VzIG1vdXNlIGV2ZW50cyBhdCB0aGUgZW5kIG9mIGRyYWdnaW5nIG9yIHJlc2l6aW5nLFxuICAgIC8vIHNvIHdlIG5lZWQgdG8gYmUgc21hcnQgYW5kIHN5bnRoZXNpemUgYnV0dG9uIGV2ZW50cy5cblxuICAgIGxldCBldmVudFR5cGU6IG51bWJlciwgZXZlbnRCdXR0b25zID0gZXZlbnQuYnV0dG9ucztcbiAgICBzd2l0Y2ggKGV2ZW50LnR5cGUpIHtcbiAgICAgICAgY2FzZSAnbW91c2Vkb3duJzpcbiAgICAgICAgICAgIGV2ZW50VHlwZSA9IE1vdXNlRG93bjtcbiAgICAgICAgICAgIGlmICghYnV0dG9uc1RyYWNrZWQpIHsgZXZlbnRCdXR0b25zID0gYnV0dG9ucyB8ICgxIDw8IGV2ZW50LmJ1dHRvbik7IH1cbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlICdtb3VzZXVwJzpcbiAgICAgICAgICAgIGV2ZW50VHlwZSA9IE1vdXNlVXA7XG4gICAgICAgICAgICBpZiAoIWJ1dHRvbnNUcmFja2VkKSB7IGV2ZW50QnV0dG9ucyA9IGJ1dHRvbnMgJiB+KDEgPDwgZXZlbnQuYnV0dG9uKTsgfVxuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgICBldmVudFR5cGUgPSBNb3VzZU1vdmU7XG4gICAgICAgICAgICBpZiAoIWJ1dHRvbnNUcmFja2VkKSB7IGV2ZW50QnV0dG9ucyA9IGJ1dHRvbnM7IH1cbiAgICAgICAgICAgIGJyZWFrO1xuICAgIH1cblxuICAgIGxldCByZWxlYXNlZCA9IGJ1dHRvbnMgJiB+ZXZlbnRCdXR0b25zO1xuICAgIGxldCBwcmVzc2VkID0gZXZlbnRCdXR0b25zICYgfmJ1dHRvbnM7XG5cbiAgICBidXR0b25zID0gZXZlbnRCdXR0b25zO1xuXG4gICAgLy8gU3ludGhlc2l6ZSBhIHJlbGVhc2UtcHJlc3Mgc2VxdWVuY2UgaWYgd2UgZGV0ZWN0IGEgcHJlc3Mgb2YgYW4gYWxyZWFkeSBwcmVzc2VkIGJ1dHRvbi5cbiAgICBpZiAoZXZlbnRUeXBlID09PSBNb3VzZURvd24gJiYgIShwcmVzc2VkICYgZXZlbnQuYnV0dG9uKSkge1xuICAgICAgICByZWxlYXNlZCB8PSAoMSA8PCBldmVudC5idXR0b24pO1xuICAgICAgICBwcmVzc2VkIHw9ICgxIDw8IGV2ZW50LmJ1dHRvbik7XG4gICAgfVxuXG4gICAgLy8gU3VwcHJlc3MgYWxsIGJ1dHRvbiBldmVudHMgZHVyaW5nIGRyYWdnaW5nIGFuZCByZXNpemluZyxcbiAgICAvLyB1bmxlc3MgdGhpcyBpcyBhIG1vdXNldXAgZXZlbnQgdGhhdCBpcyBlbmRpbmcgYSBkcmFnIGFjdGlvbi5cbiAgICBpZiAoXG4gICAgICAgIGV2ZW50VHlwZSAhPT0gTW91c2VNb3ZlIC8vIEZhc3QgcGF0aCBmb3IgbW91c2Vtb3ZlXG4gICAgICAgICYmIHJlc2l6aW5nXG4gICAgICAgIHx8IChcbiAgICAgICAgICAgIGRyYWdnaW5nXG4gICAgICAgICAgICAmJiAoXG4gICAgICAgICAgICAgICAgZXZlbnRUeXBlID09PSBNb3VzZURvd25cbiAgICAgICAgICAgICAgICB8fCBldmVudC5idXR0b24gIT09IDBcbiAgICAgICAgICAgIClcbiAgICAgICAgKVxuICAgICkge1xuICAgICAgICBldmVudC5zdG9wSW1tZWRpYXRlUHJvcGFnYXRpb24oKTtcbiAgICAgICAgZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgfVxuXG4gICAgLy8gSGFuZGxlIHJlbGVhc2VzXG4gICAgaWYgKHJlbGVhc2VkICYgMSkgeyBwcmltYXJ5VXAoZXZlbnQpOyB9XG4gICAgLy8gSGFuZGxlIHByZXNzZXNcbiAgICBpZiAocHJlc3NlZCAmIDEpIHsgcHJpbWFyeURvd24oZXZlbnQpOyB9XG5cbiAgICAvLyBIYW5kbGUgbW91c2Vtb3ZlXG4gICAgaWYgKGV2ZW50VHlwZSA9PT0gTW91c2VNb3ZlKSB7IG9uTW91c2VNb3ZlKGV2ZW50KTsgfTtcbn1cblxuZnVuY3Rpb24gcHJpbWFyeURvd24oZXZlbnQ6IE1vdXNlRXZlbnQpOiB2b2lkIHtcbiAgICAvLyBSZXNldCByZWFkaW5lc3Mgc3RhdGUuXG4gICAgY2FuRHJhZyA9IGZhbHNlO1xuICAgIGNhblJlc2l6ZSA9IGZhbHNlO1xuXG4gICAgLy8gSWdub3JlIHJlcGVhdGVkIGNsaWNrcyBvbiBtYWNPUyBhbmQgTGludXguXG4gICAgaWYgKCFJc1dpbmRvd3MoKSkge1xuICAgICAgICBpZiAoZXZlbnQudHlwZSA9PT0gJ21vdXNlZG93bicgJiYgZXZlbnQuYnV0dG9uID09PSAwICYmIGV2ZW50LmRldGFpbCAhPT0gMSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgaWYgKHJlc2l6ZUVkZ2UpIHtcbiAgICAgICAgLy8gUmVhZHkgdG8gcmVzaXplIGlmIHRoZSBwcmltYXJ5IGJ1dHRvbiB3YXMgcHJlc3NlZCBmb3IgdGhlIGZpcnN0IHRpbWUuXG4gICAgICAgIGNhblJlc2l6ZSA9IHRydWU7XG4gICAgICAgIC8vIERvIG5vdCBzdGFydCBkcmFnIG9wZXJhdGlvbnMgd2hlbiBvbiByZXNpemUgZWRnZXMuXG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBSZXRyaWV2ZSB0YXJnZXQgZWxlbWVudFxuICAgIGNvbnN0IHRhcmdldCA9IGV2ZW50VGFyZ2V0KGV2ZW50KTtcblxuICAgIC8vIFJlYWR5IHRvIGRyYWcgaWYgdGhlIHByaW1hcnkgYnV0dG9uIHdhcyBwcmVzc2VkIGZvciB0aGUgZmlyc3QgdGltZSBvbiBhIGRyYWdnYWJsZSBlbGVtZW50LlxuICAgIC8vIElnbm9yZSBjbGlja3Mgb24gdGhlIHNjcm9sbGJhci5cbiAgICBjb25zdCBzdHlsZSA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKHRhcmdldCk7XG4gICAgY2FuRHJhZyA9IChcbiAgICAgICAgc3R5bGUuZ2V0UHJvcGVydHlWYWx1ZShcIi0td2FpbHMtZHJhZ2dhYmxlXCIpLnRyaW0oKSA9PT0gXCJkcmFnXCJcbiAgICAgICAgJiYgKFxuICAgICAgICAgICAgZXZlbnQub2Zmc2V0WCAtIHBhcnNlRmxvYXQoc3R5bGUucGFkZGluZ0xlZnQpIDwgdGFyZ2V0LmNsaWVudFdpZHRoXG4gICAgICAgICAgICAmJiBldmVudC5vZmZzZXRZIC0gcGFyc2VGbG9hdChzdHlsZS5wYWRkaW5nVG9wKSA8IHRhcmdldC5jbGllbnRIZWlnaHRcbiAgICAgICAgKVxuICAgICk7XG59XG5cbmZ1bmN0aW9uIHByaW1hcnlVcChldmVudDogTW91c2VFdmVudCkge1xuICAgIC8vIFN0b3AgZHJhZ2dpbmcgYW5kIHJlc2l6aW5nLlxuICAgIGNhbkRyYWcgPSBmYWxzZTtcbiAgICBkcmFnZ2luZyA9IGZhbHNlO1xuICAgIGNhblJlc2l6ZSA9IGZhbHNlO1xuICAgIHJlc2l6aW5nID0gZmFsc2U7XG59XG5cbmNvbnN0IGN1cnNvckZvckVkZ2UgPSBPYmplY3QuZnJlZXplKHtcbiAgICBcInNlLXJlc2l6ZVwiOiBcIm53c2UtcmVzaXplXCIsXG4gICAgXCJzdy1yZXNpemVcIjogXCJuZXN3LXJlc2l6ZVwiLFxuICAgIFwibnctcmVzaXplXCI6IFwibndzZS1yZXNpemVcIixcbiAgICBcIm5lLXJlc2l6ZVwiOiBcIm5lc3ctcmVzaXplXCIsXG4gICAgXCJ3LXJlc2l6ZVwiOiBcImV3LXJlc2l6ZVwiLFxuICAgIFwibi1yZXNpemVcIjogXCJucy1yZXNpemVcIixcbiAgICBcInMtcmVzaXplXCI6IFwibnMtcmVzaXplXCIsXG4gICAgXCJlLXJlc2l6ZVwiOiBcImV3LXJlc2l6ZVwiLFxufSlcblxuZnVuY3Rpb24gc2V0UmVzaXplKGVkZ2U/OiBrZXlvZiB0eXBlb2YgY3Vyc29yRm9yRWRnZSk6IHZvaWQge1xuICAgIGlmIChlZGdlKSB7XG4gICAgICAgIGlmICghcmVzaXplRWRnZSkgeyBkZWZhdWx0Q3Vyc29yID0gZG9jdW1lbnQuYm9keS5zdHlsZS5jdXJzb3I7IH1cbiAgICAgICAgZG9jdW1lbnQuYm9keS5zdHlsZS5jdXJzb3IgPSBjdXJzb3JGb3JFZGdlW2VkZ2VdO1xuICAgIH0gZWxzZSBpZiAoIWVkZ2UgJiYgcmVzaXplRWRnZSkge1xuICAgICAgICBkb2N1bWVudC5ib2R5LnN0eWxlLmN1cnNvciA9IGRlZmF1bHRDdXJzb3I7XG4gICAgfVxuXG4gICAgcmVzaXplRWRnZSA9IGVkZ2UgfHwgXCJcIjtcbn1cblxuZnVuY3Rpb24gb25Nb3VzZU1vdmUoZXZlbnQ6IE1vdXNlRXZlbnQpOiB2b2lkIHtcbiAgICBpZiAoY2FuUmVzaXplICYmIHJlc2l6ZUVkZ2UpIHtcbiAgICAgICAgLy8gU3RhcnQgcmVzaXppbmcuXG4gICAgICAgIHJlc2l6aW5nID0gdHJ1ZTtcbiAgICAgICAgaW52b2tlKFwid2FpbHM6cmVzaXplOlwiICsgcmVzaXplRWRnZSk7XG4gICAgfSBlbHNlIGlmIChjYW5EcmFnKSB7XG4gICAgICAgIC8vIFN0YXJ0IGRyYWdnaW5nLlxuICAgICAgICBkcmFnZ2luZyA9IHRydWU7XG4gICAgICAgIGludm9rZShcIndhaWxzOmRyYWdcIik7XG4gICAgfVxuXG4gICAgaWYgKGRyYWdnaW5nIHx8IHJlc2l6aW5nKSB7XG4gICAgICAgIC8vIEVpdGhlciBkcmFnIG9yIHJlc2l6ZSBpcyBvbmdvaW5nLFxuICAgICAgICAvLyByZXNldCByZWFkaW5lc3MgYW5kIHN0b3AgcHJvY2Vzc2luZy5cbiAgICAgICAgY2FuRHJhZyA9IGNhblJlc2l6ZSA9IGZhbHNlO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgaWYgKCFyZXNpemFibGUgfHwgIUlzV2luZG93cygpKSB7XG4gICAgICAgIGlmIChyZXNpemVFZGdlKSB7IHNldFJlc2l6ZSgpOyB9XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBjb25zdCByZXNpemVIYW5kbGVIZWlnaHQgPSBHZXRGbGFnKFwic3lzdGVtLnJlc2l6ZUhhbmRsZUhlaWdodFwiKSB8fCA1O1xuICAgIGNvbnN0IHJlc2l6ZUhhbmRsZVdpZHRoID0gR2V0RmxhZyhcInN5c3RlbS5yZXNpemVIYW5kbGVXaWR0aFwiKSB8fCA1O1xuXG4gICAgLy8gRXh0cmEgcGl4ZWxzIGZvciB0aGUgY29ybmVyIGFyZWFzLlxuICAgIGNvbnN0IGNvcm5lckV4dHJhID0gR2V0RmxhZyhcInJlc2l6ZUNvcm5lckV4dHJhXCIpIHx8IDEwO1xuXG4gICAgY29uc3QgcmlnaHRCb3JkZXIgPSAod2luZG93Lm91dGVyV2lkdGggLSBldmVudC5jbGllbnRYKSA8IHJlc2l6ZUhhbmRsZVdpZHRoO1xuICAgIGNvbnN0IGxlZnRCb3JkZXIgPSBldmVudC5jbGllbnRYIDwgcmVzaXplSGFuZGxlV2lkdGg7XG4gICAgY29uc3QgdG9wQm9yZGVyID0gZXZlbnQuY2xpZW50WSA8IHJlc2l6ZUhhbmRsZUhlaWdodDtcbiAgICBjb25zdCBib3R0b21Cb3JkZXIgPSAod2luZG93Lm91dGVySGVpZ2h0IC0gZXZlbnQuY2xpZW50WSkgPCByZXNpemVIYW5kbGVIZWlnaHQ7XG5cbiAgICAvLyBBZGp1c3QgZm9yIGNvcm5lciBhcmVhcy5cbiAgICBjb25zdCByaWdodENvcm5lciA9ICh3aW5kb3cub3V0ZXJXaWR0aCAtIGV2ZW50LmNsaWVudFgpIDwgKHJlc2l6ZUhhbmRsZVdpZHRoICsgY29ybmVyRXh0cmEpO1xuICAgIGNvbnN0IGxlZnRDb3JuZXIgPSBldmVudC5jbGllbnRYIDwgKHJlc2l6ZUhhbmRsZVdpZHRoICsgY29ybmVyRXh0cmEpO1xuICAgIGNvbnN0IHRvcENvcm5lciA9IGV2ZW50LmNsaWVudFkgPCAocmVzaXplSGFuZGxlSGVpZ2h0ICsgY29ybmVyRXh0cmEpO1xuICAgIGNvbnN0IGJvdHRvbUNvcm5lciA9ICh3aW5kb3cub3V0ZXJIZWlnaHQgLSBldmVudC5jbGllbnRZKSA8IChyZXNpemVIYW5kbGVIZWlnaHQgKyBjb3JuZXJFeHRyYSk7XG5cbiAgICBpZiAoIWxlZnRDb3JuZXIgJiYgIXRvcENvcm5lciAmJiAhYm90dG9tQ29ybmVyICYmICFyaWdodENvcm5lcikge1xuICAgICAgICAvLyBPcHRpbWlzYXRpb246IG91dCBvZiBhbGwgY29ybmVyIGFyZWFzIGltcGxpZXMgb3V0IG9mIGJvcmRlcnMuXG4gICAgICAgIHNldFJlc2l6ZSgpO1xuICAgIH1cbiAgICAvLyBEZXRlY3QgY29ybmVycy5cbiAgICBlbHNlIGlmIChyaWdodENvcm5lciAmJiBib3R0b21Db3JuZXIpIHNldFJlc2l6ZShcInNlLXJlc2l6ZVwiKTtcbiAgICBlbHNlIGlmIChsZWZ0Q29ybmVyICYmIGJvdHRvbUNvcm5lcikgc2V0UmVzaXplKFwic3ctcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKGxlZnRDb3JuZXIgJiYgdG9wQ29ybmVyKSBzZXRSZXNpemUoXCJudy1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAodG9wQ29ybmVyICYmIHJpZ2h0Q29ybmVyKSBzZXRSZXNpemUoXCJuZS1yZXNpemVcIik7XG4gICAgLy8gRGV0ZWN0IGJvcmRlcnMuXG4gICAgZWxzZSBpZiAobGVmdEJvcmRlcikgc2V0UmVzaXplKFwidy1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAodG9wQm9yZGVyKSBzZXRSZXNpemUoXCJuLXJlc2l6ZVwiKTtcbiAgICBlbHNlIGlmIChib3R0b21Cb3JkZXIpIHNldFJlc2l6ZShcInMtcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKHJpZ2h0Qm9yZGVyKSBzZXRSZXNpemUoXCJlLXJlc2l6ZVwiKTtcbiAgICAvLyBPdXQgb2YgYm9yZGVyIGFyZWEuXG4gICAgZWxzZSBzZXRSZXNpemUoKTtcbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHsgbmV3UnVudGltZUNhbGxlciwgb2JqZWN0TmFtZXMgfSBmcm9tIFwiLi9ydW50aW1lLmpzXCI7XG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5BcHBsaWNhdGlvbik7XG5cbmNvbnN0IEhpZGVNZXRob2QgPSAwO1xuY29uc3QgU2hvd01ldGhvZCA9IDE7XG5jb25zdCBRdWl0TWV0aG9kID0gMjtcblxuLyoqXG4gKiBIaWRlcyBhIGNlcnRhaW4gbWV0aG9kIGJ5IGNhbGxpbmcgdGhlIEhpZGVNZXRob2QgZnVuY3Rpb24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBIaWRlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBjYWxsKEhpZGVNZXRob2QpO1xufVxuXG4vKipcbiAqIENhbGxzIHRoZSBTaG93TWV0aG9kIGFuZCByZXR1cm5zIHRoZSByZXN1bHQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBTaG93KCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBjYWxsKFNob3dNZXRob2QpO1xufVxuXG4vKipcbiAqIENhbGxzIHRoZSBRdWl0TWV0aG9kIHRvIHRlcm1pbmF0ZSB0aGUgcHJvZ3JhbS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFF1aXQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgcmV0dXJuIGNhbGwoUXVpdE1ldGhvZCk7XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IENhbmNlbGxhYmxlUHJvbWlzZSwgdHlwZSBDYW5jZWxsYWJsZVByb21pc2VXaXRoUmVzb2x2ZXJzIH0gZnJvbSBcIi4vY2FuY2VsbGFibGUuanNcIjtcbmltcG9ydCB7IG5ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzIH0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuaW1wb3J0IHsgbmFub2lkIH0gZnJvbSBcIi4vbmFub2lkLmpzXCI7XG5cbi8vIFNldHVwXG53aW5kb3cuX3dhaWxzID0gd2luZG93Ll93YWlscyB8fCB7fTtcblxudHlwZSBQcm9taXNlUmVzb2x2ZXJzID0gT21pdDxDYW5jZWxsYWJsZVByb21pc2VXaXRoUmVzb2x2ZXJzPGFueT4sIFwicHJvbWlzZVwiIHwgXCJvbmNhbmNlbGxlZFwiPlxuXG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5DYWxsKTtcbmNvbnN0IGNhbmNlbENhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdE5hbWVzLkNhbmNlbENhbGwpO1xuY29uc3QgY2FsbFJlc3BvbnNlcyA9IG5ldyBNYXA8c3RyaW5nLCBQcm9taXNlUmVzb2x2ZXJzPigpO1xuXG5jb25zdCBDYWxsQmluZGluZyA9IDA7XG5jb25zdCBDYW5jZWxNZXRob2QgPSAwXG5cbi8qKlxuICogSG9sZHMgYWxsIHJlcXVpcmVkIGluZm9ybWF0aW9uIGZvciBhIGJpbmRpbmcgY2FsbC5cbiAqIE1heSBwcm92aWRlIGVpdGhlciBhIG1ldGhvZCBJRCBvciBhIG1ldGhvZCBuYW1lLCBidXQgbm90IGJvdGguXG4gKi9cbmV4cG9ydCB0eXBlIENhbGxPcHRpb25zID0ge1xuICAgIC8qKiBUaGUgbnVtZXJpYyBJRCBvZiB0aGUgYm91bmQgbWV0aG9kIHRvIGNhbGwuICovXG4gICAgbWV0aG9kSUQ6IG51bWJlcjtcbiAgICAvKiogVGhlIGZ1bGx5IHF1YWxpZmllZCBuYW1lIG9mIHRoZSBib3VuZCBtZXRob2QgdG8gY2FsbC4gKi9cbiAgICBtZXRob2ROYW1lPzogbmV2ZXI7XG4gICAgLyoqIEFyZ3VtZW50cyB0byBiZSBwYXNzZWQgaW50byB0aGUgYm91bmQgbWV0aG9kLiAqL1xuICAgIGFyZ3M6IGFueVtdO1xufSB8IHtcbiAgICAvKiogVGhlIG51bWVyaWMgSUQgb2YgdGhlIGJvdW5kIG1ldGhvZCB0byBjYWxsLiAqL1xuICAgIG1ldGhvZElEPzogbmV2ZXI7XG4gICAgLyoqIFRoZSBmdWxseSBxdWFsaWZpZWQgbmFtZSBvZiB0aGUgYm91bmQgbWV0aG9kIHRvIGNhbGwuICovXG4gICAgbWV0aG9kTmFtZTogc3RyaW5nO1xuICAgIC8qKiBBcmd1bWVudHMgdG8gYmUgcGFzc2VkIGludG8gdGhlIGJvdW5kIG1ldGhvZC4gKi9cbiAgICBhcmdzOiBhbnlbXTtcbn07XG5cbi8qKlxuICogRXhjZXB0aW9uIGNsYXNzIHRoYXQgd2lsbCBiZSB0aHJvd24gaW4gY2FzZSB0aGUgYm91bmQgbWV0aG9kIHJldHVybnMgYW4gZXJyb3IuXG4gKiBUaGUgdmFsdWUgb2YgdGhlIHtAbGluayBSdW50aW1lRXJyb3IjbmFtZX0gcHJvcGVydHkgaXMgXCJSdW50aW1lRXJyb3JcIi5cbiAqL1xuZXhwb3J0IGNsYXNzIFJ1bnRpbWVFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgICAvKipcbiAgICAgKiBDb25zdHJ1Y3RzIGEgbmV3IFJ1bnRpbWVFcnJvciBpbnN0YW5jZS5cbiAgICAgKiBAcGFyYW0gbWVzc2FnZSAtIFRoZSBlcnJvciBtZXNzYWdlLlxuICAgICAqIEBwYXJhbSBvcHRpb25zIC0gT3B0aW9ucyB0byBiZSBmb3J3YXJkZWQgdG8gdGhlIEVycm9yIGNvbnN0cnVjdG9yLlxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKG1lc3NhZ2U/OiBzdHJpbmcsIG9wdGlvbnM/OiBFcnJvck9wdGlvbnMpIHtcbiAgICAgICAgc3VwZXIobWVzc2FnZSwgb3B0aW9ucyk7XG4gICAgICAgIHRoaXMubmFtZSA9IFwiUnVudGltZUVycm9yXCI7XG4gICAgfVxufVxuXG4vKipcbiAqIEdlbmVyYXRlcyBhIHVuaXF1ZSBJRCB1c2luZyB0aGUgbmFub2lkIGxpYnJhcnkuXG4gKlxuICogQHJldHVybnMgQSB1bmlxdWUgSUQgdGhhdCBkb2VzIG5vdCBleGlzdCBpbiB0aGUgY2FsbFJlc3BvbnNlcyBzZXQuXG4gKi9cbmZ1bmN0aW9uIGdlbmVyYXRlSUQoKTogc3RyaW5nIHtcbiAgICBsZXQgcmVzdWx0O1xuICAgIGRvIHtcbiAgICAgICAgcmVzdWx0ID0gbmFub2lkKCk7XG4gICAgfSB3aGlsZSAoY2FsbFJlc3BvbnNlcy5oYXMocmVzdWx0KSk7XG4gICAgcmV0dXJuIHJlc3VsdDtcbn1cblxuLyoqXG4gKiBDYWxsIGEgYm91bmQgbWV0aG9kIGFjY29yZGluZyB0byB0aGUgZ2l2ZW4gY2FsbCBvcHRpb25zLlxuICpcbiAqIEluIGNhc2Ugb2YgZmFpbHVyZSwgdGhlIHJldHVybmVkIHByb21pc2Ugd2lsbCByZWplY3Qgd2l0aCBhbiBleGNlcHRpb25cbiAqIGFtb25nIFJlZmVyZW5jZUVycm9yICh1bmtub3duIG1ldGhvZCksIFR5cGVFcnJvciAod3JvbmcgYXJndW1lbnQgY291bnQgb3IgdHlwZSksXG4gKiB7QGxpbmsgUnVudGltZUVycm9yfSAobWV0aG9kIHJldHVybmVkIGFuIGVycm9yKSwgb3Igb3RoZXIgKG5ldHdvcmsgb3IgaW50ZXJuYWwgZXJyb3JzKS5cbiAqIFRoZSBleGNlcHRpb24gbWlnaHQgaGF2ZSBhIFwiY2F1c2VcIiBmaWVsZCB3aXRoIHRoZSB2YWx1ZSByZXR1cm5lZFxuICogYnkgdGhlIGFwcGxpY2F0aW9uLSBvciBzZXJ2aWNlLWxldmVsIGVycm9yIG1hcnNoYWxpbmcgZnVuY3Rpb25zLlxuICpcbiAqIEBwYXJhbSBvcHRpb25zIC0gQSBtZXRob2QgY2FsbCBkZXNjcmlwdG9yLlxuICogQHJldHVybnMgVGhlIHJlc3VsdCBvZiB0aGUgY2FsbC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIENhbGwob3B0aW9uczogQ2FsbE9wdGlvbnMpOiBDYW5jZWxsYWJsZVByb21pc2U8YW55PiB7XG4gICAgY29uc3QgaWQgPSBnZW5lcmF0ZUlEKCk7XG5cbiAgICBjb25zdCByZXN1bHQgPSBDYW5jZWxsYWJsZVByb21pc2Uud2l0aFJlc29sdmVyczxhbnk+KCk7XG4gICAgY2FsbFJlc3BvbnNlcy5zZXQoaWQsIHsgcmVzb2x2ZTogcmVzdWx0LnJlc29sdmUsIHJlamVjdDogcmVzdWx0LnJlamVjdCB9KTtcblxuICAgIGNvbnN0IHJlcXVlc3QgPSBjYWxsKENhbGxCaW5kaW5nLCBPYmplY3QuYXNzaWduKHsgXCJjYWxsLWlkXCI6IGlkIH0sIG9wdGlvbnMpKTtcbiAgICBsZXQgcnVubmluZyA9IHRydWU7XG5cbiAgICByZXF1ZXN0LnRoZW4oKHJlcykgPT4ge1xuICAgICAgICBydW5uaW5nID0gZmFsc2U7XG4gICAgICAgIGNhbGxSZXNwb25zZXMuZGVsZXRlKGlkKTtcbiAgICAgICAgcmVzdWx0LnJlc29sdmUocmVzKTtcbiAgICB9LCAoZXJyKSA9PiB7XG4gICAgICAgIHJ1bm5pbmcgPSBmYWxzZTtcbiAgICAgICAgY2FsbFJlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgICAgICByZXN1bHQucmVqZWN0KGVycik7XG4gICAgfSk7XG5cbiAgICBjb25zdCBjYW5jZWwgPSAoKSA9PiB7XG4gICAgICAgIGNhbGxSZXNwb25zZXMuZGVsZXRlKGlkKTtcbiAgICAgICAgcmV0dXJuIGNhbmNlbENhbGwoQ2FuY2VsTWV0aG9kLCB7XCJjYWxsLWlkXCI6IGlkfSkuY2F0Y2goKGVycikgPT4ge1xuICAgICAgICAgICAgY29uc29sZS5lcnJvcihcIkVycm9yIHdoaWxlIHJlcXVlc3RpbmcgYmluZGluZyBjYWxsIGNhbmNlbGxhdGlvbjpcIiwgZXJyKTtcbiAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIHJlc3VsdC5vbmNhbmNlbGxlZCA9ICgpID0+IHtcbiAgICAgICAgaWYgKHJ1bm5pbmcpIHtcbiAgICAgICAgICAgIHJldHVybiBjYW5jZWwoKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHJldHVybiByZXF1ZXN0LnRoZW4oY2FuY2VsKTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICByZXR1cm4gcmVzdWx0LnByb21pc2U7XG59XG5cbi8qKlxuICogQ2FsbHMgYSBib3VuZCBtZXRob2QgYnkgbmFtZSB3aXRoIHRoZSBzcGVjaWZpZWQgYXJndW1lbnRzLlxuICogU2VlIHtAbGluayBDYWxsfSBmb3IgZGV0YWlscy5cbiAqXG4gKiBAcGFyYW0gbWV0aG9kTmFtZSAtIFRoZSBuYW1lIG9mIHRoZSBtZXRob2QgaW4gdGhlIGZvcm1hdCAncGFja2FnZS5zdHJ1Y3QubWV0aG9kJy5cbiAqIEBwYXJhbSBhcmdzIC0gVGhlIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSBtZXRob2QuXG4gKiBAcmV0dXJucyBUaGUgcmVzdWx0IG9mIHRoZSBtZXRob2QgY2FsbC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEJ5TmFtZShtZXRob2ROYW1lOiBzdHJpbmcsIC4uLmFyZ3M6IGFueVtdKTogQ2FuY2VsbGFibGVQcm9taXNlPGFueT4ge1xuICAgIHJldHVybiBDYWxsKHsgbWV0aG9kTmFtZSwgYXJncyB9KTtcbn1cblxuLyoqXG4gKiBDYWxscyBhIG1ldGhvZCBieSBpdHMgbnVtZXJpYyBJRCB3aXRoIHRoZSBzcGVjaWZpZWQgYXJndW1lbnRzLlxuICogU2VlIHtAbGluayBDYWxsfSBmb3IgZGV0YWlscy5cbiAqXG4gKiBAcGFyYW0gbWV0aG9kSUQgLSBUaGUgSUQgb2YgdGhlIG1ldGhvZCB0byBjYWxsLlxuICogQHBhcmFtIGFyZ3MgLSBUaGUgYXJndW1lbnRzIHRvIHBhc3MgdG8gdGhlIG1ldGhvZC5cbiAqIEByZXR1cm4gVGhlIHJlc3VsdCBvZiB0aGUgbWV0aG9kIGNhbGwuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBCeUlEKG1ldGhvZElEOiBudW1iZXIsIC4uLmFyZ3M6IGFueVtdKTogQ2FuY2VsbGFibGVQcm9taXNlPGFueT4ge1xuICAgIHJldHVybiBDYWxsKHsgbWV0aG9kSUQsIGFyZ3MgfSk7XG59XG4iLCAiLy8gU291cmNlOiBodHRwczovL2dpdGh1Yi5jb20vaW5zcGVjdC1qcy9pcy1jYWxsYWJsZVxuXG4vLyBUaGUgTUlUIExpY2Vuc2UgKE1JVClcbi8vXG4vLyBDb3B5cmlnaHQgKGMpIDIwMTUgSm9yZGFuIEhhcmJhbmRcbi8vXG4vLyBQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5XG4vLyBvZiB0aGlzIHNvZnR3YXJlIGFuZCBhc3NvY2lhdGVkIGRvY3VtZW50YXRpb24gZmlsZXMgKHRoZSBcIlNvZnR3YXJlXCIpLCB0byBkZWFsXG4vLyBpbiB0aGUgU29mdHdhcmUgd2l0aG91dCByZXN0cmljdGlvbiwgaW5jbHVkaW5nIHdpdGhvdXQgbGltaXRhdGlvbiB0aGUgcmlnaHRzXG4vLyB0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsXG4vLyBjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXNcbi8vIGZ1cm5pc2hlZCB0byBkbyBzbywgc3ViamVjdCB0byB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnM6XG4vL1xuLy8gVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsXG4vLyBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLlxuLy9cbi8vIFRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1Jcbi8vIElNUExJRUQsIElOQ0xVRElORyBCVVQgTk9UIExJTUlURUQgVE8gVEhFIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZLFxuLy8gRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFXG4vLyBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSXG4vLyBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBUT1JUIE9SIE9USEVSV0lTRSwgQVJJU0lORyBGUk9NLFxuLy8gT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEVcbi8vIFNPRlRXQVJFLlxuXG52YXIgZm5Ub1N0ciA9IEZ1bmN0aW9uLnByb3RvdHlwZS50b1N0cmluZztcbnZhciByZWZsZWN0QXBwbHk6IHR5cGVvZiBSZWZsZWN0LmFwcGx5IHwgZmFsc2UgfCBudWxsID0gdHlwZW9mIFJlZmxlY3QgPT09ICdvYmplY3QnICYmIFJlZmxlY3QgIT09IG51bGwgJiYgUmVmbGVjdC5hcHBseTtcbnZhciBiYWRBcnJheUxpa2U6IGFueTtcbnZhciBpc0NhbGxhYmxlTWFya2VyOiBhbnk7XG5pZiAodHlwZW9mIHJlZmxlY3RBcHBseSA9PT0gJ2Z1bmN0aW9uJyAmJiB0eXBlb2YgT2JqZWN0LmRlZmluZVByb3BlcnR5ID09PSAnZnVuY3Rpb24nKSB7XG4gICAgdHJ5IHtcbiAgICAgICAgYmFkQXJyYXlMaWtlID0gT2JqZWN0LmRlZmluZVByb3BlcnR5KHt9LCAnbGVuZ3RoJywge1xuICAgICAgICAgICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgaXNDYWxsYWJsZU1hcmtlcjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICAgIGlzQ2FsbGFibGVNYXJrZXIgPSB7fTtcbiAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLXRocm93LWxpdGVyYWxcbiAgICAgICAgcmVmbGVjdEFwcGx5KGZ1bmN0aW9uICgpIHsgdGhyb3cgNDI7IH0sIG51bGwsIGJhZEFycmF5TGlrZSk7XG4gICAgfSBjYXRjaCAoXykge1xuICAgICAgICBpZiAoXyAhPT0gaXNDYWxsYWJsZU1hcmtlcikge1xuICAgICAgICAgICAgcmVmbGVjdEFwcGx5ID0gbnVsbDtcbiAgICAgICAgfVxuICAgIH1cbn0gZWxzZSB7XG4gICAgcmVmbGVjdEFwcGx5ID0gbnVsbDtcbn1cblxudmFyIGNvbnN0cnVjdG9yUmVnZXggPSAvXlxccypjbGFzc1xcYi87XG52YXIgaXNFUzZDbGFzc0ZuID0gZnVuY3Rpb24gaXNFUzZDbGFzc0Z1bmN0aW9uKHZhbHVlOiBhbnkpOiBib29sZWFuIHtcbiAgICB0cnkge1xuICAgICAgICB2YXIgZm5TdHIgPSBmblRvU3RyLmNhbGwodmFsdWUpO1xuICAgICAgICByZXR1cm4gY29uc3RydWN0b3JSZWdleC50ZXN0KGZuU3RyKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTsgLy8gbm90IGEgZnVuY3Rpb25cbiAgICB9XG59O1xuXG52YXIgdHJ5RnVuY3Rpb25PYmplY3QgPSBmdW5jdGlvbiB0cnlGdW5jdGlvblRvU3RyKHZhbHVlOiBhbnkpOiBib29sZWFuIHtcbiAgICB0cnkge1xuICAgICAgICBpZiAoaXNFUzZDbGFzc0ZuKHZhbHVlKSkgeyByZXR1cm4gZmFsc2U7IH1cbiAgICAgICAgZm5Ub1N0ci5jYWxsKHZhbHVlKTtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxufTtcbnZhciB0b1N0ciA9IE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmc7XG52YXIgb2JqZWN0Q2xhc3MgPSAnW29iamVjdCBPYmplY3RdJztcbnZhciBmbkNsYXNzID0gJ1tvYmplY3QgRnVuY3Rpb25dJztcbnZhciBnZW5DbGFzcyA9ICdbb2JqZWN0IEdlbmVyYXRvckZ1bmN0aW9uXSc7XG52YXIgZGRhQ2xhc3MgPSAnW29iamVjdCBIVE1MQWxsQ29sbGVjdGlvbl0nOyAvLyBJRSAxMVxudmFyIGRkYUNsYXNzMiA9ICdbb2JqZWN0IEhUTUwgZG9jdW1lbnQuYWxsIGNsYXNzXSc7XG52YXIgZGRhQ2xhc3MzID0gJ1tvYmplY3QgSFRNTENvbGxlY3Rpb25dJzsgLy8gSUUgOS0xMFxudmFyIGhhc1RvU3RyaW5nVGFnID0gdHlwZW9mIFN5bWJvbCA9PT0gJ2Z1bmN0aW9uJyAmJiAhIVN5bWJvbC50b1N0cmluZ1RhZzsgLy8gYmV0dGVyOiB1c2UgYGhhcy10b3N0cmluZ3RhZ2BcblxudmFyIGlzSUU2OCA9ICEoMCBpbiBbLF0pOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIG5vLXNwYXJzZS1hcnJheXMsIGNvbW1hLXNwYWNpbmdcblxudmFyIGlzRERBOiAodmFsdWU6IGFueSkgPT4gYm9vbGVhbiA9IGZ1bmN0aW9uIGlzRG9jdW1lbnREb3RBbGwoKSB7IHJldHVybiBmYWxzZTsgfTtcbmlmICh0eXBlb2YgZG9jdW1lbnQgPT09ICdvYmplY3QnKSB7XG4gICAgLy8gRmlyZWZveCAzIGNhbm9uaWNhbGl6ZXMgRERBIHRvIHVuZGVmaW5lZCB3aGVuIGl0J3Mgbm90IGFjY2Vzc2VkIGRpcmVjdGx5XG4gICAgdmFyIGFsbCA9IGRvY3VtZW50LmFsbDtcbiAgICBpZiAodG9TdHIuY2FsbChhbGwpID09PSB0b1N0ci5jYWxsKGRvY3VtZW50LmFsbCkpIHtcbiAgICAgICAgaXNEREEgPSBmdW5jdGlvbiBpc0RvY3VtZW50RG90QWxsKHZhbHVlKSB7XG4gICAgICAgICAgICAvKiBnbG9iYWxzIGRvY3VtZW50OiBmYWxzZSAqL1xuICAgICAgICAgICAgLy8gaW4gSUUgNi04LCB0eXBlb2YgZG9jdW1lbnQuYWxsIGlzIFwib2JqZWN0XCIgYW5kIGl0J3MgdHJ1dGh5XG4gICAgICAgICAgICBpZiAoKGlzSUU2OCB8fCAhdmFsdWUpICYmICh0eXBlb2YgdmFsdWUgPT09ICd1bmRlZmluZWQnIHx8IHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcpKSB7XG4gICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgdmFyIHN0ciA9IHRvU3RyLmNhbGwodmFsdWUpO1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gKFxuICAgICAgICAgICAgICAgICAgICAgICAgc3RyID09PSBkZGFDbGFzc1xuICAgICAgICAgICAgICAgICAgICAgICAgfHwgc3RyID09PSBkZGFDbGFzczJcbiAgICAgICAgICAgICAgICAgICAgICAgIHx8IHN0ciA9PT0gZGRhQ2xhc3MzIC8vIG9wZXJhIDEyLjE2XG4gICAgICAgICAgICAgICAgICAgICAgICB8fCBzdHIgPT09IG9iamVjdENsYXNzIC8vIElFIDYtOFxuICAgICAgICAgICAgICAgICAgICApICYmIHZhbHVlKCcnKSA9PSBudWxsOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIGVxZXFlcVxuICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGUpIHsgLyoqLyB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH07XG4gICAgfVxufVxuXG5mdW5jdGlvbiBpc0NhbGxhYmxlUmVmQXBwbHk8VD4odmFsdWU6IFQgfCB1bmtub3duKTogdmFsdWUgaXMgKC4uLmFyZ3M6IGFueVtdKSA9PiBhbnkgIHtcbiAgICBpZiAoaXNEREEodmFsdWUpKSB7IHJldHVybiB0cnVlOyB9XG4gICAgaWYgKCF2YWx1ZSkgeyByZXR1cm4gZmFsc2U7IH1cbiAgICBpZiAodHlwZW9mIHZhbHVlICE9PSAnZnVuY3Rpb24nICYmIHR5cGVvZiB2YWx1ZSAhPT0gJ29iamVjdCcpIHsgcmV0dXJuIGZhbHNlOyB9XG4gICAgdHJ5IHtcbiAgICAgICAgKHJlZmxlY3RBcHBseSBhcyBhbnkpKHZhbHVlLCBudWxsLCBiYWRBcnJheUxpa2UpO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgaWYgKGUgIT09IGlzQ2FsbGFibGVNYXJrZXIpIHsgcmV0dXJuIGZhbHNlOyB9XG4gICAgfVxuICAgIHJldHVybiAhaXNFUzZDbGFzc0ZuKHZhbHVlKSAmJiB0cnlGdW5jdGlvbk9iamVjdCh2YWx1ZSk7XG59XG5cbmZ1bmN0aW9uIGlzQ2FsbGFibGVOb1JlZkFwcGx5PFQ+KHZhbHVlOiBUIHwgdW5rbm93bik6IHZhbHVlIGlzICguLi5hcmdzOiBhbnlbXSkgPT4gYW55IHtcbiAgICBpZiAoaXNEREEodmFsdWUpKSB7IHJldHVybiB0cnVlOyB9XG4gICAgaWYgKCF2YWx1ZSkgeyByZXR1cm4gZmFsc2U7IH1cbiAgICBpZiAodHlwZW9mIHZhbHVlICE9PSAnZnVuY3Rpb24nICYmIHR5cGVvZiB2YWx1ZSAhPT0gJ29iamVjdCcpIHsgcmV0dXJuIGZhbHNlOyB9XG4gICAgaWYgKGhhc1RvU3RyaW5nVGFnKSB7IHJldHVybiB0cnlGdW5jdGlvbk9iamVjdCh2YWx1ZSk7IH1cbiAgICBpZiAoaXNFUzZDbGFzc0ZuKHZhbHVlKSkgeyByZXR1cm4gZmFsc2U7IH1cbiAgICB2YXIgc3RyQ2xhc3MgPSB0b1N0ci5jYWxsKHZhbHVlKTtcbiAgICBpZiAoc3RyQ2xhc3MgIT09IGZuQ2xhc3MgJiYgc3RyQ2xhc3MgIT09IGdlbkNsYXNzICYmICEoL15cXFtvYmplY3QgSFRNTC8pLnRlc3Qoc3RyQ2xhc3MpKSB7IHJldHVybiBmYWxzZTsgfVxuICAgIHJldHVybiB0cnlGdW5jdGlvbk9iamVjdCh2YWx1ZSk7XG59O1xuXG5leHBvcnQgZGVmYXVsdCByZWZsZWN0QXBwbHkgPyBpc0NhbGxhYmxlUmVmQXBwbHkgOiBpc0NhbGxhYmxlTm9SZWZBcHBseTtcbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IGlzQ2FsbGFibGUgZnJvbSBcIi4vY2FsbGFibGUuanNcIjtcblxuLyoqXG4gKiBFeGNlcHRpb24gY2xhc3MgdGhhdCB3aWxsIGJlIHVzZWQgYXMgcmVqZWN0aW9uIHJlYXNvblxuICogaW4gY2FzZSBhIHtAbGluayBDYW5jZWxsYWJsZVByb21pc2V9IGlzIGNhbmNlbGxlZCBzdWNjZXNzZnVsbHkuXG4gKlxuICogVGhlIHZhbHVlIG9mIHRoZSB7QGxpbmsgbmFtZX0gcHJvcGVydHkgaXMgdGhlIHN0cmluZyBgXCJDYW5jZWxFcnJvclwiYC5cbiAqIFRoZSB2YWx1ZSBvZiB0aGUge0BsaW5rIGNhdXNlfSBwcm9wZXJ0eSBpcyB0aGUgY2F1c2UgcGFzc2VkIHRvIHRoZSBjYW5jZWwgbWV0aG9kLCBpZiBhbnkuXG4gKi9cbmV4cG9ydCBjbGFzcyBDYW5jZWxFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgICAvKipcbiAgICAgKiBDb25zdHJ1Y3RzIGEgbmV3IGBDYW5jZWxFcnJvcmAgaW5zdGFuY2UuXG4gICAgICogQHBhcmFtIG1lc3NhZ2UgLSBUaGUgZXJyb3IgbWVzc2FnZS5cbiAgICAgKiBAcGFyYW0gb3B0aW9ucyAtIE9wdGlvbnMgdG8gYmUgZm9yd2FyZGVkIHRvIHRoZSBFcnJvciBjb25zdHJ1Y3Rvci5cbiAgICAgKi9cbiAgICBjb25zdHJ1Y3RvcihtZXNzYWdlPzogc3RyaW5nLCBvcHRpb25zPzogRXJyb3JPcHRpb25zKSB7XG4gICAgICAgIHN1cGVyKG1lc3NhZ2UsIG9wdGlvbnMpO1xuICAgICAgICB0aGlzLm5hbWUgPSBcIkNhbmNlbEVycm9yXCI7XG4gICAgfVxufVxuXG4vKipcbiAqIEV4Y2VwdGlvbiBjbGFzcyB0aGF0IHdpbGwgYmUgcmVwb3J0ZWQgYXMgYW4gdW5oYW5kbGVkIHJlamVjdGlvblxuICogaW4gY2FzZSBhIHtAbGluayBDYW5jZWxsYWJsZVByb21pc2V9IHJlamVjdHMgYWZ0ZXIgYmVpbmcgY2FuY2VsbGVkLFxuICogb3Igd2hlbiB0aGUgYG9uY2FuY2VsbGVkYCBjYWxsYmFjayB0aHJvd3Mgb3IgcmVqZWN0cy5cbiAqXG4gKiBUaGUgdmFsdWUgb2YgdGhlIHtAbGluayBuYW1lfSBwcm9wZXJ0eSBpcyB0aGUgc3RyaW5nIGBcIkNhbmNlbGxlZFJlamVjdGlvbkVycm9yXCJgLlxuICogVGhlIHZhbHVlIG9mIHRoZSB7QGxpbmsgY2F1c2V9IHByb3BlcnR5IGlzIHRoZSByZWFzb24gdGhlIHByb21pc2UgcmVqZWN0ZWQgd2l0aC5cbiAqXG4gKiBCZWNhdXNlIHRoZSBvcmlnaW5hbCBwcm9taXNlIHdhcyBjYW5jZWxsZWQsXG4gKiBhIHdyYXBwZXIgcHJvbWlzZSB3aWxsIGJlIHBhc3NlZCB0byB0aGUgdW5oYW5kbGVkIHJlamVjdGlvbiBsaXN0ZW5lciBpbnN0ZWFkLlxuICogVGhlIHtAbGluayBwcm9taXNlfSBwcm9wZXJ0eSBob2xkcyBhIHJlZmVyZW5jZSB0byB0aGUgb3JpZ2luYWwgcHJvbWlzZS5cbiAqL1xuZXhwb3J0IGNsYXNzIENhbmNlbGxlZFJlamVjdGlvbkVycm9yIGV4dGVuZHMgRXJyb3Ige1xuICAgIC8qKlxuICAgICAqIEhvbGRzIGEgcmVmZXJlbmNlIHRvIHRoZSBwcm9taXNlIHRoYXQgd2FzIGNhbmNlbGxlZCBhbmQgdGhlbiByZWplY3RlZC5cbiAgICAgKi9cbiAgICBwcm9taXNlOiBDYW5jZWxsYWJsZVByb21pc2U8dW5rbm93bj47XG5cbiAgICAvKipcbiAgICAgKiBDb25zdHJ1Y3RzIGEgbmV3IGBDYW5jZWxsZWRSZWplY3Rpb25FcnJvcmAgaW5zdGFuY2UuXG4gICAgICogQHBhcmFtIHByb21pc2UgLSBUaGUgcHJvbWlzZSB0aGF0IGNhdXNlZCB0aGUgZXJyb3Igb3JpZ2luYWxseS5cbiAgICAgKiBAcGFyYW0gcmVhc29uIC0gVGhlIHJlamVjdGlvbiByZWFzb24uXG4gICAgICogQHBhcmFtIGluZm8gLSBBbiBvcHRpb25hbCBpbmZvcm1hdGl2ZSBtZXNzYWdlIHNwZWNpZnlpbmcgdGhlIGNpcmN1bXN0YW5jZXMgaW4gd2hpY2ggdGhlIGVycm9yIHdhcyB0aHJvd24uXG4gICAgICogICAgICAgICAgICAgICBEZWZhdWx0cyB0byB0aGUgc3RyaW5nIGBcIlVuaGFuZGxlZCByZWplY3Rpb24gaW4gY2FuY2VsbGVkIHByb21pc2UuXCJgLlxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKHByb21pc2U6IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPiwgcmVhc29uPzogYW55LCBpbmZvPzogc3RyaW5nKSB7XG4gICAgICAgIHN1cGVyKChpbmZvID8/IFwiVW5oYW5kbGVkIHJlamVjdGlvbiBpbiBjYW5jZWxsZWQgcHJvbWlzZS5cIikgKyBcIiBSZWFzb246IFwiICsgZXJyb3JNZXNzYWdlKHJlYXNvbiksIHsgY2F1c2U6IHJlYXNvbiB9KTtcbiAgICAgICAgdGhpcy5wcm9taXNlID0gcHJvbWlzZTtcbiAgICAgICAgdGhpcy5uYW1lID0gXCJDYW5jZWxsZWRSZWplY3Rpb25FcnJvclwiO1xuICAgIH1cbn1cblxudHlwZSBDYW5jZWxsYWJsZVByb21pc2VSZXNvbHZlcjxUPiA9ICh2YWx1ZTogVCB8IFByb21pc2VMaWtlPFQ+IHwgQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUPikgPT4gdm9pZDtcbnR5cGUgQ2FuY2VsbGFibGVQcm9taXNlUmVqZWN0b3IgPSAocmVhc29uPzogYW55KSA9PiB2b2lkO1xudHlwZSBDYW5jZWxsYWJsZVByb21pc2VDYW5jZWxsZXIgPSAoY2F1c2U/OiBhbnkpID0+IHZvaWQgfCBQcm9taXNlTGlrZTx2b2lkPjtcbnR5cGUgQ2FuY2VsbGFibGVQcm9taXNlRXhlY3V0b3I8VD4gPSAocmVzb2x2ZTogQ2FuY2VsbGFibGVQcm9taXNlUmVzb2x2ZXI8VD4sIHJlamVjdDogQ2FuY2VsbGFibGVQcm9taXNlUmVqZWN0b3IpID0+IHZvaWQ7XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUPiB7XG4gICAgdGhlbjxUUmVzdWx0MSA9IFQsIFRSZXN1bHQyID0gbmV2ZXI+KG9uZnVsZmlsbGVkPzogKCh2YWx1ZTogVCkgPT4gVFJlc3VsdDEgfCBQcm9taXNlTGlrZTxUUmVzdWx0MT4gfCBDYW5jZWxsYWJsZVByb21pc2VMaWtlPFRSZXN1bHQxPikgfCB1bmRlZmluZWQgfCBudWxsLCBvbnJlamVjdGVkPzogKChyZWFzb246IGFueSkgPT4gVFJlc3VsdDIgfCBQcm9taXNlTGlrZTxUUmVzdWx0Mj4gfCBDYW5jZWxsYWJsZVByb21pc2VMaWtlPFRSZXN1bHQyPikgfCB1bmRlZmluZWQgfCBudWxsKTogQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUUmVzdWx0MSB8IFRSZXN1bHQyPjtcbiAgICBjYW5jZWwoY2F1c2U/OiBhbnkpOiB2b2lkIHwgUHJvbWlzZUxpa2U8dm9pZD47XG59XG5cbi8qKlxuICogV3JhcHMgYSBjYW5jZWxsYWJsZSBwcm9taXNlIGFsb25nIHdpdGggaXRzIHJlc29sdXRpb24gbWV0aG9kcy5cbiAqIFRoZSBgb25jYW5jZWxsZWRgIGZpZWxkIHdpbGwgYmUgbnVsbCBpbml0aWFsbHkgYnV0IG1heSBiZSBzZXQgdG8gcHJvdmlkZSBhIGN1c3RvbSBjYW5jZWxsYXRpb24gZnVuY3Rpb24uXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQ2FuY2VsbGFibGVQcm9taXNlV2l0aFJlc29sdmVyczxUPiB7XG4gICAgcHJvbWlzZTogQ2FuY2VsbGFibGVQcm9taXNlPFQ+O1xuICAgIHJlc29sdmU6IENhbmNlbGxhYmxlUHJvbWlzZVJlc29sdmVyPFQ+O1xuICAgIHJlamVjdDogQ2FuY2VsbGFibGVQcm9taXNlUmVqZWN0b3I7XG4gICAgb25jYW5jZWxsZWQ6IENhbmNlbGxhYmxlUHJvbWlzZUNhbmNlbGxlciB8IG51bGw7XG59XG5cbmludGVyZmFjZSBDYW5jZWxsYWJsZVByb21pc2VTdGF0ZSB7XG4gICAgcmVhZG9ubHkgcm9vdDogQ2FuY2VsbGFibGVQcm9taXNlU3RhdGU7XG4gICAgcmVzb2x2aW5nOiBib29sZWFuO1xuICAgIHNldHRsZWQ6IGJvb2xlYW47XG4gICAgcmVhc29uPzogQ2FuY2VsRXJyb3I7XG59XG5cbi8vIFByaXZhdGUgZmllbGQgbmFtZXMuXG5jb25zdCBiYXJyaWVyU3ltID0gU3ltYm9sKFwiYmFycmllclwiKTtcbmNvbnN0IGNhbmNlbEltcGxTeW0gPSBTeW1ib2woXCJjYW5jZWxJbXBsXCIpO1xuY29uc3Qgc3BlY2llczogdHlwZW9mIFN5bWJvbC5zcGVjaWVzID0gU3ltYm9sLnNwZWNpZXMgPz8gU3ltYm9sKFwic3BlY2llc1BvbHlmaWxsXCIpO1xuXG4vKipcbiAqIEEgcHJvbWlzZSB3aXRoIGFuIGF0dGFjaGVkIG1ldGhvZCBmb3IgY2FuY2VsbGluZyBsb25nLXJ1bm5pbmcgb3BlcmF0aW9ucyAoc2VlIHtAbGluayBDYW5jZWxsYWJsZVByb21pc2UjY2FuY2VsfSkuXG4gKiBDYW5jZWxsYXRpb24gY2FuIG9wdGlvbmFsbHkgYmUgYm91bmQgdG8gYW4ge0BsaW5rIEFib3J0U2lnbmFsfVxuICogZm9yIGJldHRlciBjb21wb3NhYmlsaXR5IChzZWUge0BsaW5rIENhbmNlbGxhYmxlUHJvbWlzZSNjYW5jZWxPbn0pLlxuICpcbiAqIENhbmNlbGxpbmcgYSBwZW5kaW5nIHByb21pc2Ugd2lsbCByZXN1bHQgaW4gYW4gaW1tZWRpYXRlIHJlamVjdGlvblxuICogd2l0aCBhbiBpbnN0YW5jZSBvZiB7QGxpbmsgQ2FuY2VsRXJyb3J9IGFzIHJlYXNvbixcbiAqIGJ1dCB3aG9ldmVyIHN0YXJ0ZWQgdGhlIHByb21pc2Ugd2lsbCBiZSByZXNwb25zaWJsZVxuICogZm9yIGFjdHVhbGx5IGFib3J0aW5nIHRoZSB1bmRlcmx5aW5nIG9wZXJhdGlvbi5cbiAqIFRvIHRoaXMgcHVycG9zZSwgdGhlIGNvbnN0cnVjdG9yIGFuZCBhbGwgY2hhaW5pbmcgbWV0aG9kc1xuICogYWNjZXB0IG9wdGlvbmFsIGNhbmNlbGxhdGlvbiBjYWxsYmFja3MuXG4gKlxuICogSWYgYSBgQ2FuY2VsbGFibGVQcm9taXNlYCBzdGlsbCByZXNvbHZlcyBhZnRlciBoYXZpbmcgYmVlbiBjYW5jZWxsZWQsXG4gKiB0aGUgcmVzdWx0IHdpbGwgYmUgZGlzY2FyZGVkLiBJZiBpdCByZWplY3RzLCB0aGUgcmVhc29uXG4gKiB3aWxsIGJlIHJlcG9ydGVkIGFzIGFuIHVuaGFuZGxlZCByZWplY3Rpb24sXG4gKiB3cmFwcGVkIGluIGEge0BsaW5rIENhbmNlbGxlZFJlamVjdGlvbkVycm9yfSBpbnN0YW5jZS5cbiAqIFRvIGZhY2lsaXRhdGUgdGhlIGhhbmRsaW5nIG9mIGNhbmNlbGxhdGlvbiByZXF1ZXN0cyxcbiAqIGNhbmNlbGxlZCBgQ2FuY2VsbGFibGVQcm9taXNlYHMgd2lsbCBfbm90XyByZXBvcnQgdW5oYW5kbGVkIGBDYW5jZWxFcnJvcmBzXG4gKiB3aG9zZSBgY2F1c2VgIGZpZWxkIGlzIHRoZSBzYW1lIGFzIHRoZSBvbmUgd2l0aCB3aGljaCB0aGUgY3VycmVudCBwcm9taXNlIHdhcyBjYW5jZWxsZWQuXG4gKlxuICogQWxsIHVzdWFsIHByb21pc2UgbWV0aG9kcyBhcmUgZGVmaW5lZCBhbmQgcmV0dXJuIGEgYENhbmNlbGxhYmxlUHJvbWlzZWBcbiAqIHdob3NlIGNhbmNlbCBtZXRob2Qgd2lsbCBjYW5jZWwgdGhlIHBhcmVudCBvcGVyYXRpb24gYXMgd2VsbCwgcHJvcGFnYXRpbmcgdGhlIGNhbmNlbGxhdGlvbiByZWFzb25cbiAqIHVwd2FyZHMgdGhyb3VnaCBwcm9taXNlIGNoYWlucy5cbiAqIENvbnZlcnNlbHksIGNhbmNlbGxpbmcgYSBwcm9taXNlIHdpbGwgbm90IGF1dG9tYXRpY2FsbHkgY2FuY2VsIGRlcGVuZGVudCBwcm9taXNlcyBkb3duc3RyZWFtOlxuICogYGBgdHNcbiAqIGxldCByb290ID0gbmV3IENhbmNlbGxhYmxlUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7IC4uLiB9KTtcbiAqIGxldCBjaGlsZDEgPSByb290LnRoZW4oKCkgPT4geyAuLi4gfSk7XG4gKiBsZXQgY2hpbGQyID0gY2hpbGQxLnRoZW4oKCkgPT4geyAuLi4gfSk7XG4gKiBsZXQgY2hpbGQzID0gcm9vdC5jYXRjaCgoKSA9PiB7IC4uLiB9KTtcbiAqIGNoaWxkMS5jYW5jZWwoKTsgLy8gQ2FuY2VscyBjaGlsZDEgYW5kIHJvb3QsIGJ1dCBub3QgY2hpbGQyIG9yIGNoaWxkM1xuICogYGBgXG4gKiBDYW5jZWxsaW5nIGEgcHJvbWlzZSB0aGF0IGhhcyBhbHJlYWR5IHNldHRsZWQgaXMgc2FmZSBhbmQgaGFzIG5vIGNvbnNlcXVlbmNlLlxuICpcbiAqIFRoZSBgY2FuY2VsYCBtZXRob2QgcmV0dXJucyBhIHByb21pc2UgdGhhdCBfYWx3YXlzIGZ1bGZpbGxzX1xuICogYWZ0ZXIgdGhlIHdob2xlIGNoYWluIGhhcyBwcm9jZXNzZWQgdGhlIGNhbmNlbCByZXF1ZXN0XG4gKiBhbmQgYWxsIGF0dGFjaGVkIGNhbGxiYWNrcyB1cCB0byB0aGF0IG1vbWVudCBoYXZlIHJ1bi5cbiAqXG4gKiBBbGwgRVMyMDI0IHByb21pc2UgbWV0aG9kcyAoc3RhdGljIGFuZCBpbnN0YW5jZSkgYXJlIGRlZmluZWQgb24gQ2FuY2VsbGFibGVQcm9taXNlLFxuICogYnV0IGFjdHVhbCBhdmFpbGFiaWxpdHkgbWF5IHZhcnkgd2l0aCBPUy93ZWJ2aWV3IHZlcnNpb24uXG4gKlxuICogSW4gbGluZSB3aXRoIHRoZSBwcm9wb3NhbCBhdCBodHRwczovL2dpdGh1Yi5jb20vdGMzOS9wcm9wb3NhbC1ybS1idWlsdGluLXN1YmNsYXNzaW5nLFxuICogYENhbmNlbGxhYmxlUHJvbWlzZWAgZG9lcyBub3Qgc3VwcG9ydCB0cmFuc3BhcmVudCBzdWJjbGFzc2luZy5cbiAqIEV4dGVuZGVycyBzaG91bGQgdGFrZSBjYXJlIHRvIHByb3ZpZGUgdGhlaXIgb3duIG1ldGhvZCBpbXBsZW1lbnRhdGlvbnMuXG4gKiBUaGlzIG1pZ2h0IGJlIHJlY29uc2lkZXJlZCBpbiBjYXNlIHRoZSBwcm9wb3NhbCBpcyByZXRpcmVkLlxuICpcbiAqIENhbmNlbGxhYmxlUHJvbWlzZSBpcyBhIHdyYXBwZXIgYXJvdW5kIHRoZSBET00gUHJvbWlzZSBvYmplY3RcbiAqIGFuZCBpcyBjb21wbGlhbnQgd2l0aCB0aGUgW1Byb21pc2VzL0ErIHNwZWNpZmljYXRpb25dKGh0dHBzOi8vcHJvbWlzZXNhcGx1cy5jb20vKVxuICogKGl0IHBhc3NlcyB0aGUgW2NvbXBsaWFuY2Ugc3VpdGVdKGh0dHBzOi8vZ2l0aHViLmNvbS9wcm9taXNlcy1hcGx1cy9wcm9taXNlcy10ZXN0cykpXG4gKiBpZiBzbyBpcyB0aGUgdW5kZXJseWluZyBpbXBsZW1lbnRhdGlvbi5cbiAqL1xuZXhwb3J0IGNsYXNzIENhbmNlbGxhYmxlUHJvbWlzZTxUPiBleHRlbmRzIFByb21pc2U8VD4gaW1wbGVtZW50cyBQcm9taXNlTGlrZTxUPiwgQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUPiB7XG4gICAgLy8gUHJpdmF0ZSBmaWVsZHMuXG4gICAgLyoqIEBpbnRlcm5hbCAqL1xuICAgIHByaXZhdGUgW2JhcnJpZXJTeW1dITogUGFydGlhbDxQcm9taXNlV2l0aFJlc29sdmVyczx2b2lkPj4gfCBudWxsO1xuICAgIC8qKiBAaW50ZXJuYWwgKi9cbiAgICBwcml2YXRlIHJlYWRvbmx5IFtjYW5jZWxJbXBsU3ltXSE6IChyZWFzb246IENhbmNlbEVycm9yKSA9PiB2b2lkIHwgUHJvbWlzZUxpa2U8dm9pZD47XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IGBDYW5jZWxsYWJsZVByb21pc2VgLlxuICAgICAqXG4gICAgICogQHBhcmFtIGV4ZWN1dG9yIC0gQSBjYWxsYmFjayB1c2VkIHRvIGluaXRpYWxpemUgdGhlIHByb21pc2UuIFRoaXMgY2FsbGJhY2sgaXMgcGFzc2VkIHR3byBhcmd1bWVudHM6XG4gICAgICogICAgICAgICAgICAgICAgICAgYSBgcmVzb2x2ZWAgY2FsbGJhY2sgdXNlZCB0byByZXNvbHZlIHRoZSBwcm9taXNlIHdpdGggYSB2YWx1ZVxuICAgICAqICAgICAgICAgICAgICAgICAgIG9yIHRoZSByZXN1bHQgb2YgYW5vdGhlciBwcm9taXNlIChwb3NzaWJseSBjYW5jZWxsYWJsZSksXG4gICAgICogICAgICAgICAgICAgICAgICAgYW5kIGEgYHJlamVjdGAgY2FsbGJhY2sgdXNlZCB0byByZWplY3QgdGhlIHByb21pc2Ugd2l0aCBhIHByb3ZpZGVkIHJlYXNvbiBvciBlcnJvci5cbiAgICAgKiAgICAgICAgICAgICAgICAgICBJZiB0aGUgdmFsdWUgcHJvdmlkZWQgdG8gdGhlIGByZXNvbHZlYCBjYWxsYmFjayBpcyBhIHRoZW5hYmxlIF9hbmRfIGNhbmNlbGxhYmxlIG9iamVjdFxuICAgICAqICAgICAgICAgICAgICAgICAgIChpdCBoYXMgYSBgdGhlbmAgX2FuZF8gYSBgY2FuY2VsYCBtZXRob2QpLFxuICAgICAqICAgICAgICAgICAgICAgICAgIGNhbmNlbGxhdGlvbiByZXF1ZXN0cyB3aWxsIGJlIGZvcndhcmRlZCB0byB0aGF0IG9iamVjdCBhbmQgdGhlIG9uY2FuY2VsbGVkIHdpbGwgbm90IGJlIGludm9rZWQgYW55bW9yZS5cbiAgICAgKiAgICAgICAgICAgICAgICAgICBJZiBhbnkgb25lIG9mIHRoZSB0d28gY2FsbGJhY2tzIGlzIGNhbGxlZCBfYWZ0ZXJfIHRoZSBwcm9taXNlIGhhcyBiZWVuIGNhbmNlbGxlZCxcbiAgICAgKiAgICAgICAgICAgICAgICAgICB0aGUgcHJvdmlkZWQgdmFsdWVzIHdpbGwgYmUgY2FuY2VsbGVkIGFuZCByZXNvbHZlZCBhcyB1c3VhbCxcbiAgICAgKiAgICAgICAgICAgICAgICAgICBidXQgdGhlaXIgcmVzdWx0cyB3aWxsIGJlIGRpc2NhcmRlZC5cbiAgICAgKiAgICAgICAgICAgICAgICAgICBIb3dldmVyLCBpZiB0aGUgcmVzb2x1dGlvbiBwcm9jZXNzIHVsdGltYXRlbHkgZW5kcyB1cCBpbiBhIHJlamVjdGlvblxuICAgICAqICAgICAgICAgICAgICAgICAgIHRoYXQgaXMgbm90IGR1ZSB0byBjYW5jZWxsYXRpb24sIHRoZSByZWplY3Rpb24gcmVhc29uXG4gICAgICogICAgICAgICAgICAgICAgICAgd2lsbCBiZSB3cmFwcGVkIGluIGEge0BsaW5rIENhbmNlbGxlZFJlamVjdGlvbkVycm9yfVxuICAgICAqICAgICAgICAgICAgICAgICAgIGFuZCBidWJibGVkIHVwIGFzIGFuIHVuaGFuZGxlZCByZWplY3Rpb24uXG4gICAgICogQHBhcmFtIG9uY2FuY2VsbGVkIC0gSXQgaXMgdGhlIGNhbGxlcidzIHJlc3BvbnNpYmlsaXR5IHRvIGVuc3VyZSB0aGF0IGFueSBvcGVyYXRpb25cbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICBzdGFydGVkIGJ5IHRoZSBleGVjdXRvciBpcyBwcm9wZXJseSBoYWx0ZWQgdXBvbiBjYW5jZWxsYXRpb24uXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgVGhpcyBvcHRpb25hbCBjYWxsYmFjayBjYW4gYmUgdXNlZCB0byB0aGF0IHB1cnBvc2UuXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgSXQgd2lsbCBiZSBjYWxsZWQgX3N5bmNocm9ub3VzbHlfIHdpdGggYSBjYW5jZWxsYXRpb24gY2F1c2VcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICB3aGVuIGNhbmNlbGxhdGlvbiBpcyByZXF1ZXN0ZWQsIF9hZnRlcl8gdGhlIHByb21pc2UgaGFzIGFscmVhZHkgcmVqZWN0ZWRcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICB3aXRoIGEge0BsaW5rIENhbmNlbEVycm9yfSwgYnV0IF9iZWZvcmVfXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgYW55IHtAbGluayB0aGVufS97QGxpbmsgY2F0Y2h9L3tAbGluayBmaW5hbGx5fSBjYWxsYmFjayBydW5zLlxuICAgICAqICAgICAgICAgICAgICAgICAgICAgIElmIHRoZSBjYWxsYmFjayByZXR1cm5zIGEgdGhlbmFibGUsIHRoZSBwcm9taXNlIHJldHVybmVkIGZyb20ge0BsaW5rIGNhbmNlbH1cbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICB3aWxsIG9ubHkgZnVsZmlsbCBhZnRlciB0aGUgZm9ybWVyIGhhcyBzZXR0bGVkLlxuICAgICAqICAgICAgICAgICAgICAgICAgICAgIFVuaGFuZGxlZCBleGNlcHRpb25zIG9yIHJlamVjdGlvbnMgZnJvbSB0aGUgY2FsbGJhY2sgd2lsbCBiZSB3cmFwcGVkXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgaW4gYSB7QGxpbmsgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3J9IGFuZCBidWJibGVkIHVwIGFzIHVuaGFuZGxlZCByZWplY3Rpb25zLlxuICAgICAqICAgICAgICAgICAgICAgICAgICAgIElmIHRoZSBgcmVzb2x2ZWAgY2FsbGJhY2sgaXMgY2FsbGVkIGJlZm9yZSBjYW5jZWxsYXRpb24gd2l0aCBhIGNhbmNlbGxhYmxlIHByb21pc2UsXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgY2FuY2VsbGF0aW9uIHJlcXVlc3RzIG9uIHRoaXMgcHJvbWlzZSB3aWxsIGJlIGRpdmVydGVkIHRvIHRoYXQgcHJvbWlzZSxcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICBhbmQgdGhlIG9yaWdpbmFsIGBvbmNhbmNlbGxlZGAgY2FsbGJhY2sgd2lsbCBiZSBkaXNjYXJkZWQuXG4gICAgICovXG4gICAgY29uc3RydWN0b3IoZXhlY3V0b3I6IENhbmNlbGxhYmxlUHJvbWlzZUV4ZWN1dG9yPFQ+LCBvbmNhbmNlbGxlZD86IENhbmNlbGxhYmxlUHJvbWlzZUNhbmNlbGxlcikge1xuICAgICAgICBsZXQgcmVzb2x2ZSE6ICh2YWx1ZTogVCB8IFByb21pc2VMaWtlPFQ+KSA9PiB2b2lkO1xuICAgICAgICBsZXQgcmVqZWN0ITogKHJlYXNvbj86IGFueSkgPT4gdm9pZDtcbiAgICAgICAgc3VwZXIoKHJlcywgcmVqKSA9PiB7IHJlc29sdmUgPSByZXM7IHJlamVjdCA9IHJlajsgfSk7XG5cbiAgICAgICAgaWYgKCh0aGlzLmNvbnN0cnVjdG9yIGFzIGFueSlbc3BlY2llc10gIT09IFByb21pc2UpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJDYW5jZWxsYWJsZVByb21pc2UgZG9lcyBub3Qgc3VwcG9ydCB0cmFuc3BhcmVudCBzdWJjbGFzc2luZy4gUGxlYXNlIHJlZnJhaW4gZnJvbSBvdmVycmlkaW5nIHRoZSBbU3ltYm9sLnNwZWNpZXNdIHN0YXRpYyBwcm9wZXJ0eS5cIik7XG4gICAgICAgIH1cblxuICAgICAgICBsZXQgcHJvbWlzZTogQ2FuY2VsbGFibGVQcm9taXNlV2l0aFJlc29sdmVyczxUPiA9IHtcbiAgICAgICAgICAgIHByb21pc2U6IHRoaXMsXG4gICAgICAgICAgICByZXNvbHZlLFxuICAgICAgICAgICAgcmVqZWN0LFxuICAgICAgICAgICAgZ2V0IG9uY2FuY2VsbGVkKCkgeyByZXR1cm4gb25jYW5jZWxsZWQgPz8gbnVsbDsgfSxcbiAgICAgICAgICAgIHNldCBvbmNhbmNlbGxlZChjYikgeyBvbmNhbmNlbGxlZCA9IGNiID8/IHVuZGVmaW5lZDsgfVxuICAgICAgICB9O1xuXG4gICAgICAgIGNvbnN0IHN0YXRlOiBDYW5jZWxsYWJsZVByb21pc2VTdGF0ZSA9IHtcbiAgICAgICAgICAgIGdldCByb290KCkgeyByZXR1cm4gc3RhdGU7IH0sXG4gICAgICAgICAgICByZXNvbHZpbmc6IGZhbHNlLFxuICAgICAgICAgICAgc2V0dGxlZDogZmFsc2VcbiAgICAgICAgfTtcblxuICAgICAgICAvLyBTZXR1cCBjYW5jZWxsYXRpb24gc3lzdGVtLlxuICAgICAgICB2b2lkIE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHRoaXMsIHtcbiAgICAgICAgICAgIFtiYXJyaWVyU3ltXToge1xuICAgICAgICAgICAgICAgIGNvbmZpZ3VyYWJsZTogZmFsc2UsXG4gICAgICAgICAgICAgICAgZW51bWVyYWJsZTogZmFsc2UsXG4gICAgICAgICAgICAgICAgd3JpdGFibGU6IHRydWUsXG4gICAgICAgICAgICAgICAgdmFsdWU6IG51bGxcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBbY2FuY2VsSW1wbFN5bV06IHtcbiAgICAgICAgICAgICAgICBjb25maWd1cmFibGU6IGZhbHNlLFxuICAgICAgICAgICAgICAgIGVudW1lcmFibGU6IGZhbHNlLFxuICAgICAgICAgICAgICAgIHdyaXRhYmxlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICB2YWx1ZTogY2FuY2VsbGVyRm9yKHByb21pc2UsIHN0YXRlKVxuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcblxuICAgICAgICAvLyBSdW4gdGhlIGFjdHVhbCBleGVjdXRvci5cbiAgICAgICAgY29uc3QgcmVqZWN0b3IgPSByZWplY3RvckZvcihwcm9taXNlLCBzdGF0ZSk7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBleGVjdXRvcihyZXNvbHZlckZvcihwcm9taXNlLCBzdGF0ZSksIHJlamVjdG9yKTtcbiAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICBpZiAoc3RhdGUucmVzb2x2aW5nKSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS5sb2coXCJVbmhhbmRsZWQgZXhjZXB0aW9uIGluIENhbmNlbGxhYmxlUHJvbWlzZSBleGVjdXRvci5cIiwgZXJyKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgcmVqZWN0b3IoZXJyKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENhbmNlbHMgaW1tZWRpYXRlbHkgdGhlIGV4ZWN1dGlvbiBvZiB0aGUgb3BlcmF0aW9uIGFzc29jaWF0ZWQgd2l0aCB0aGlzIHByb21pc2UuXG4gICAgICogVGhlIHByb21pc2UgcmVqZWN0cyB3aXRoIGEge0BsaW5rIENhbmNlbEVycm9yfSBpbnN0YW5jZSBhcyByZWFzb24sXG4gICAgICogd2l0aCB0aGUge0BsaW5rIENhbmNlbEVycm9yI2NhdXNlfSBwcm9wZXJ0eSBzZXQgdG8gdGhlIGdpdmVuIGFyZ3VtZW50LCBpZiBhbnkuXG4gICAgICpcbiAgICAgKiBIYXMgbm8gZWZmZWN0IGlmIGNhbGxlZCBhZnRlciB0aGUgcHJvbWlzZSBoYXMgYWxyZWFkeSBzZXR0bGVkO1xuICAgICAqIHJlcGVhdGVkIGNhbGxzIGluIHBhcnRpY3VsYXIgYXJlIHNhZmUsIGJ1dCBvbmx5IHRoZSBmaXJzdCBvbmVcbiAgICAgKiB3aWxsIHNldCB0aGUgY2FuY2VsbGF0aW9uIGNhdXNlLlxuICAgICAqXG4gICAgICogVGhlIGBDYW5jZWxFcnJvcmAgZXhjZXB0aW9uIF9uZWVkIG5vdF8gYmUgaGFuZGxlZCBleHBsaWNpdGx5IF9vbiB0aGUgcHJvbWlzZXMgdGhhdCBhcmUgYmVpbmcgY2FuY2VsbGVkOl9cbiAgICAgKiBjYW5jZWxsaW5nIGEgcHJvbWlzZSB3aXRoIG5vIGF0dGFjaGVkIHJlamVjdGlvbiBoYW5kbGVyIGRvZXMgbm90IHRyaWdnZXIgYW4gdW5oYW5kbGVkIHJlamVjdGlvbiBldmVudC5cbiAgICAgKiBUaGVyZWZvcmUsIHRoZSBmb2xsb3dpbmcgaWRpb21zIGFyZSBhbGwgZXF1YWxseSBjb3JyZWN0OlxuICAgICAqIGBgYHRzXG4gICAgICogbmV3IENhbmNlbGxhYmxlUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7IC4uLiB9KS5jYW5jZWwoKTtcbiAgICAgKiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsgLi4uIH0pLnRoZW4oLi4uKS5jYW5jZWwoKTtcbiAgICAgKiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsgLi4uIH0pLnRoZW4oLi4uKS5jYXRjaCguLi4pLmNhbmNlbCgpO1xuICAgICAqIGBgYFxuICAgICAqIFdoZW5ldmVyIHNvbWUgY2FuY2VsbGVkIHByb21pc2UgaW4gYSBjaGFpbiByZWplY3RzIHdpdGggYSBgQ2FuY2VsRXJyb3JgXG4gICAgICogd2l0aCB0aGUgc2FtZSBjYW5jZWxsYXRpb24gY2F1c2UgYXMgaXRzZWxmLCB0aGUgZXJyb3Igd2lsbCBiZSBkaXNjYXJkZWQgc2lsZW50bHkuXG4gICAgICogSG93ZXZlciwgdGhlIGBDYW5jZWxFcnJvcmAgX3dpbGwgc3RpbGwgYmUgZGVsaXZlcmVkXyB0byBhbGwgYXR0YWNoZWQgcmVqZWN0aW9uIGhhbmRsZXJzXG4gICAgICogYWRkZWQgYnkge0BsaW5rIHRoZW59IGFuZCByZWxhdGVkIG1ldGhvZHM6XG4gICAgICogYGBgdHNcbiAgICAgKiBsZXQgY2FuY2VsbGFibGUgPSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsgLi4uIH0pO1xuICAgICAqIGNhbmNlbGxhYmxlLnRoZW4oKCkgPT4geyAuLi4gfSkuY2F0Y2goY29uc29sZS5sb2cpO1xuICAgICAqIGNhbmNlbGxhYmxlLmNhbmNlbCgpOyAvLyBBIENhbmNlbEVycm9yIGlzIHByaW50ZWQgdG8gdGhlIGNvbnNvbGUuXG4gICAgICogYGBgXG4gICAgICogSWYgdGhlIGBDYW5jZWxFcnJvcmAgaXMgbm90IGhhbmRsZWQgZG93bnN0cmVhbSBieSB0aGUgdGltZSBpdCByZWFjaGVzXG4gICAgICogYSBfbm9uLWNhbmNlbGxlZF8gcHJvbWlzZSwgaXQgX3dpbGxfIHRyaWdnZXIgYW4gdW5oYW5kbGVkIHJlamVjdGlvbiBldmVudCxcbiAgICAgKiBqdXN0IGxpa2Ugbm9ybWFsIHJlamVjdGlvbnMgd291bGQ6XG4gICAgICogYGBgdHNcbiAgICAgKiBsZXQgY2FuY2VsbGFibGUgPSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsgLi4uIH0pO1xuICAgICAqIGxldCBjaGFpbmVkID0gY2FuY2VsbGFibGUudGhlbigoKSA9PiB7IC4uLiB9KS50aGVuKCgpID0+IHsgLi4uIH0pOyAvLyBObyBjYXRjaC4uLlxuICAgICAqIGNhbmNlbGxhYmxlLmNhbmNlbCgpOyAvLyBVbmhhbmRsZWQgcmVqZWN0aW9uIGV2ZW50IG9uIGNoYWluZWQhXG4gICAgICogYGBgXG4gICAgICogVGhlcmVmb3JlLCBpdCBpcyBpbXBvcnRhbnQgdG8gZWl0aGVyIGNhbmNlbCB3aG9sZSBwcm9taXNlIGNoYWlucyBmcm9tIHRoZWlyIHRhaWwsXG4gICAgICogYXMgc2hvd24gaW4gdGhlIGNvcnJlY3QgaWRpb21zIGFib3ZlLCBvciB0YWtlIGNhcmUgb2YgaGFuZGxpbmcgZXJyb3JzIGV2ZXJ5d2hlcmUuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBBIGNhbmNlbGxhYmxlIHByb21pc2UgdGhhdCBfZnVsZmlsbHNfIGFmdGVyIHRoZSBjYW5jZWwgY2FsbGJhY2sgKGlmIGFueSlcbiAgICAgKiBhbmQgYWxsIGhhbmRsZXJzIGF0dGFjaGVkIHVwIHRvIHRoZSBjYWxsIHRvIGNhbmNlbCBoYXZlIHJ1bi5cbiAgICAgKiBJZiB0aGUgY2FuY2VsIGNhbGxiYWNrIHJldHVybnMgYSB0aGVuYWJsZSwgdGhlIHByb21pc2UgcmV0dXJuZWQgYnkgYGNhbmNlbGBcbiAgICAgKiB3aWxsIGFsc28gd2FpdCBmb3IgdGhhdCB0aGVuYWJsZSB0byBzZXR0bGUuXG4gICAgICogVGhpcyBlbmFibGVzIGNhbGxlcnMgdG8gd2FpdCBmb3IgdGhlIGNhbmNlbGxlZCBvcGVyYXRpb24gdG8gdGVybWluYXRlXG4gICAgICogd2l0aG91dCBiZWluZyBmb3JjZWQgdG8gaGFuZGxlIHBvdGVudGlhbCBlcnJvcnMgYXQgdGhlIGNhbGwgc2l0ZS5cbiAgICAgKiBgYGB0c1xuICAgICAqIGNhbmNlbGxhYmxlLmNhbmNlbCgpLnRoZW4oKCkgPT4ge1xuICAgICAqICAgICAvLyBDbGVhbnVwIGZpbmlzaGVkLCBpdCdzIHNhZmUgdG8gZG8gc29tZXRoaW5nIGVsc2UuXG4gICAgICogfSwgKGVycikgPT4ge1xuICAgICAqICAgICAvLyBVbnJlYWNoYWJsZTogdGhlIHByb21pc2UgcmV0dXJuZWQgZnJvbSBjYW5jZWwgd2lsbCBuZXZlciByZWplY3QuXG4gICAgICogfSk7XG4gICAgICogYGBgXG4gICAgICogTm90ZSB0aGF0IHRoZSByZXR1cm5lZCBwcm9taXNlIHdpbGwgX25vdF8gaGFuZGxlIGltcGxpY2l0bHkgYW55IHJlamVjdGlvblxuICAgICAqIHRoYXQgbWlnaHQgaGF2ZSBvY2N1cnJlZCBhbHJlYWR5IGluIHRoZSBjYW5jZWxsZWQgY2hhaW4uXG4gICAgICogSXQgd2lsbCBqdXN0IHRyYWNrIHdoZXRoZXIgcmVnaXN0ZXJlZCBoYW5kbGVycyBoYXZlIGJlZW4gZXhlY3V0ZWQgb3Igbm90LlxuICAgICAqIFRoZXJlZm9yZSwgdW5oYW5kbGVkIHJlamVjdGlvbnMgd2lsbCBuZXZlciBiZSBzaWxlbnRseSBoYW5kbGVkIGJ5IGNhbGxpbmcgY2FuY2VsLlxuICAgICAqL1xuICAgIGNhbmNlbChjYXVzZT86IGFueSk6IENhbmNlbGxhYmxlUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPHZvaWQ+KChyZXNvbHZlKSA9PiB7XG4gICAgICAgICAgICAvLyBJTlZBUklBTlQ6IHRoZSByZXN1bHQgb2YgdGhpc1tjYW5jZWxJbXBsU3ltXSBhbmQgdGhlIGJhcnJpZXIgZG8gbm90IGV2ZXIgcmVqZWN0LlxuICAgICAgICAgICAgLy8gVW5mb3J0dW5hdGVseSBtYWNPUyBIaWdoIFNpZXJyYSBkb2VzIG5vdCBzdXBwb3J0IFByb21pc2UuYWxsU2V0dGxlZC5cbiAgICAgICAgICAgIFByb21pc2UuYWxsKFtcbiAgICAgICAgICAgICAgICB0aGlzW2NhbmNlbEltcGxTeW1dKG5ldyBDYW5jZWxFcnJvcihcIlByb21pc2UgY2FuY2VsbGVkLlwiLCB7IGNhdXNlIH0pKSxcbiAgICAgICAgICAgICAgICBjdXJyZW50QmFycmllcih0aGlzKVxuICAgICAgICAgICAgXSkudGhlbigoKSA9PiByZXNvbHZlKCksICgpID0+IHJlc29sdmUoKSk7XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEJpbmRzIHByb21pc2UgY2FuY2VsbGF0aW9uIHRvIHRoZSBhYm9ydCBldmVudCBvZiB0aGUgZ2l2ZW4ge0BsaW5rIEFib3J0U2lnbmFsfS5cbiAgICAgKiBJZiB0aGUgc2lnbmFsIGhhcyBhbHJlYWR5IGFib3J0ZWQsIHRoZSBwcm9taXNlIHdpbGwgYmUgY2FuY2VsbGVkIGltbWVkaWF0ZWx5LlxuICAgICAqIFdoZW4gZWl0aGVyIGNvbmRpdGlvbiBpcyB2ZXJpZmllZCwgdGhlIGNhbmNlbGxhdGlvbiBjYXVzZSB3aWxsIGJlIHNldFxuICAgICAqIHRvIHRoZSBzaWduYWwncyBhYm9ydCByZWFzb24gKHNlZSB7QGxpbmsgQWJvcnRTaWduYWwjcmVhc29ufSkuXG4gICAgICpcbiAgICAgKiBIYXMgbm8gZWZmZWN0IGlmIGNhbGxlZCAob3IgaWYgdGhlIHNpZ25hbCBhYm9ydHMpIF9hZnRlcl8gdGhlIHByb21pc2UgaGFzIGFscmVhZHkgc2V0dGxlZC5cbiAgICAgKiBPbmx5IHRoZSBmaXJzdCBzaWduYWwgdG8gYWJvcnQgd2lsbCBzZXQgdGhlIGNhbmNlbGxhdGlvbiBjYXVzZS5cbiAgICAgKlxuICAgICAqIEZvciBtb3JlIGRldGFpbHMgYWJvdXQgdGhlIGNhbmNlbGxhdGlvbiBwcm9jZXNzLFxuICAgICAqIHNlZSB7QGxpbmsgY2FuY2VsfSBhbmQgdGhlIGBDYW5jZWxsYWJsZVByb21pc2VgIGNvbnN0cnVjdG9yLlxuICAgICAqXG4gICAgICogVGhpcyBtZXRob2QgZW5hYmxlcyBgYXdhaXRgaW5nIGNhbmNlbGxhYmxlIHByb21pc2VzIHdpdGhvdXQgaGF2aW5nXG4gICAgICogdG8gc3RvcmUgdGhlbSBmb3IgZnV0dXJlIGNhbmNlbGxhdGlvbiwgZS5nLjpcbiAgICAgKiBgYGB0c1xuICAgICAqIGF3YWl0IGxvbmdSdW5uaW5nT3BlcmF0aW9uKCkuY2FuY2VsT24oc2lnbmFsKTtcbiAgICAgKiBgYGBcbiAgICAgKiBpbnN0ZWFkIG9mOlxuICAgICAqIGBgYHRzXG4gICAgICogbGV0IHByb21pc2VUb0JlQ2FuY2VsbGVkID0gbG9uZ1J1bm5pbmdPcGVyYXRpb24oKTtcbiAgICAgKiBhd2FpdCBwcm9taXNlVG9CZUNhbmNlbGxlZDtcbiAgICAgKiBgYGBcbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoaXMgcHJvbWlzZSwgZm9yIG1ldGhvZCBjaGFpbmluZy5cbiAgICAgKi9cbiAgICBjYW5jZWxPbihzaWduYWw6IEFib3J0U2lnbmFsKTogQ2FuY2VsbGFibGVQcm9taXNlPFQ+IHtcbiAgICAgICAgaWYgKHNpZ25hbC5hYm9ydGVkKSB7XG4gICAgICAgICAgICB2b2lkIHRoaXMuY2FuY2VsKHNpZ25hbC5yZWFzb24pXG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBzaWduYWwuYWRkRXZlbnRMaXN0ZW5lcignYWJvcnQnLCAoKSA9PiB2b2lkIHRoaXMuY2FuY2VsKHNpZ25hbC5yZWFzb24pLCB7Y2FwdHVyZTogdHJ1ZX0pO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQXR0YWNoZXMgY2FsbGJhY2tzIGZvciB0aGUgcmVzb2x1dGlvbiBhbmQvb3IgcmVqZWN0aW9uIG9mIHRoZSBgQ2FuY2VsbGFibGVQcm9taXNlYC5cbiAgICAgKlxuICAgICAqIFRoZSBvcHRpb25hbCBgb25jYW5jZWxsZWRgIGFyZ3VtZW50IHdpbGwgYmUgaW52b2tlZCB3aGVuIHRoZSByZXR1cm5lZCBwcm9taXNlIGlzIGNhbmNlbGxlZCxcbiAgICAgKiB3aXRoIHRoZSBzYW1lIHNlbWFudGljcyBhcyB0aGUgYG9uY2FuY2VsbGVkYCBhcmd1bWVudCBvZiB0aGUgY29uc3RydWN0b3IuXG4gICAgICogV2hlbiB0aGUgcGFyZW50IHByb21pc2UgcmVqZWN0cyBvciBpcyBjYW5jZWxsZWQsIHRoZSBgb25yZWplY3RlZGAgY2FsbGJhY2sgd2lsbCBydW4sXG4gICAgICogX2V2ZW4gYWZ0ZXIgdGhlIHJldHVybmVkIHByb21pc2UgaGFzIGJlZW4gY2FuY2VsbGVkOl9cbiAgICAgKiBpbiB0aGF0IGNhc2UsIHNob3VsZCBpdCByZWplY3Qgb3IgdGhyb3csIHRoZSByZWFzb24gd2lsbCBiZSB3cmFwcGVkXG4gICAgICogaW4gYSB7QGxpbmsgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3J9IGFuZCBidWJibGVkIHVwIGFzIGFuIHVuaGFuZGxlZCByZWplY3Rpb24uXG4gICAgICpcbiAgICAgKiBAcGFyYW0gb25mdWxmaWxsZWQgVGhlIGNhbGxiYWNrIHRvIGV4ZWN1dGUgd2hlbiB0aGUgUHJvbWlzZSBpcyByZXNvbHZlZC5cbiAgICAgKiBAcGFyYW0gb25yZWplY3RlZCBUaGUgY2FsbGJhY2sgdG8gZXhlY3V0ZSB3aGVuIHRoZSBQcm9taXNlIGlzIHJlamVjdGVkLlxuICAgICAqIEByZXR1cm5zIEEgYENhbmNlbGxhYmxlUHJvbWlzZWAgZm9yIHRoZSBjb21wbGV0aW9uIG9mIHdoaWNoZXZlciBjYWxsYmFjayBpcyBleGVjdXRlZC5cbiAgICAgKiBUaGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBob29rZWQgdXAgdG8gcHJvcGFnYXRlIGNhbmNlbGxhdGlvbiByZXF1ZXN0cyB1cCB0aGUgY2hhaW4sIGJ1dCBub3QgZG93bjpcbiAgICAgKlxuICAgICAqICAgLSBpZiB0aGUgcGFyZW50IHByb21pc2UgaXMgY2FuY2VsbGVkLCB0aGUgYG9ucmVqZWN0ZWRgIGhhbmRsZXIgd2lsbCBiZSBpbnZva2VkIHdpdGggYSBgQ2FuY2VsRXJyb3JgXG4gICAgICogICAgIGFuZCB0aGUgcmV0dXJuZWQgcHJvbWlzZSBfd2lsbCByZXNvbHZlIHJlZ3VsYXJseV8gd2l0aCBpdHMgcmVzdWx0O1xuICAgICAqICAgLSBjb252ZXJzZWx5LCBpZiB0aGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBjYW5jZWxsZWQsIF90aGUgcGFyZW50IHByb21pc2UgaXMgY2FuY2VsbGVkIHRvbztfXG4gICAgICogICAgIHRoZSBgb25yZWplY3RlZGAgaGFuZGxlciB3aWxsIHN0aWxsIGJlIGludm9rZWQgd2l0aCB0aGUgcGFyZW50J3MgYENhbmNlbEVycm9yYCxcbiAgICAgKiAgICAgYnV0IGl0cyByZXN1bHQgd2lsbCBiZSBkaXNjYXJkZWRcbiAgICAgKiAgICAgYW5kIHRoZSByZXR1cm5lZCBwcm9taXNlIHdpbGwgcmVqZWN0IHdpdGggYSBgQ2FuY2VsRXJyb3JgIGFzIHdlbGwuXG4gICAgICpcbiAgICAgKiBUaGUgcHJvbWlzZSByZXR1cm5lZCBmcm9tIHtAbGluayBjYW5jZWx9IHdpbGwgZnVsZmlsbCBvbmx5IGFmdGVyIGFsbCBhdHRhY2hlZCBoYW5kbGVyc1xuICAgICAqIHVwIHRoZSBlbnRpcmUgcHJvbWlzZSBjaGFpbiBoYXZlIGJlZW4gcnVuLlxuICAgICAqXG4gICAgICogSWYgZWl0aGVyIGNhbGxiYWNrIHJldHVybnMgYSBjYW5jZWxsYWJsZSBwcm9taXNlLFxuICAgICAqIGNhbmNlbGxhdGlvbiByZXF1ZXN0cyB3aWxsIGJlIGRpdmVydGVkIHRvIGl0LFxuICAgICAqIGFuZCB0aGUgc3BlY2lmaWVkIGBvbmNhbmNlbGxlZGAgY2FsbGJhY2sgd2lsbCBiZSBkaXNjYXJkZWQuXG4gICAgICovXG4gICAgdGhlbjxUUmVzdWx0MSA9IFQsIFRSZXN1bHQyID0gbmV2ZXI+KG9uZnVsZmlsbGVkPzogKCh2YWx1ZTogVCkgPT4gVFJlc3VsdDEgfCBQcm9taXNlTGlrZTxUUmVzdWx0MT4gfCBDYW5jZWxsYWJsZVByb21pc2VMaWtlPFRSZXN1bHQxPikgfCB1bmRlZmluZWQgfCBudWxsLCBvbnJlamVjdGVkPzogKChyZWFzb246IGFueSkgPT4gVFJlc3VsdDIgfCBQcm9taXNlTGlrZTxUUmVzdWx0Mj4gfCBDYW5jZWxsYWJsZVByb21pc2VMaWtlPFRSZXN1bHQyPikgfCB1bmRlZmluZWQgfCBudWxsLCBvbmNhbmNlbGxlZD86IENhbmNlbGxhYmxlUHJvbWlzZUNhbmNlbGxlcik6IENhbmNlbGxhYmxlUHJvbWlzZTxUUmVzdWx0MSB8IFRSZXN1bHQyPiB7XG4gICAgICAgIGlmICghKHRoaXMgaW5zdGFuY2VvZiBDYW5jZWxsYWJsZVByb21pc2UpKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFwiQ2FuY2VsbGFibGVQcm9taXNlLnByb3RvdHlwZS50aGVuIGNhbGxlZCBvbiBhbiBpbnZhbGlkIG9iamVjdC5cIik7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBOT1RFOiBUeXBlU2NyaXB0J3MgYnVpbHQtaW4gdHlwZSBmb3IgdGhlbiBpcyBicm9rZW4sXG4gICAgICAgIC8vIGFzIGl0IGFsbG93cyBzcGVjaWZ5aW5nIGFuIGFyYml0cmFyeSBUUmVzdWx0MSAhPSBUIGV2ZW4gd2hlbiBvbmZ1bGZpbGxlZCBpcyBub3QgYSBmdW5jdGlvbi5cbiAgICAgICAgLy8gV2UgY2Fubm90IGZpeCBpdCBpZiB3ZSB3YW50IHRvIENhbmNlbGxhYmxlUHJvbWlzZSB0byBpbXBsZW1lbnQgUHJvbWlzZUxpa2U8VD4uXG5cbiAgICAgICAgaWYgKCFpc0NhbGxhYmxlKG9uZnVsZmlsbGVkKSkgeyBvbmZ1bGZpbGxlZCA9IGlkZW50aXR5IGFzIGFueTsgfVxuICAgICAgICBpZiAoIWlzQ2FsbGFibGUob25yZWplY3RlZCkpIHsgb25yZWplY3RlZCA9IHRocm93ZXI7IH1cblxuICAgICAgICBpZiAob25mdWxmaWxsZWQgPT09IGlkZW50aXR5ICYmIG9ucmVqZWN0ZWQgPT0gdGhyb3dlcikge1xuICAgICAgICAgICAgLy8gU2hvcnRjdXQgZm9yIHRyaXZpYWwgYXJndW1lbnRzLlxuICAgICAgICAgICAgcmV0dXJuIG5ldyBDYW5jZWxsYWJsZVByb21pc2UoKHJlc29sdmUpID0+IHJlc29sdmUodGhpcyBhcyBhbnkpKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IGJhcnJpZXI6IFBhcnRpYWw8UHJvbWlzZVdpdGhSZXNvbHZlcnM8dm9pZD4+ID0ge307XG4gICAgICAgIHRoaXNbYmFycmllclN5bV0gPSBiYXJyaWVyO1xuXG4gICAgICAgIHJldHVybiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPFRSZXN1bHQxIHwgVFJlc3VsdDI+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIHZvaWQgc3VwZXIudGhlbihcbiAgICAgICAgICAgICAgICAodmFsdWUpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKHRoaXNbYmFycmllclN5bV0gPT09IGJhcnJpZXIpIHsgdGhpc1tiYXJyaWVyU3ltXSA9IG51bGw7IH1cbiAgICAgICAgICAgICAgICAgICAgYmFycmllci5yZXNvbHZlPy4oKTtcblxuICAgICAgICAgICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmVzb2x2ZShvbmZ1bGZpbGxlZCEodmFsdWUpKTtcbiAgICAgICAgICAgICAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZWplY3QoZXJyKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgKHJlYXNvbj8pID0+IHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKHRoaXNbYmFycmllclN5bV0gPT09IGJhcnJpZXIpIHsgdGhpc1tiYXJyaWVyU3ltXSA9IG51bGw7IH1cbiAgICAgICAgICAgICAgICAgICAgYmFycmllci5yZXNvbHZlPy4oKTtcblxuICAgICAgICAgICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmVzb2x2ZShvbnJlamVjdGVkIShyZWFzb24pKTtcbiAgICAgICAgICAgICAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZWplY3QoZXJyKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICk7XG4gICAgICAgIH0sIGFzeW5jIChjYXVzZT8pID0+IHtcbiAgICAgICAgICAgIC8vY2FuY2VsbGVkID0gdHJ1ZTtcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIG9uY2FuY2VsbGVkPy4oY2F1c2UpO1xuICAgICAgICAgICAgfSBmaW5hbGx5IHtcbiAgICAgICAgICAgICAgICBhd2FpdCB0aGlzLmNhbmNlbChjYXVzZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEF0dGFjaGVzIGEgY2FsbGJhY2sgZm9yIG9ubHkgdGhlIHJlamVjdGlvbiBvZiB0aGUgUHJvbWlzZS5cbiAgICAgKlxuICAgICAqIFRoZSBvcHRpb25hbCBgb25jYW5jZWxsZWRgIGFyZ3VtZW50IHdpbGwgYmUgaW52b2tlZCB3aGVuIHRoZSByZXR1cm5lZCBwcm9taXNlIGlzIGNhbmNlbGxlZCxcbiAgICAgKiB3aXRoIHRoZSBzYW1lIHNlbWFudGljcyBhcyB0aGUgYG9uY2FuY2VsbGVkYCBhcmd1bWVudCBvZiB0aGUgY29uc3RydWN0b3IuXG4gICAgICogV2hlbiB0aGUgcGFyZW50IHByb21pc2UgcmVqZWN0cyBvciBpcyBjYW5jZWxsZWQsIHRoZSBgb25yZWplY3RlZGAgY2FsbGJhY2sgd2lsbCBydW4sXG4gICAgICogX2V2ZW4gYWZ0ZXIgdGhlIHJldHVybmVkIHByb21pc2UgaGFzIGJlZW4gY2FuY2VsbGVkOl9cbiAgICAgKiBpbiB0aGF0IGNhc2UsIHNob3VsZCBpdCByZWplY3Qgb3IgdGhyb3csIHRoZSByZWFzb24gd2lsbCBiZSB3cmFwcGVkXG4gICAgICogaW4gYSB7QGxpbmsgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3J9IGFuZCBidWJibGVkIHVwIGFzIGFuIHVuaGFuZGxlZCByZWplY3Rpb24uXG4gICAgICpcbiAgICAgKiBJdCBpcyBlcXVpdmFsZW50IHRvXG4gICAgICogYGBgdHNcbiAgICAgKiBjYW5jZWxsYWJsZVByb21pc2UudGhlbih1bmRlZmluZWQsIG9ucmVqZWN0ZWQsIG9uY2FuY2VsbGVkKTtcbiAgICAgKiBgYGBcbiAgICAgKiBhbmQgdGhlIHNhbWUgY2F2ZWF0cyBhcHBseS5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIEEgUHJvbWlzZSBmb3IgdGhlIGNvbXBsZXRpb24gb2YgdGhlIGNhbGxiYWNrLlxuICAgICAqIENhbmNlbGxhdGlvbiByZXF1ZXN0cyBvbiB0aGUgcmV0dXJuZWQgcHJvbWlzZVxuICAgICAqIHdpbGwgcHJvcGFnYXRlIHVwIHRoZSBjaGFpbiB0byB0aGUgcGFyZW50IHByb21pc2UsXG4gICAgICogYnV0IG5vdCBpbiB0aGUgb3RoZXIgZGlyZWN0aW9uLlxuICAgICAqXG4gICAgICogVGhlIHByb21pc2UgcmV0dXJuZWQgZnJvbSB7QGxpbmsgY2FuY2VsfSB3aWxsIGZ1bGZpbGwgb25seSBhZnRlciBhbGwgYXR0YWNoZWQgaGFuZGxlcnNcbiAgICAgKiB1cCB0aGUgZW50aXJlIHByb21pc2UgY2hhaW4gaGF2ZSBiZWVuIHJ1bi5cbiAgICAgKlxuICAgICAqIElmIGBvbnJlamVjdGVkYCByZXR1cm5zIGEgY2FuY2VsbGFibGUgcHJvbWlzZSxcbiAgICAgKiBjYW5jZWxsYXRpb24gcmVxdWVzdHMgd2lsbCBiZSBkaXZlcnRlZCB0byBpdCxcbiAgICAgKiBhbmQgdGhlIHNwZWNpZmllZCBgb25jYW5jZWxsZWRgIGNhbGxiYWNrIHdpbGwgYmUgZGlzY2FyZGVkLlxuICAgICAqIFNlZSB7QGxpbmsgdGhlbn0gZm9yIG1vcmUgZGV0YWlscy5cbiAgICAgKi9cbiAgICBjYXRjaDxUUmVzdWx0ID0gbmV2ZXI+KG9ucmVqZWN0ZWQ/OiAoKHJlYXNvbjogYW55KSA9PiAoUHJvbWlzZUxpa2U8VFJlc3VsdD4gfCBUUmVzdWx0KSkgfCB1bmRlZmluZWQgfCBudWxsLCBvbmNhbmNlbGxlZD86IENhbmNlbGxhYmxlUHJvbWlzZUNhbmNlbGxlcik6IENhbmNlbGxhYmxlUHJvbWlzZTxUIHwgVFJlc3VsdD4ge1xuICAgICAgICByZXR1cm4gdGhpcy50aGVuKHVuZGVmaW5lZCwgb25yZWplY3RlZCwgb25jYW5jZWxsZWQpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEF0dGFjaGVzIGEgY2FsbGJhY2sgdGhhdCBpcyBpbnZva2VkIHdoZW4gdGhlIENhbmNlbGxhYmxlUHJvbWlzZSBpcyBzZXR0bGVkIChmdWxmaWxsZWQgb3IgcmVqZWN0ZWQpLiBUaGVcbiAgICAgKiByZXNvbHZlZCB2YWx1ZSBjYW5ub3QgYmUgYWNjZXNzZWQgb3IgbW9kaWZpZWQgZnJvbSB0aGUgY2FsbGJhY2suXG4gICAgICogVGhlIHJldHVybmVkIHByb21pc2Ugd2lsbCBzZXR0bGUgaW4gdGhlIHNhbWUgc3RhdGUgYXMgdGhlIG9yaWdpbmFsIG9uZVxuICAgICAqIGFmdGVyIHRoZSBwcm92aWRlZCBjYWxsYmFjayBoYXMgY29tcGxldGVkIGV4ZWN1dGlvbixcbiAgICAgKiB1bmxlc3MgdGhlIGNhbGxiYWNrIHRocm93cyBvciByZXR1cm5zIGEgcmVqZWN0aW5nIHByb21pc2UsXG4gICAgICogaW4gd2hpY2ggY2FzZSB0aGUgcmV0dXJuZWQgcHJvbWlzZSB3aWxsIHJlamVjdCBhcyB3ZWxsLlxuICAgICAqXG4gICAgICogVGhlIG9wdGlvbmFsIGBvbmNhbmNlbGxlZGAgYXJndW1lbnQgd2lsbCBiZSBpbnZva2VkIHdoZW4gdGhlIHJldHVybmVkIHByb21pc2UgaXMgY2FuY2VsbGVkLFxuICAgICAqIHdpdGggdGhlIHNhbWUgc2VtYW50aWNzIGFzIHRoZSBgb25jYW5jZWxsZWRgIGFyZ3VtZW50IG9mIHRoZSBjb25zdHJ1Y3Rvci5cbiAgICAgKiBPbmNlIHRoZSBwYXJlbnQgcHJvbWlzZSBzZXR0bGVzLCB0aGUgYG9uZmluYWxseWAgY2FsbGJhY2sgd2lsbCBydW4sXG4gICAgICogX2V2ZW4gYWZ0ZXIgdGhlIHJldHVybmVkIHByb21pc2UgaGFzIGJlZW4gY2FuY2VsbGVkOl9cbiAgICAgKiBpbiB0aGF0IGNhc2UsIHNob3VsZCBpdCByZWplY3Qgb3IgdGhyb3csIHRoZSByZWFzb24gd2lsbCBiZSB3cmFwcGVkXG4gICAgICogaW4gYSB7QGxpbmsgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3J9IGFuZCBidWJibGVkIHVwIGFzIGFuIHVuaGFuZGxlZCByZWplY3Rpb24uXG4gICAgICpcbiAgICAgKiBUaGlzIG1ldGhvZCBpcyBpbXBsZW1lbnRlZCBpbiB0ZXJtcyBvZiB7QGxpbmsgdGhlbn0gYW5kIHRoZSBzYW1lIGNhdmVhdHMgYXBwbHkuXG4gICAgICogSXQgaXMgcG9seWZpbGxlZCwgaGVuY2UgYXZhaWxhYmxlIGluIGV2ZXJ5IE9TL3dlYnZpZXcgdmVyc2lvbi5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIEEgUHJvbWlzZSBmb3IgdGhlIGNvbXBsZXRpb24gb2YgdGhlIGNhbGxiYWNrLlxuICAgICAqIENhbmNlbGxhdGlvbiByZXF1ZXN0cyBvbiB0aGUgcmV0dXJuZWQgcHJvbWlzZVxuICAgICAqIHdpbGwgcHJvcGFnYXRlIHVwIHRoZSBjaGFpbiB0byB0aGUgcGFyZW50IHByb21pc2UsXG4gICAgICogYnV0IG5vdCBpbiB0aGUgb3RoZXIgZGlyZWN0aW9uLlxuICAgICAqXG4gICAgICogVGhlIHByb21pc2UgcmV0dXJuZWQgZnJvbSB7QGxpbmsgY2FuY2VsfSB3aWxsIGZ1bGZpbGwgb25seSBhZnRlciBhbGwgYXR0YWNoZWQgaGFuZGxlcnNcbiAgICAgKiB1cCB0aGUgZW50aXJlIHByb21pc2UgY2hhaW4gaGF2ZSBiZWVuIHJ1bi5cbiAgICAgKlxuICAgICAqIElmIGBvbmZpbmFsbHlgIHJldHVybnMgYSBjYW5jZWxsYWJsZSBwcm9taXNlLFxuICAgICAqIGNhbmNlbGxhdGlvbiByZXF1ZXN0cyB3aWxsIGJlIGRpdmVydGVkIHRvIGl0LFxuICAgICAqIGFuZCB0aGUgc3BlY2lmaWVkIGBvbmNhbmNlbGxlZGAgY2FsbGJhY2sgd2lsbCBiZSBkaXNjYXJkZWQuXG4gICAgICogU2VlIHtAbGluayB0aGVufSBmb3IgbW9yZSBkZXRhaWxzLlxuICAgICAqL1xuICAgIGZpbmFsbHkob25maW5hbGx5PzogKCgpID0+IHZvaWQpIHwgdW5kZWZpbmVkIHwgbnVsbCwgb25jYW5jZWxsZWQ/OiBDYW5jZWxsYWJsZVByb21pc2VDYW5jZWxsZXIpOiBDYW5jZWxsYWJsZVByb21pc2U8VD4ge1xuICAgICAgICBpZiAoISh0aGlzIGluc3RhbmNlb2YgQ2FuY2VsbGFibGVQcm9taXNlKSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcIkNhbmNlbGxhYmxlUHJvbWlzZS5wcm90b3R5cGUuZmluYWxseSBjYWxsZWQgb24gYW4gaW52YWxpZCBvYmplY3QuXCIpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKCFpc0NhbGxhYmxlKG9uZmluYWxseSkpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLnRoZW4ob25maW5hbGx5LCBvbmZpbmFsbHksIG9uY2FuY2VsbGVkKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLnRoZW4oXG4gICAgICAgICAgICAodmFsdWUpID0+IENhbmNlbGxhYmxlUHJvbWlzZS5yZXNvbHZlKG9uZmluYWxseSgpKS50aGVuKCgpID0+IHZhbHVlKSxcbiAgICAgICAgICAgIChyZWFzb24/KSA9PiBDYW5jZWxsYWJsZVByb21pc2UucmVzb2x2ZShvbmZpbmFsbHkoKSkudGhlbigoKSA9PiB7IHRocm93IHJlYXNvbjsgfSksXG4gICAgICAgICAgICBvbmNhbmNlbGxlZCxcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBXZSB1c2UgdGhlIGBbU3ltYm9sLnNwZWNpZXNdYCBzdGF0aWMgcHJvcGVydHksIGlmIGF2YWlsYWJsZSxcbiAgICAgKiB0byBkaXNhYmxlIHRoZSBidWlsdC1pbiBhdXRvbWF0aWMgc3ViY2xhc3NpbmcgZmVhdHVyZXMgZnJvbSB7QGxpbmsgUHJvbWlzZX0uXG4gICAgICogSXQgaXMgY3JpdGljYWwgZm9yIHBlcmZvcm1hbmNlIHJlYXNvbnMgdGhhdCBleHRlbmRlcnMgZG8gbm90IG92ZXJyaWRlIHRoaXMuXG4gICAgICogT25jZSB0aGUgcHJvcG9zYWwgYXQgaHR0cHM6Ly9naXRodWIuY29tL3RjMzkvcHJvcG9zYWwtcm0tYnVpbHRpbi1zdWJjbGFzc2luZ1xuICAgICAqIGlzIGVpdGhlciBhY2NlcHRlZCBvciByZXRpcmVkLCB0aGlzIGltcGxlbWVudGF0aW9uIHdpbGwgaGF2ZSB0byBiZSByZXZpc2VkIGFjY29yZGluZ2x5LlxuICAgICAqXG4gICAgICogQGlnbm9yZVxuICAgICAqIEBpbnRlcm5hbFxuICAgICAqL1xuICAgIHN0YXRpYyBnZXQgW3NwZWNpZXNdKCkge1xuICAgICAgICByZXR1cm4gUHJvbWlzZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgQ2FuY2VsbGFibGVQcm9taXNlIHRoYXQgaXMgcmVzb2x2ZWQgd2l0aCBhbiBhcnJheSBvZiByZXN1bHRzXG4gICAgICogd2hlbiBhbGwgb2YgdGhlIHByb3ZpZGVkIFByb21pc2VzIHJlc29sdmUsIG9yIHJlamVjdGVkIHdoZW4gYW55IFByb21pc2UgaXMgcmVqZWN0ZWQuXG4gICAgICpcbiAgICAgKiBFdmVyeSBvbmUgb2YgdGhlIHByb3ZpZGVkIG9iamVjdHMgdGhhdCBpcyBhIHRoZW5hYmxlIF9hbmRfIGNhbmNlbGxhYmxlIG9iamVjdFxuICAgICAqIHdpbGwgYmUgY2FuY2VsbGVkIHdoZW4gdGhlIHJldHVybmVkIHByb21pc2UgaXMgY2FuY2VsbGVkLCB3aXRoIHRoZSBzYW1lIGNhdXNlLlxuICAgICAqXG4gICAgICogQGdyb3VwIFN0YXRpYyBNZXRob2RzXG4gICAgICovXG4gICAgc3RhdGljIGFsbDxUPih2YWx1ZXM6IEl0ZXJhYmxlPFQgfCBQcm9taXNlTGlrZTxUPj4pOiBDYW5jZWxsYWJsZVByb21pc2U8QXdhaXRlZDxUPltdPjtcbiAgICBzdGF0aWMgYWxsPFQgZXh0ZW5kcyByZWFkb25seSB1bmtub3duW10gfCBbXT4odmFsdWVzOiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPHsgLXJlYWRvbmx5IFtQIGluIGtleW9mIFRdOiBBd2FpdGVkPFRbUF0+OyB9PjtcbiAgICBzdGF0aWMgYWxsPFQgZXh0ZW5kcyBJdGVyYWJsZTx1bmtub3duPiB8IEFycmF5TGlrZTx1bmtub3duPj4odmFsdWVzOiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+IHtcbiAgICAgICAgbGV0IGNvbGxlY3RlZCA9IEFycmF5LmZyb20odmFsdWVzKTtcbiAgICAgICAgY29uc3QgcHJvbWlzZSA9IGNvbGxlY3RlZC5sZW5ndGggPT09IDBcbiAgICAgICAgICAgID8gQ2FuY2VsbGFibGVQcm9taXNlLnJlc29sdmUoY29sbGVjdGVkKVxuICAgICAgICAgICAgOiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgICAgICB2b2lkIFByb21pc2UuYWxsKGNvbGxlY3RlZCkudGhlbihyZXNvbHZlLCByZWplY3QpO1xuICAgICAgICAgICAgfSwgKGNhdXNlPyk6IFByb21pc2U8dm9pZD4gPT4gY2FuY2VsQWxsKHByb21pc2UsIGNvbGxlY3RlZCwgY2F1c2UpKTtcbiAgICAgICAgcmV0dXJuIHByb21pc2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIENhbmNlbGxhYmxlUHJvbWlzZSB0aGF0IGlzIHJlc29sdmVkIHdpdGggYW4gYXJyYXkgb2YgcmVzdWx0c1xuICAgICAqIHdoZW4gYWxsIG9mIHRoZSBwcm92aWRlZCBQcm9taXNlcyByZXNvbHZlIG9yIHJlamVjdC5cbiAgICAgKlxuICAgICAqIEV2ZXJ5IG9uZSBvZiB0aGUgcHJvdmlkZWQgb2JqZWN0cyB0aGF0IGlzIGEgdGhlbmFibGUgX2FuZF8gY2FuY2VsbGFibGUgb2JqZWN0XG4gICAgICogd2lsbCBiZSBjYW5jZWxsZWQgd2hlbiB0aGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBjYW5jZWxsZWQsIHdpdGggdGhlIHNhbWUgY2F1c2UuXG4gICAgICpcbiAgICAgKiBAZ3JvdXAgU3RhdGljIE1ldGhvZHNcbiAgICAgKi9cbiAgICBzdGF0aWMgYWxsU2V0dGxlZDxUPih2YWx1ZXM6IEl0ZXJhYmxlPFQgfCBQcm9taXNlTGlrZTxUPj4pOiBDYW5jZWxsYWJsZVByb21pc2U8UHJvbWlzZVNldHRsZWRSZXN1bHQ8QXdhaXRlZDxUPj5bXT47XG4gICAgc3RhdGljIGFsbFNldHRsZWQ8VCBleHRlbmRzIHJlYWRvbmx5IHVua25vd25bXSB8IFtdPih2YWx1ZXM6IFQpOiBDYW5jZWxsYWJsZVByb21pc2U8eyAtcmVhZG9ubHkgW1AgaW4ga2V5b2YgVF06IFByb21pc2VTZXR0bGVkUmVzdWx0PEF3YWl0ZWQ8VFtQXT4+OyB9PjtcbiAgICBzdGF0aWMgYWxsU2V0dGxlZDxUIGV4dGVuZHMgSXRlcmFibGU8dW5rbm93bj4gfCBBcnJheUxpa2U8dW5rbm93bj4+KHZhbHVlczogVCk6IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPiB7XG4gICAgICAgIGxldCBjb2xsZWN0ZWQgPSBBcnJheS5mcm9tKHZhbHVlcyk7XG4gICAgICAgIGNvbnN0IHByb21pc2UgPSBjb2xsZWN0ZWQubGVuZ3RoID09PSAwXG4gICAgICAgICAgICA/IENhbmNlbGxhYmxlUHJvbWlzZS5yZXNvbHZlKGNvbGxlY3RlZClcbiAgICAgICAgICAgIDogbmV3IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICAgICAgdm9pZCBQcm9taXNlLmFsbFNldHRsZWQoY29sbGVjdGVkKS50aGVuKHJlc29sdmUsIHJlamVjdCk7XG4gICAgICAgICAgICB9LCAoY2F1c2U/KTogUHJvbWlzZTx2b2lkPiA9PiBjYW5jZWxBbGwocHJvbWlzZSwgY29sbGVjdGVkLCBjYXVzZSkpO1xuICAgICAgICByZXR1cm4gcHJvbWlzZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBUaGUgYW55IGZ1bmN0aW9uIHJldHVybnMgYSBwcm9taXNlIHRoYXQgaXMgZnVsZmlsbGVkIGJ5IHRoZSBmaXJzdCBnaXZlbiBwcm9taXNlIHRvIGJlIGZ1bGZpbGxlZCxcbiAgICAgKiBvciByZWplY3RlZCB3aXRoIGFuIEFnZ3JlZ2F0ZUVycm9yIGNvbnRhaW5pbmcgYW4gYXJyYXkgb2YgcmVqZWN0aW9uIHJlYXNvbnNcbiAgICAgKiBpZiBhbGwgb2YgdGhlIGdpdmVuIHByb21pc2VzIGFyZSByZWplY3RlZC5cbiAgICAgKiBJdCByZXNvbHZlcyBhbGwgZWxlbWVudHMgb2YgdGhlIHBhc3NlZCBpdGVyYWJsZSB0byBwcm9taXNlcyBhcyBpdCBydW5zIHRoaXMgYWxnb3JpdGhtLlxuICAgICAqXG4gICAgICogRXZlcnkgb25lIG9mIHRoZSBwcm92aWRlZCBvYmplY3RzIHRoYXQgaXMgYSB0aGVuYWJsZSBfYW5kXyBjYW5jZWxsYWJsZSBvYmplY3RcbiAgICAgKiB3aWxsIGJlIGNhbmNlbGxlZCB3aGVuIHRoZSByZXR1cm5lZCBwcm9taXNlIGlzIGNhbmNlbGxlZCwgd2l0aCB0aGUgc2FtZSBjYXVzZS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyBhbnk8VD4odmFsdWVzOiBJdGVyYWJsZTxUIHwgUHJvbWlzZUxpa2U8VD4+KTogQ2FuY2VsbGFibGVQcm9taXNlPEF3YWl0ZWQ8VD4+O1xuICAgIHN0YXRpYyBhbnk8VCBleHRlbmRzIHJlYWRvbmx5IHVua25vd25bXSB8IFtdPih2YWx1ZXM6IFQpOiBDYW5jZWxsYWJsZVByb21pc2U8QXdhaXRlZDxUW251bWJlcl0+PjtcbiAgICBzdGF0aWMgYW55PFQgZXh0ZW5kcyBJdGVyYWJsZTx1bmtub3duPiB8IEFycmF5TGlrZTx1bmtub3duPj4odmFsdWVzOiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+IHtcbiAgICAgICAgbGV0IGNvbGxlY3RlZCA9IEFycmF5LmZyb20odmFsdWVzKTtcbiAgICAgICAgY29uc3QgcHJvbWlzZSA9IGNvbGxlY3RlZC5sZW5ndGggPT09IDBcbiAgICAgICAgICAgID8gQ2FuY2VsbGFibGVQcm9taXNlLnJlc29sdmUoY29sbGVjdGVkKVxuICAgICAgICAgICAgOiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgICAgICB2b2lkIFByb21pc2UuYW55KGNvbGxlY3RlZCkudGhlbihyZXNvbHZlLCByZWplY3QpO1xuICAgICAgICAgICAgfSwgKGNhdXNlPyk6IFByb21pc2U8dm9pZD4gPT4gY2FuY2VsQWxsKHByb21pc2UsIGNvbGxlY3RlZCwgY2F1c2UpKTtcbiAgICAgICAgcmV0dXJuIHByb21pc2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIFByb21pc2UgdGhhdCBpcyByZXNvbHZlZCBvciByZWplY3RlZCB3aGVuIGFueSBvZiB0aGUgcHJvdmlkZWQgUHJvbWlzZXMgYXJlIHJlc29sdmVkIG9yIHJlamVjdGVkLlxuICAgICAqXG4gICAgICogRXZlcnkgb25lIG9mIHRoZSBwcm92aWRlZCBvYmplY3RzIHRoYXQgaXMgYSB0aGVuYWJsZSBfYW5kXyBjYW5jZWxsYWJsZSBvYmplY3RcbiAgICAgKiB3aWxsIGJlIGNhbmNlbGxlZCB3aGVuIHRoZSByZXR1cm5lZCBwcm9taXNlIGlzIGNhbmNlbGxlZCwgd2l0aCB0aGUgc2FtZSBjYXVzZS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyByYWNlPFQ+KHZhbHVlczogSXRlcmFibGU8VCB8IFByb21pc2VMaWtlPFQ+Pik6IENhbmNlbGxhYmxlUHJvbWlzZTxBd2FpdGVkPFQ+PjtcbiAgICBzdGF0aWMgcmFjZTxUIGV4dGVuZHMgcmVhZG9ubHkgdW5rbm93bltdIHwgW10+KHZhbHVlczogVCk6IENhbmNlbGxhYmxlUHJvbWlzZTxBd2FpdGVkPFRbbnVtYmVyXT4+O1xuICAgIHN0YXRpYyByYWNlPFQgZXh0ZW5kcyBJdGVyYWJsZTx1bmtub3duPiB8IEFycmF5TGlrZTx1bmtub3duPj4odmFsdWVzOiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+IHtcbiAgICAgICAgbGV0IGNvbGxlY3RlZCA9IEFycmF5LmZyb20odmFsdWVzKTtcbiAgICAgICAgY29uc3QgcHJvbWlzZSA9IG5ldyBDYW5jZWxsYWJsZVByb21pc2U8dW5rbm93bj4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgdm9pZCBQcm9taXNlLnJhY2UoY29sbGVjdGVkKS50aGVuKHJlc29sdmUsIHJlamVjdCk7XG4gICAgICAgIH0sIChjYXVzZT8pOiBQcm9taXNlPHZvaWQ+ID0+IGNhbmNlbEFsbChwcm9taXNlLCBjb2xsZWN0ZWQsIGNhdXNlKSk7XG4gICAgICAgIHJldHVybiBwcm9taXNlO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgY2FuY2VsbGVkIENhbmNlbGxhYmxlUHJvbWlzZSBmb3IgdGhlIHByb3ZpZGVkIGNhdXNlLlxuICAgICAqXG4gICAgICogQGdyb3VwIFN0YXRpYyBNZXRob2RzXG4gICAgICovXG4gICAgc3RhdGljIGNhbmNlbDxUID0gbmV2ZXI+KGNhdXNlPzogYW55KTogQ2FuY2VsbGFibGVQcm9taXNlPFQ+IHtcbiAgICAgICAgY29uc3QgcCA9IG5ldyBDYW5jZWxsYWJsZVByb21pc2U8VD4oKCkgPT4ge30pO1xuICAgICAgICBwLmNhbmNlbChjYXVzZSk7XG4gICAgICAgIHJldHVybiBwO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlIHRoYXQgY2FuY2Vsc1xuICAgICAqIGFmdGVyIHRoZSBzcGVjaWZpZWQgdGltZW91dCwgd2l0aCB0aGUgcHJvdmlkZWQgY2F1c2UuXG4gICAgICpcbiAgICAgKiBJZiB0aGUge0BsaW5rIEFib3J0U2lnbmFsLnRpbWVvdXR9IGZhY3RvcnkgbWV0aG9kIGlzIGF2YWlsYWJsZSxcbiAgICAgKiBpdCBpcyB1c2VkIHRvIGJhc2UgdGhlIHRpbWVvdXQgb24gX2FjdGl2ZV8gdGltZSByYXRoZXIgdGhhbiBfZWxhcHNlZF8gdGltZS5cbiAgICAgKiBPdGhlcndpc2UsIGB0aW1lb3V0YCBmYWxscyBiYWNrIHRvIHtAbGluayBzZXRUaW1lb3V0fS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyB0aW1lb3V0PFQgPSBuZXZlcj4obWlsbGlzZWNvbmRzOiBudW1iZXIsIGNhdXNlPzogYW55KTogQ2FuY2VsbGFibGVQcm9taXNlPFQ+IHtcbiAgICAgICAgY29uc3QgcHJvbWlzZSA9IG5ldyBDYW5jZWxsYWJsZVByb21pc2U8VD4oKCkgPT4ge30pO1xuICAgICAgICBpZiAoQWJvcnRTaWduYWwgJiYgdHlwZW9mIEFib3J0U2lnbmFsID09PSAnZnVuY3Rpb24nICYmIEFib3J0U2lnbmFsLnRpbWVvdXQgJiYgdHlwZW9mIEFib3J0U2lnbmFsLnRpbWVvdXQgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgICAgIEFib3J0U2lnbmFsLnRpbWVvdXQobWlsbGlzZWNvbmRzKS5hZGRFdmVudExpc3RlbmVyKCdhYm9ydCcsICgpID0+IHZvaWQgcHJvbWlzZS5jYW5jZWwoY2F1c2UpKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHNldFRpbWVvdXQoKCkgPT4gdm9pZCBwcm9taXNlLmNhbmNlbChjYXVzZSksIG1pbGxpc2Vjb25kcyk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHByb21pc2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyBDYW5jZWxsYWJsZVByb21pc2UgdGhhdCByZXNvbHZlcyBhZnRlciB0aGUgc3BlY2lmaWVkIHRpbWVvdXQuXG4gICAgICogVGhlIHJldHVybmVkIHByb21pc2UgY2FuIGJlIGNhbmNlbGxlZCB3aXRob3V0IGNvbnNlcXVlbmNlcy5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyBzbGVlcChtaWxsaXNlY29uZHM6IG51bWJlcik6IENhbmNlbGxhYmxlUHJvbWlzZTx2b2lkPjtcbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IENhbmNlbGxhYmxlUHJvbWlzZSB0aGF0IHJlc29sdmVzIGFmdGVyXG4gICAgICogdGhlIHNwZWNpZmllZCB0aW1lb3V0LCB3aXRoIHRoZSBwcm92aWRlZCB2YWx1ZS5cbiAgICAgKiBUaGUgcmV0dXJuZWQgcHJvbWlzZSBjYW4gYmUgY2FuY2VsbGVkIHdpdGhvdXQgY29uc2VxdWVuY2VzLlxuICAgICAqXG4gICAgICogQGdyb3VwIFN0YXRpYyBNZXRob2RzXG4gICAgICovXG4gICAgc3RhdGljIHNsZWVwPFQ+KG1pbGxpc2Vjb25kczogbnVtYmVyLCB2YWx1ZTogVCk6IENhbmNlbGxhYmxlUHJvbWlzZTxUPjtcbiAgICBzdGF0aWMgc2xlZXA8VCA9IHZvaWQ+KG1pbGxpc2Vjb25kczogbnVtYmVyLCB2YWx1ZT86IFQpOiBDYW5jZWxsYWJsZVByb21pc2U8VD4ge1xuICAgICAgICByZXR1cm4gbmV3IENhbmNlbGxhYmxlUHJvbWlzZTxUPigocmVzb2x2ZSkgPT4ge1xuICAgICAgICAgICAgc2V0VGltZW91dCgoKSA9PiByZXNvbHZlKHZhbHVlISksIG1pbGxpc2Vjb25kcyk7XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgcmVqZWN0ZWQgQ2FuY2VsbGFibGVQcm9taXNlIGZvciB0aGUgcHJvdmlkZWQgcmVhc29uLlxuICAgICAqXG4gICAgICogQGdyb3VwIFN0YXRpYyBNZXRob2RzXG4gICAgICovXG4gICAgc3RhdGljIHJlamVjdDxUID0gbmV2ZXI+KHJlYXNvbj86IGFueSk6IENhbmNlbGxhYmxlUHJvbWlzZTxUPiB7XG4gICAgICAgIHJldHVybiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPFQ+KChfLCByZWplY3QpID0+IHJlamVjdChyZWFzb24pKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IHJlc29sdmVkIENhbmNlbGxhYmxlUHJvbWlzZS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyByZXNvbHZlKCk6IENhbmNlbGxhYmxlUHJvbWlzZTx2b2lkPjtcbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IHJlc29sdmVkIENhbmNlbGxhYmxlUHJvbWlzZSBmb3IgdGhlIHByb3ZpZGVkIHZhbHVlLlxuICAgICAqXG4gICAgICogQGdyb3VwIFN0YXRpYyBNZXRob2RzXG4gICAgICovXG4gICAgc3RhdGljIHJlc29sdmU8VD4odmFsdWU6IFQpOiBDYW5jZWxsYWJsZVByb21pc2U8QXdhaXRlZDxUPj47XG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyByZXNvbHZlZCBDYW5jZWxsYWJsZVByb21pc2UgZm9yIHRoZSBwcm92aWRlZCB2YWx1ZS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyByZXNvbHZlPFQ+KHZhbHVlOiBUIHwgUHJvbWlzZUxpa2U8VD4pOiBDYW5jZWxsYWJsZVByb21pc2U8QXdhaXRlZDxUPj47XG4gICAgc3RhdGljIHJlc29sdmU8VCA9IHZvaWQ+KHZhbHVlPzogVCB8IFByb21pc2VMaWtlPFQ+KTogQ2FuY2VsbGFibGVQcm9taXNlPEF3YWl0ZWQ8VD4+IHtcbiAgICAgICAgaWYgKHZhbHVlIGluc3RhbmNlb2YgQ2FuY2VsbGFibGVQcm9taXNlKSB7XG4gICAgICAgICAgICAvLyBPcHRpbWlzZSBmb3IgY2FuY2VsbGFibGUgcHJvbWlzZXMuXG4gICAgICAgICAgICByZXR1cm4gdmFsdWU7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIG5ldyBDYW5jZWxsYWJsZVByb21pc2U8YW55PigocmVzb2x2ZSkgPT4gcmVzb2x2ZSh2YWx1ZSkpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlIGFuZCByZXR1cm5zIGl0IGluIGFuIG9iamVjdCwgYWxvbmcgd2l0aCBpdHMgcmVzb2x2ZSBhbmQgcmVqZWN0IGZ1bmN0aW9uc1xuICAgICAqIGFuZCBhIGdldHRlci9zZXR0ZXIgZm9yIHRoZSBjYW5jZWxsYXRpb24gY2FsbGJhY2suXG4gICAgICpcbiAgICAgKiBUaGlzIG1ldGhvZCBpcyBwb2x5ZmlsbGVkLCBoZW5jZSBhdmFpbGFibGUgaW4gZXZlcnkgT1Mvd2VidmlldyB2ZXJzaW9uLlxuICAgICAqXG4gICAgICogQGdyb3VwIFN0YXRpYyBNZXRob2RzXG4gICAgICovXG4gICAgc3RhdGljIHdpdGhSZXNvbHZlcnM8VD4oKTogQ2FuY2VsbGFibGVQcm9taXNlV2l0aFJlc29sdmVyczxUPiB7XG4gICAgICAgIGxldCByZXN1bHQ6IENhbmNlbGxhYmxlUHJvbWlzZVdpdGhSZXNvbHZlcnM8VD4gPSB7IG9uY2FuY2VsbGVkOiBudWxsIH0gYXMgYW55O1xuICAgICAgICByZXN1bHQucHJvbWlzZSA9IG5ldyBDYW5jZWxsYWJsZVByb21pc2U8VD4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgcmVzdWx0LnJlc29sdmUgPSByZXNvbHZlO1xuICAgICAgICAgICAgcmVzdWx0LnJlamVjdCA9IHJlamVjdDtcbiAgICAgICAgfSwgKGNhdXNlPzogYW55KSA9PiB7IHJlc3VsdC5vbmNhbmNlbGxlZD8uKGNhdXNlKTsgfSk7XG4gICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgfVxufVxuXG4vKipcbiAqIFJldHVybnMgYSBjYWxsYmFjayB0aGF0IGltcGxlbWVudHMgdGhlIGNhbmNlbGxhdGlvbiBhbGdvcml0aG0gZm9yIHRoZSBnaXZlbiBjYW5jZWxsYWJsZSBwcm9taXNlLlxuICogVGhlIHByb21pc2UgcmV0dXJuZWQgZnJvbSB0aGUgcmVzdWx0aW5nIGZ1bmN0aW9uIGRvZXMgbm90IHJlamVjdC5cbiAqL1xuZnVuY3Rpb24gY2FuY2VsbGVyRm9yPFQ+KHByb21pc2U6IENhbmNlbGxhYmxlUHJvbWlzZVdpdGhSZXNvbHZlcnM8VD4sIHN0YXRlOiBDYW5jZWxsYWJsZVByb21pc2VTdGF0ZSkge1xuICAgIGxldCBjYW5jZWxsYXRpb25Qcm9taXNlOiB2b2lkIHwgUHJvbWlzZUxpa2U8dm9pZD4gPSB1bmRlZmluZWQ7XG5cbiAgICByZXR1cm4gKHJlYXNvbjogQ2FuY2VsRXJyb3IpOiB2b2lkIHwgUHJvbWlzZUxpa2U8dm9pZD4gPT4ge1xuICAgICAgICBpZiAoIXN0YXRlLnNldHRsZWQpIHtcbiAgICAgICAgICAgIHN0YXRlLnNldHRsZWQgPSB0cnVlO1xuICAgICAgICAgICAgc3RhdGUucmVhc29uID0gcmVhc29uO1xuICAgICAgICAgICAgcHJvbWlzZS5yZWplY3QocmVhc29uKTtcblxuICAgICAgICAgICAgLy8gQXR0YWNoIGFuIGVycm9yIGhhbmRsZXIgdGhhdCBpZ25vcmVzIHRoaXMgc3BlY2lmaWMgcmVqZWN0aW9uIHJlYXNvbiBhbmQgbm90aGluZyBlbHNlLlxuICAgICAgICAgICAgLy8gSW4gdGhlb3J5LCBhIHNhbmUgdW5kZXJseWluZyBpbXBsZW1lbnRhdGlvbiBhdCB0aGlzIHBvaW50XG4gICAgICAgICAgICAvLyBzaG91bGQgYWx3YXlzIHJlamVjdCB3aXRoIG91ciBjYW5jZWxsYXRpb24gcmVhc29uLFxuICAgICAgICAgICAgLy8gaGVuY2UgdGhlIGhhbmRsZXIgd2lsbCBuZXZlciB0aHJvdy5cbiAgICAgICAgICAgIHZvaWQgUHJvbWlzZS5wcm90b3R5cGUudGhlbi5jYWxsKHByb21pc2UucHJvbWlzZSwgdW5kZWZpbmVkLCAoZXJyKSA9PiB7XG4gICAgICAgICAgICAgICAgaWYgKGVyciAhPT0gcmVhc29uKSB7XG4gICAgICAgICAgICAgICAgICAgIHRocm93IGVycjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIElmIHJlYXNvbiBpcyBub3Qgc2V0LCB0aGUgcHJvbWlzZSByZXNvbHZlZCByZWd1bGFybHksIGhlbmNlIHdlIG11c3Qgbm90IGNhbGwgb25jYW5jZWxsZWQuXG4gICAgICAgIC8vIElmIG9uY2FuY2VsbGVkIGlzIHVuc2V0LCBubyBuZWVkIHRvIGdvIGFueSBmdXJ0aGVyLlxuICAgICAgICBpZiAoIXN0YXRlLnJlYXNvbiB8fCAhcHJvbWlzZS5vbmNhbmNlbGxlZCkgeyByZXR1cm47IH1cblxuICAgICAgICBjYW5jZWxsYXRpb25Qcm9taXNlID0gbmV3IFByb21pc2U8dm9pZD4oKHJlc29sdmUpID0+IHtcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZShwcm9taXNlLm9uY2FuY2VsbGVkIShzdGF0ZS5yZWFzb24hLmNhdXNlKSk7XG4gICAgICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgICAgICBQcm9taXNlLnJlamVjdChuZXcgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3IocHJvbWlzZS5wcm9taXNlLCBlcnIsIFwiVW5oYW5kbGVkIGV4Y2VwdGlvbiBpbiBvbmNhbmNlbGxlZCBjYWxsYmFjay5cIikpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KS5jYXRjaCgocmVhc29uPykgPT4ge1xuICAgICAgICAgICAgUHJvbWlzZS5yZWplY3QobmV3IENhbmNlbGxlZFJlamVjdGlvbkVycm9yKHByb21pc2UucHJvbWlzZSwgcmVhc29uLCBcIlVuaGFuZGxlZCByZWplY3Rpb24gaW4gb25jYW5jZWxsZWQgY2FsbGJhY2suXCIpKTtcbiAgICAgICAgfSk7XG5cbiAgICAgICAgLy8gVW5zZXQgb25jYW5jZWxsZWQgdG8gcHJldmVudCByZXBlYXRlZCBjYWxscy5cbiAgICAgICAgcHJvbWlzZS5vbmNhbmNlbGxlZCA9IG51bGw7XG5cbiAgICAgICAgcmV0dXJuIGNhbmNlbGxhdGlvblByb21pc2U7XG4gICAgfVxufVxuXG4vKipcbiAqIFJldHVybnMgYSBjYWxsYmFjayB0aGF0IGltcGxlbWVudHMgdGhlIHJlc29sdXRpb24gYWxnb3JpdGhtIGZvciB0aGUgZ2l2ZW4gY2FuY2VsbGFibGUgcHJvbWlzZS5cbiAqL1xuZnVuY3Rpb24gcmVzb2x2ZXJGb3I8VD4ocHJvbWlzZTogQ2FuY2VsbGFibGVQcm9taXNlV2l0aFJlc29sdmVyczxUPiwgc3RhdGU6IENhbmNlbGxhYmxlUHJvbWlzZVN0YXRlKTogQ2FuY2VsbGFibGVQcm9taXNlUmVzb2x2ZXI8VD4ge1xuICAgIHJldHVybiAodmFsdWUpID0+IHtcbiAgICAgICAgaWYgKHN0YXRlLnJlc29sdmluZykgeyByZXR1cm47IH1cbiAgICAgICAgc3RhdGUucmVzb2x2aW5nID0gdHJ1ZTtcblxuICAgICAgICBpZiAodmFsdWUgPT09IHByb21pc2UucHJvbWlzZSkge1xuICAgICAgICAgICAgaWYgKHN0YXRlLnNldHRsZWQpIHsgcmV0dXJuOyB9XG4gICAgICAgICAgICBzdGF0ZS5zZXR0bGVkID0gdHJ1ZTtcbiAgICAgICAgICAgIHByb21pc2UucmVqZWN0KG5ldyBUeXBlRXJyb3IoXCJBIHByb21pc2UgY2Fubm90IGJlIHJlc29sdmVkIHdpdGggaXRzZWxmLlwiKSk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodmFsdWUgIT0gbnVsbCAmJiAodHlwZW9mIHZhbHVlID09PSAnb2JqZWN0JyB8fCB0eXBlb2YgdmFsdWUgPT09ICdmdW5jdGlvbicpKSB7XG4gICAgICAgICAgICBsZXQgdGhlbjogYW55O1xuICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICB0aGVuID0gKHZhbHVlIGFzIGFueSkudGhlbjtcbiAgICAgICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgICAgIHN0YXRlLnNldHRsZWQgPSB0cnVlO1xuICAgICAgICAgICAgICAgIHByb21pc2UucmVqZWN0KGVycik7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoaXNDYWxsYWJsZSh0aGVuKSkge1xuICAgICAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgICAgIGxldCBjYW5jZWwgPSAodmFsdWUgYXMgYW55KS5jYW5jZWw7XG4gICAgICAgICAgICAgICAgICAgIGlmIChpc0NhbGxhYmxlKGNhbmNlbCkpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IG9uY2FuY2VsbGVkID0gKGNhdXNlPzogYW55KSA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgUmVmbGVjdC5hcHBseShjYW5jZWwsIHZhbHVlLCBbY2F1c2VdKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoc3RhdGUucmVhc29uKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gSWYgYWxyZWFkeSBjYW5jZWxsZWQsIHByb3BhZ2F0ZSBjYW5jZWxsYXRpb24uXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gVGhlIHByb21pc2UgcmV0dXJuZWQgZnJvbSB0aGUgY2FuY2VsbGVyIGFsZ29yaXRobSBkb2VzIG5vdCByZWplY3RcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBzbyBpdCBjYW4gYmUgZGlzY2FyZGVkIHNhZmVseS5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2b2lkIGNhbmNlbGxlckZvcih7IC4uLnByb21pc2UsIG9uY2FuY2VsbGVkIH0sIHN0YXRlKShzdGF0ZS5yZWFzb24pO1xuICAgICAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9taXNlLm9uY2FuY2VsbGVkID0gb25jYW5jZWxsZWQ7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9IGNhdGNoIHt9XG5cbiAgICAgICAgICAgICAgICBjb25zdCBuZXdTdGF0ZTogQ2FuY2VsbGFibGVQcm9taXNlU3RhdGUgPSB7XG4gICAgICAgICAgICAgICAgICAgIHJvb3Q6IHN0YXRlLnJvb3QsXG4gICAgICAgICAgICAgICAgICAgIHJlc29sdmluZzogZmFsc2UsXG4gICAgICAgICAgICAgICAgICAgIGdldCBzZXR0bGVkKCkgeyByZXR1cm4gdGhpcy5yb290LnNldHRsZWQgfSxcbiAgICAgICAgICAgICAgICAgICAgc2V0IHNldHRsZWQodmFsdWUpIHsgdGhpcy5yb290LnNldHRsZWQgPSB2YWx1ZTsgfSxcbiAgICAgICAgICAgICAgICAgICAgZ2V0IHJlYXNvbigpIHsgcmV0dXJuIHRoaXMucm9vdC5yZWFzb24gfVxuICAgICAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgICAgICBjb25zdCByZWplY3RvciA9IHJlamVjdG9yRm9yKHByb21pc2UsIG5ld1N0YXRlKTtcbiAgICAgICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgICAgICBSZWZsZWN0LmFwcGx5KHRoZW4sIHZhbHVlLCBbcmVzb2x2ZXJGb3IocHJvbWlzZSwgbmV3U3RhdGUpLCByZWplY3Rvcl0pO1xuICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgICAgICAgICByZWplY3RvcihlcnIpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICByZXR1cm47IC8vIElNUE9SVEFOVCFcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChzdGF0ZS5zZXR0bGVkKSB7IHJldHVybjsgfVxuICAgICAgICBzdGF0ZS5zZXR0bGVkID0gdHJ1ZTtcbiAgICAgICAgcHJvbWlzZS5yZXNvbHZlKHZhbHVlKTtcbiAgICB9O1xufVxuXG4vKipcbiAqIFJldHVybnMgYSBjYWxsYmFjayB0aGF0IGltcGxlbWVudHMgdGhlIHJlamVjdGlvbiBhbGdvcml0aG0gZm9yIHRoZSBnaXZlbiBjYW5jZWxsYWJsZSBwcm9taXNlLlxuICovXG5mdW5jdGlvbiByZWplY3RvckZvcjxUPihwcm9taXNlOiBDYW5jZWxsYWJsZVByb21pc2VXaXRoUmVzb2x2ZXJzPFQ+LCBzdGF0ZTogQ2FuY2VsbGFibGVQcm9taXNlU3RhdGUpOiBDYW5jZWxsYWJsZVByb21pc2VSZWplY3RvciB7XG4gICAgcmV0dXJuIChyZWFzb24/KSA9PiB7XG4gICAgICAgIGlmIChzdGF0ZS5yZXNvbHZpbmcpIHsgcmV0dXJuOyB9XG4gICAgICAgIHN0YXRlLnJlc29sdmluZyA9IHRydWU7XG5cbiAgICAgICAgaWYgKHN0YXRlLnNldHRsZWQpIHtcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgaWYgKHJlYXNvbiBpbnN0YW5jZW9mIENhbmNlbEVycm9yICYmIHN0YXRlLnJlYXNvbiBpbnN0YW5jZW9mIENhbmNlbEVycm9yICYmIE9iamVjdC5pcyhyZWFzb24uY2F1c2UsIHN0YXRlLnJlYXNvbi5jYXVzZSkpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gU3dhbGxvdyBsYXRlIHJlamVjdGlvbnMgdGhhdCBhcmUgQ2FuY2VsRXJyb3JzIHdob3NlIGNhbmNlbGxhdGlvbiBjYXVzZSBpcyB0aGUgc2FtZSBhcyBvdXJzLlxuICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBjYXRjaCB7fVxuXG4gICAgICAgICAgICB2b2lkIFByb21pc2UucmVqZWN0KG5ldyBDYW5jZWxsZWRSZWplY3Rpb25FcnJvcihwcm9taXNlLnByb21pc2UsIHJlYXNvbikpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgc3RhdGUuc2V0dGxlZCA9IHRydWU7XG4gICAgICAgICAgICBwcm9taXNlLnJlamVjdChyZWFzb24pO1xuICAgICAgICB9XG4gICAgfVxufVxuXG4vKipcbiAqIENhbmNlbHMgYWxsIHZhbHVlcyBpbiBhbiBhcnJheSB0aGF0IGxvb2sgbGlrZSBjYW5jZWxsYWJsZSB0aGVuYWJsZXMuXG4gKiBSZXR1cm5zIGEgcHJvbWlzZSB0aGF0IGZ1bGZpbGxzIG9uY2UgYWxsIGNhbmNlbGxhdGlvbiBwcm9jZWR1cmVzIGZvciB0aGUgZ2l2ZW4gdmFsdWVzIGhhdmUgc2V0dGxlZC5cbiAqL1xuZnVuY3Rpb24gY2FuY2VsQWxsKHBhcmVudDogQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+LCB2YWx1ZXM6IGFueVtdLCBjYXVzZT86IGFueSk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHJlc3VsdHM6IFByb21pc2U8dm9pZD5bXSA9IFtdO1xuXG4gICAgZm9yIChjb25zdCB2YWx1ZSBvZiB2YWx1ZXMpIHtcbiAgICAgICAgbGV0IGNhbmNlbDogQ2FuY2VsbGFibGVQcm9taXNlQ2FuY2VsbGVyO1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgaWYgKCFpc0NhbGxhYmxlKHZhbHVlLnRoZW4pKSB7IGNvbnRpbnVlOyB9XG4gICAgICAgICAgICBjYW5jZWwgPSB2YWx1ZS5jYW5jZWw7XG4gICAgICAgICAgICBpZiAoIWlzQ2FsbGFibGUoY2FuY2VsKSkgeyBjb250aW51ZTsgfVxuICAgICAgICB9IGNhdGNoIHsgY29udGludWU7IH1cblxuICAgICAgICBsZXQgcmVzdWx0OiB2b2lkIHwgUHJvbWlzZUxpa2U8dm9pZD47XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICByZXN1bHQgPSBSZWZsZWN0LmFwcGx5KGNhbmNlbCwgdmFsdWUsIFtjYXVzZV0pO1xuICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgIFByb21pc2UucmVqZWN0KG5ldyBDYW5jZWxsZWRSZWplY3Rpb25FcnJvcihwYXJlbnQsIGVyciwgXCJVbmhhbmRsZWQgZXhjZXB0aW9uIGluIGNhbmNlbCBtZXRob2QuXCIpKTtcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKCFyZXN1bHQpIHsgY29udGludWU7IH1cbiAgICAgICAgcmVzdWx0cy5wdXNoKFxuICAgICAgICAgICAgKHJlc3VsdCBpbnN0YW5jZW9mIFByb21pc2UgID8gcmVzdWx0IDogUHJvbWlzZS5yZXNvbHZlKHJlc3VsdCkpLmNhdGNoKChyZWFzb24/KSA9PiB7XG4gICAgICAgICAgICAgICAgUHJvbWlzZS5yZWplY3QobmV3IENhbmNlbGxlZFJlamVjdGlvbkVycm9yKHBhcmVudCwgcmVhc29uLCBcIlVuaGFuZGxlZCByZWplY3Rpb24gaW4gY2FuY2VsIG1ldGhvZC5cIikpO1xuICAgICAgICAgICAgfSlcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICByZXR1cm4gUHJvbWlzZS5hbGwocmVzdWx0cykgYXMgYW55O1xufVxuXG4vKipcbiAqIFJldHVybnMgaXRzIGFyZ3VtZW50LlxuICovXG5mdW5jdGlvbiBpZGVudGl0eTxUPih4OiBUKTogVCB7XG4gICAgcmV0dXJuIHg7XG59XG5cbi8qKlxuICogVGhyb3dzIGl0cyBhcmd1bWVudC5cbiAqL1xuZnVuY3Rpb24gdGhyb3dlcihyZWFzb24/OiBhbnkpOiBuZXZlciB7XG4gICAgdGhyb3cgcmVhc29uO1xufVxuXG4vKipcbiAqIEF0dGVtcHRzIHZhcmlvdXMgc3RyYXRlZ2llcyB0byBjb252ZXJ0IGFuIGVycm9yIHRvIGEgc3RyaW5nLlxuICovXG5mdW5jdGlvbiBlcnJvck1lc3NhZ2UoZXJyOiBhbnkpOiBzdHJpbmcge1xuICAgIHRyeSB7XG4gICAgICAgIGlmIChlcnIgaW5zdGFuY2VvZiBFcnJvciB8fCB0eXBlb2YgZXJyICE9PSAnb2JqZWN0JyB8fCBlcnIudG9TdHJpbmcgIT09IE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcpIHtcbiAgICAgICAgICAgIHJldHVybiBcIlwiICsgZXJyO1xuICAgICAgICB9XG4gICAgfSBjYXRjaCB7fVxuXG4gICAgdHJ5IHtcbiAgICAgICAgcmV0dXJuIEpTT04uc3RyaW5naWZ5KGVycik7XG4gICAgfSBjYXRjaCB7fVxuXG4gICAgdHJ5IHtcbiAgICAgICAgcmV0dXJuIE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbChlcnIpO1xuICAgIH0gY2F0Y2gge31cblxuICAgIHJldHVybiBcIjxjb3VsZCBub3QgY29udmVydCBlcnJvciB0byBzdHJpbmc+XCI7XG59XG5cbi8qKlxuICogR2V0cyB0aGUgY3VycmVudCBiYXJyaWVyIHByb21pc2UgZm9yIHRoZSBnaXZlbiBjYW5jZWxsYWJsZSBwcm9taXNlLiBJZiBuZWNlc3NhcnksIGluaXRpYWxpc2VzIHRoZSBiYXJyaWVyLlxuICovXG5mdW5jdGlvbiBjdXJyZW50QmFycmllcjxUPihwcm9taXNlOiBDYW5jZWxsYWJsZVByb21pc2U8VD4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBsZXQgcHdyOiBQYXJ0aWFsPFByb21pc2VXaXRoUmVzb2x2ZXJzPHZvaWQ+PiA9IHByb21pc2VbYmFycmllclN5bV0gPz8ge307XG4gICAgaWYgKCEoJ3Byb21pc2UnIGluIHB3cikpIHtcbiAgICAgICAgT2JqZWN0LmFzc2lnbihwd3IsIHByb21pc2VXaXRoUmVzb2x2ZXJzPHZvaWQ+KCkpO1xuICAgIH1cbiAgICBpZiAocHJvbWlzZVtiYXJyaWVyU3ltXSA9PSBudWxsKSB7XG4gICAgICAgIHB3ci5yZXNvbHZlISgpO1xuICAgICAgICBwcm9taXNlW2JhcnJpZXJTeW1dID0gcHdyO1xuICAgIH1cbiAgICByZXR1cm4gcHdyLnByb21pc2UhO1xufVxuXG4vLyBQb2x5ZmlsbCBQcm9taXNlLndpdGhSZXNvbHZlcnMuXG5sZXQgcHJvbWlzZVdpdGhSZXNvbHZlcnMgPSBQcm9taXNlLndpdGhSZXNvbHZlcnM7XG5pZiAocHJvbWlzZVdpdGhSZXNvbHZlcnMgJiYgdHlwZW9mIHByb21pc2VXaXRoUmVzb2x2ZXJzID09PSAnZnVuY3Rpb24nKSB7XG4gICAgcHJvbWlzZVdpdGhSZXNvbHZlcnMgPSBwcm9taXNlV2l0aFJlc29sdmVycy5iaW5kKFByb21pc2UpO1xufSBlbHNlIHtcbiAgICBwcm9taXNlV2l0aFJlc29sdmVycyA9IGZ1bmN0aW9uIDxUPigpOiBQcm9taXNlV2l0aFJlc29sdmVyczxUPiB7XG4gICAgICAgIGxldCByZXNvbHZlITogKHZhbHVlOiBUIHwgUHJvbWlzZUxpa2U8VD4pID0+IHZvaWQ7XG4gICAgICAgIGxldCByZWplY3QhOiAocmVhc29uPzogYW55KSA9PiB2b2lkO1xuICAgICAgICBjb25zdCBwcm9taXNlID0gbmV3IFByb21pc2U8VD4oKHJlcywgcmVqKSA9PiB7IHJlc29sdmUgPSByZXM7IHJlamVjdCA9IHJlajsgfSk7XG4gICAgICAgIHJldHVybiB7IHByb21pc2UsIHJlc29sdmUsIHJlamVjdCB9O1xuICAgIH1cbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyLCBvYmplY3ROYW1lc30gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuXG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5DbGlwYm9hcmQpO1xuXG5jb25zdCBDbGlwYm9hcmRTZXRUZXh0ID0gMDtcbmNvbnN0IENsaXBib2FyZFRleHQgPSAxO1xuXG4vKipcbiAqIFNldHMgdGhlIHRleHQgdG8gdGhlIENsaXBib2FyZC5cbiAqXG4gKiBAcGFyYW0gdGV4dCAtIFRoZSB0ZXh0IHRvIGJlIHNldCB0byB0aGUgQ2xpcGJvYXJkLlxuICogQHJldHVybiBBIFByb21pc2UgdGhhdCByZXNvbHZlcyB3aGVuIHRoZSBvcGVyYXRpb24gaXMgc3VjY2Vzc2Z1bC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFNldFRleHQodGV4dDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgcmV0dXJuIGNhbGwoQ2xpcGJvYXJkU2V0VGV4dCwge3RleHR9KTtcbn1cblxuLyoqXG4gKiBHZXQgdGhlIENsaXBib2FyZCB0ZXh0XG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCB0aGUgdGV4dCBmcm9tIHRoZSBDbGlwYm9hcmQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBUZXh0KCk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgcmV0dXJuIGNhbGwoQ2xpcGJvYXJkVGV4dCk7XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmV4cG9ydCBpbnRlcmZhY2UgU2l6ZSB7XG4gICAgLyoqIFRoZSB3aWR0aCBvZiBhIHJlY3Rhbmd1bGFyIGFyZWEuICovXG4gICAgV2lkdGg6IG51bWJlcjtcbiAgICAvKiogVGhlIGhlaWdodCBvZiBhIHJlY3Rhbmd1bGFyIGFyZWEuICovXG4gICAgSGVpZ2h0OiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmVjdCB7XG4gICAgLyoqIFRoZSBYIGNvb3JkaW5hdGUgb2YgdGhlIG9yaWdpbi4gKi9cbiAgICBYOiBudW1iZXI7XG4gICAgLyoqIFRoZSBZIGNvb3JkaW5hdGUgb2YgdGhlIG9yaWdpbi4gKi9cbiAgICBZOiBudW1iZXI7XG4gICAgLyoqIFRoZSB3aWR0aCBvZiB0aGUgcmVjdGFuZ2xlLiAqL1xuICAgIFdpZHRoOiBudW1iZXI7XG4gICAgLyoqIFRoZSBoZWlnaHQgb2YgdGhlIHJlY3RhbmdsZS4gKi9cbiAgICBIZWlnaHQ6IG51bWJlcjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBTY3JlZW4ge1xuICAgIC8qKiBVbmlxdWUgaWRlbnRpZmllciBmb3IgdGhlIHNjcmVlbi4gKi9cbiAgICBJRDogc3RyaW5nO1xuICAgIC8qKiBIdW1hbi1yZWFkYWJsZSBuYW1lIG9mIHRoZSBzY3JlZW4uICovXG4gICAgTmFtZTogc3RyaW5nO1xuICAgIC8qKiBUaGUgc2NhbGUgZmFjdG9yIG9mIHRoZSBzY3JlZW4gKERQSS85NikuIDEgPSBzdGFuZGFyZCBEUEksIDIgPSBIaURQSSAoUmV0aW5hKSwgZXRjLiAqL1xuICAgIFNjYWxlRmFjdG9yOiBudW1iZXI7XG4gICAgLyoqIFRoZSBYIGNvb3JkaW5hdGUgb2YgdGhlIHNjcmVlbi4gKi9cbiAgICBYOiBudW1iZXI7XG4gICAgLyoqIFRoZSBZIGNvb3JkaW5hdGUgb2YgdGhlIHNjcmVlbi4gKi9cbiAgICBZOiBudW1iZXI7XG4gICAgLyoqIENvbnRhaW5zIHRoZSB3aWR0aCBhbmQgaGVpZ2h0IG9mIHRoZSBzY3JlZW4uICovXG4gICAgU2l6ZTogU2l6ZTtcbiAgICAvKiogQ29udGFpbnMgdGhlIGJvdW5kcyBvZiB0aGUgc2NyZWVuIGluIHRlcm1zIG9mIFgsIFksIFdpZHRoLCBhbmQgSGVpZ2h0LiAqL1xuICAgIEJvdW5kczogUmVjdDtcbiAgICAvKiogQ29udGFpbnMgdGhlIHBoeXNpY2FsIGJvdW5kcyBvZiB0aGUgc2NyZWVuIGluIHRlcm1zIG9mIFgsIFksIFdpZHRoLCBhbmQgSGVpZ2h0IChiZWZvcmUgc2NhbGluZykuICovXG4gICAgUGh5c2ljYWxCb3VuZHM6IFJlY3Q7XG4gICAgLyoqIENvbnRhaW5zIHRoZSBhcmVhIG9mIHRoZSBzY3JlZW4gdGhhdCBpcyBhY3R1YWxseSB1c2FibGUgKGV4Y2x1ZGluZyB0YXNrYmFyIGFuZCBvdGhlciBzeXN0ZW0gVUkpLiAqL1xuICAgIFdvcmtBcmVhOiBSZWN0O1xuICAgIC8qKiBDb250YWlucyB0aGUgcGh5c2ljYWwgV29ya0FyZWEgb2YgdGhlIHNjcmVlbiAoYmVmb3JlIHNjYWxpbmcpLiAqL1xuICAgIFBoeXNpY2FsV29ya0FyZWE6IFJlY3Q7XG4gICAgLyoqIFRydWUgaWYgdGhpcyBpcyB0aGUgcHJpbWFyeSBtb25pdG9yIHNlbGVjdGVkIGJ5IHRoZSB1c2VyIGluIHRoZSBvcGVyYXRpbmcgc3lzdGVtLiAqL1xuICAgIElzUHJpbWFyeTogYm9vbGVhbjtcbiAgICAvKiogVGhlIHJvdGF0aW9uIG9mIHRoZSBzY3JlZW4uICovXG4gICAgUm90YXRpb246IG51bWJlcjtcbn1cblxuaW1wb3J0IHsgbmV3UnVudGltZUNhbGxlciwgb2JqZWN0TmFtZXMgfSBmcm9tIFwiLi9ydW50aW1lLmpzXCI7XG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5TY3JlZW5zKTtcblxuY29uc3QgZ2V0QWxsID0gMDtcbmNvbnN0IGdldFByaW1hcnkgPSAxO1xuY29uc3QgZ2V0Q3VycmVudCA9IDI7XG5cbi8qKlxuICogR2V0cyBhbGwgc2NyZWVucy5cbiAqXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB0byBhbiBhcnJheSBvZiBTY3JlZW4gb2JqZWN0cy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEdldEFsbCgpOiBQcm9taXNlPFNjcmVlbltdPiB7XG4gICAgcmV0dXJuIGNhbGwoZ2V0QWxsKTtcbn1cblxuLyoqXG4gKiBHZXRzIHRoZSBwcmltYXJ5IHNjcmVlbi5cbiAqXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB0byB0aGUgcHJpbWFyeSBzY3JlZW4uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBHZXRQcmltYXJ5KCk6IFByb21pc2U8U2NyZWVuPiB7XG4gICAgcmV0dXJuIGNhbGwoZ2V0UHJpbWFyeSk7XG59XG5cbi8qKlxuICogR2V0cyB0aGUgY3VycmVudCBhY3RpdmUgc2NyZWVuLlxuICpcbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdpdGggdGhlIGN1cnJlbnQgYWN0aXZlIHNjcmVlbi5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEdldEN1cnJlbnQoKTogUHJvbWlzZTxTY3JlZW4+IHtcbiAgICByZXR1cm4gY2FsbChnZXRDdXJyZW50KTtcbn1cbiIsICIvKlxuIF8gICAgIF9fICAgICBfIF9fXG58IHwgIC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHsgbmV3UnVudGltZUNhbGxlciwgb2JqZWN0TmFtZXMgfSBmcm9tIFwiLi9ydW50aW1lLmpzXCI7XG5cbmNvbnN0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdE5hbWVzLklPUyk7XG5cbi8vIE1ldGhvZCBJRHNcbmNvbnN0IEhhcHRpY3NJbXBhY3QgPSAwO1xuY29uc3QgRGV2aWNlSW5mbyA9IDE7XG5cbmV4cG9ydCBuYW1lc3BhY2UgSGFwdGljcyB7XG4gICAgZXhwb3J0IHR5cGUgSW1wYWN0U3R5bGUgPSBcImxpZ2h0XCJ8XCJtZWRpdW1cInxcImhlYXZ5XCJ8XCJzb2Z0XCJ8XCJyaWdpZFwiO1xuICAgIGV4cG9ydCBmdW5jdGlvbiBJbXBhY3Qoc3R5bGU6IEltcGFjdFN0eWxlID0gXCJtZWRpdW1cIik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gY2FsbChIYXB0aWNzSW1wYWN0LCB7IHN0eWxlIH0pO1xuICAgIH1cbn1cblxuZXhwb3J0IG5hbWVzcGFjZSBEZXZpY2Uge1xuICAgIGV4cG9ydCBpbnRlcmZhY2UgSW5mbyB7XG4gICAgICAgIG1vZGVsOiBzdHJpbmc7XG4gICAgICAgIHN5c3RlbU5hbWU6IHN0cmluZztcbiAgICAgICAgc3lzdGVtVmVyc2lvbjogc3RyaW5nO1xuICAgICAgICBpc1NpbXVsYXRvcjogYm9vbGVhbjtcbiAgICB9XG4gICAgZXhwb3J0IGZ1bmN0aW9uIEluZm8oKTogUHJvbWlzZTxJbmZvPiB7XG4gICAgICAgIHJldHVybiBjYWxsKERldmljZUluZm8pO1xuICAgIH1cbn1cbiJdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7OztBQ0FBO0FBQUE7QUFBQTtBQUFBO0FBQUE7OztBQ0FBO0FBQUE7QUFBQTtBQUFBOzs7QUM2QkEsSUFBTSxjQUNGO0FBRUcsU0FBUyxPQUFPLE9BQWUsSUFBWTtBQUM5QyxNQUFJLEtBQUs7QUFFVCxNQUFJLElBQUksT0FBTztBQUNmLFNBQU8sS0FBSztBQUVSLFVBQU0sWUFBYSxLQUFLLE9BQU8sSUFBSSxLQUFNLENBQUM7QUFBQSxFQUM5QztBQUNBLFNBQU87QUFDWDs7O0FDN0JBLElBQU0sYUFBYSxPQUFPLFNBQVMsU0FBUztBQU1yQyxJQUFNLGNBQWMsT0FBTyxPQUFPO0FBQUEsRUFDckMsTUFBTTtBQUFBLEVBQ04sV0FBVztBQUFBLEVBQ1gsYUFBYTtBQUFBLEVBQ2IsUUFBUTtBQUFBLEVBQ1IsYUFBYTtBQUFBLEVBQ2IsUUFBUTtBQUFBLEVBQ1IsUUFBUTtBQUFBLEVBQ1IsU0FBUztBQUFBLEVBQ1QsUUFBUTtBQUFBLEVBQ1IsU0FBUztBQUFBLEVBQ1QsWUFBWTtBQUFBLEVBQ1osS0FBSztBQUNULENBQUM7QUFDTSxJQUFJLFdBQVcsT0FBTztBQXVCN0IsSUFBSSxrQkFBMkM7QUFzQnhDLFNBQVMsYUFBYSxXQUEwQztBQUNuRSxvQkFBa0I7QUFDdEI7QUFLTyxTQUFTLGVBQXdDO0FBQ3BELFNBQU87QUFDWDtBQVNPLFNBQVMsaUJBQWlCLFFBQWdCLGFBQXFCLElBQUk7QUFDdEUsU0FBTyxTQUFVLFFBQWdCLE9BQVksTUFBTTtBQUMvQyxXQUFPLGtCQUFrQixRQUFRLFFBQVEsWUFBWSxJQUFJO0FBQUEsRUFDN0Q7QUFDSjtBQUVBLGVBQWUsa0JBQWtCLFVBQWtCLFFBQWdCLFlBQW9CLE1BQXlCO0FBckdoSCxNQUFBQSxLQUFBO0FBdUdJLE1BQUksaUJBQWlCO0FBQ2pCLFdBQU8sZ0JBQWdCLEtBQUssVUFBVSxRQUFRLFlBQVksSUFBSTtBQUFBLEVBQ2xFO0FBR0EsTUFBSSxNQUFNLElBQUksSUFBSSxVQUFVO0FBRTVCLE1BQUksT0FBdUQ7QUFBQSxJQUN6RCxRQUFRO0FBQUEsSUFDUjtBQUFBLEVBQ0Y7QUFDQSxNQUFJLFNBQVMsUUFBUSxTQUFTLFFBQVc7QUFDdkMsU0FBSyxPQUFPO0FBQUEsRUFDZDtBQUVBLE1BQUksVUFBa0M7QUFBQSxJQUNsQyxDQUFDLG1CQUFtQixHQUFHO0FBQUEsSUFDdkIsQ0FBQyxjQUFjLEdBQUc7QUFBQSxFQUN0QjtBQUNBLE1BQUksWUFBWTtBQUNaLFlBQVEscUJBQXFCLElBQUk7QUFBQSxFQUNyQztBQUVBLE1BQUksV0FBVyxNQUFNLE1BQU0sS0FBSztBQUFBLElBQzlCLFFBQVE7QUFBQSxJQUNSO0FBQUEsSUFDQSxNQUFNLEtBQUssVUFBVSxJQUFJO0FBQUEsRUFDM0IsQ0FBQztBQUNELE1BQUksQ0FBQyxTQUFTLElBQUk7QUFDZCxVQUFNLElBQUksTUFBTSxNQUFNLFNBQVMsS0FBSyxDQUFDO0FBQUEsRUFDekM7QUFFQSxRQUFLLE1BQUFBLE1BQUEsU0FBUyxRQUFRLElBQUksY0FBYyxNQUFuQyxnQkFBQUEsSUFBc0MsUUFBUSx3QkFBOUMsWUFBcUUsUUFBUSxJQUFJO0FBQ2xGLFdBQU8sU0FBUyxLQUFLO0FBQUEsRUFDekIsT0FBTztBQUNILFdBQU8sU0FBUyxLQUFLO0FBQUEsRUFDekI7QUFDSjs7O0FGaElBLElBQU0sT0FBTyxpQkFBaUIsWUFBWSxPQUFPO0FBRWpELElBQU0saUJBQWlCO0FBT2hCLFNBQVMsUUFBUSxLQUFrQztBQUN0RCxTQUFPLEtBQUssZ0JBQWdCLEVBQUMsS0FBSyxJQUFJLFNBQVMsRUFBQyxDQUFDO0FBQ3JEOzs7QUd2QkE7QUFBQTtBQUFBLGVBQUFDO0FBQUEsRUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFhQSxPQUFPLFNBQVMsT0FBTyxVQUFVLENBQUM7QUFFbEMsSUFBTUMsUUFBTyxpQkFBaUIsWUFBWSxNQUFNO0FBR2hELElBQU0sYUFBYTtBQUNuQixJQUFNLGdCQUFnQjtBQUN0QixJQUFNLGNBQWM7QUFDcEIsSUFBTSxpQkFBaUI7QUFDdkIsSUFBTSxpQkFBaUI7QUFDdkIsSUFBTSxpQkFBaUI7QUEwR3ZCLFNBQVMsT0FBTyxNQUFjLFVBQWdGLENBQUMsR0FBaUI7QUFDNUgsU0FBT0EsTUFBSyxNQUFNLE9BQU87QUFDN0I7QUFRTyxTQUFTLEtBQUssU0FBZ0Q7QUFBRSxTQUFPLE9BQU8sWUFBWSxPQUFPO0FBQUc7QUFRcEcsU0FBUyxRQUFRLFNBQWdEO0FBQUUsU0FBTyxPQUFPLGVBQWUsT0FBTztBQUFHO0FBUTFHLFNBQVNDLE9BQU0sU0FBZ0Q7QUFBRSxTQUFPLE9BQU8sYUFBYSxPQUFPO0FBQUc7QUFRdEcsU0FBUyxTQUFTLFNBQWdEO0FBQUUsU0FBTyxPQUFPLGdCQUFnQixPQUFPO0FBQUc7QUFXNUcsU0FBUyxTQUFTLFNBQTREO0FBOUtyRixNQUFBQztBQThLdUYsVUFBT0EsTUFBQSxPQUFPLGdCQUFnQixPQUFPLE1BQTlCLE9BQUFBLE1BQW1DLENBQUM7QUFBRztBQVE5SCxTQUFTLFNBQVMsU0FBaUQ7QUFBRSxTQUFPLE9BQU8sZ0JBQWdCLE9BQU87QUFBRzs7O0FDdExwSDtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBOzs7QUNhTyxJQUFNLGlCQUFpQixvQkFBSSxJQUF3QjtBQUVuRCxJQUFNLFdBQU4sTUFBZTtBQUFBLEVBS2xCLFlBQVksV0FBbUIsVUFBK0IsY0FBc0I7QUFDaEYsU0FBSyxZQUFZO0FBQ2pCLFNBQUssV0FBVztBQUNoQixTQUFLLGVBQWUsZ0JBQWdCO0FBQUEsRUFDeEM7QUFBQSxFQUVBLFNBQVMsTUFBb0I7QUFDekIsUUFBSTtBQUNBLFdBQUssU0FBUyxJQUFJO0FBQUEsSUFDdEIsU0FBUyxLQUFLO0FBQ1YsY0FBUSxNQUFNLEdBQUc7QUFBQSxJQUNyQjtBQUVBLFFBQUksS0FBSyxpQkFBaUIsR0FBSSxRQUFPO0FBQ3JDLFNBQUssZ0JBQWdCO0FBQ3JCLFdBQU8sS0FBSyxpQkFBaUI7QUFBQSxFQUNqQztBQUNKO0FBRU8sU0FBUyxZQUFZLFVBQTBCO0FBQ2xELE1BQUksWUFBWSxlQUFlLElBQUksU0FBUyxTQUFTO0FBQ3JELE1BQUksQ0FBQyxXQUFXO0FBQ1o7QUFBQSxFQUNKO0FBRUEsY0FBWSxVQUFVLE9BQU8sT0FBSyxNQUFNLFFBQVE7QUFDaEQsTUFBSSxVQUFVLFdBQVcsR0FBRztBQUN4QixtQkFBZSxPQUFPLFNBQVMsU0FBUztBQUFBLEVBQzVDLE9BQU87QUFDSCxtQkFBZSxJQUFJLFNBQVMsV0FBVyxTQUFTO0FBQUEsRUFDcEQ7QUFDSjs7O0FDbkRBO0FBQUE7QUFBQTtBQUFBLGVBQUFDO0FBQUEsRUFBQTtBQUFBO0FBQUEsYUFBQUM7QUFBQSxFQUFBO0FBQUE7QUFBQTtBQWFPLFNBQVMsSUFBYSxRQUFnQjtBQUN6QyxTQUFPO0FBQ1g7QUFNTyxTQUFTLFVBQVUsUUFBcUI7QUFDM0MsU0FBUyxVQUFVLE9BQVEsS0FBSztBQUNwQztBQU9PLFNBQVNDLE9BQWUsU0FBbUQ7QUFDOUUsTUFBSSxZQUFZLEtBQUs7QUFDakIsV0FBTyxDQUFDLFdBQVksV0FBVyxPQUFPLENBQUMsSUFBSTtBQUFBLEVBQy9DO0FBRUEsU0FBTyxDQUFDLFdBQVc7QUFDZixRQUFJLFdBQVcsTUFBTTtBQUNqQixhQUFPLENBQUM7QUFBQSxJQUNaO0FBQ0EsYUFBUyxJQUFJLEdBQUcsSUFBSSxPQUFPLFFBQVEsS0FBSztBQUNwQyxhQUFPLENBQUMsSUFBSSxRQUFRLE9BQU8sQ0FBQyxDQUFDO0FBQUEsSUFDakM7QUFDQSxXQUFPO0FBQUEsRUFDWDtBQUNKO0FBT08sU0FBU0MsS0FBYSxLQUE4QixPQUErRDtBQUN0SCxNQUFJLFVBQVUsS0FBSztBQUNmLFdBQU8sQ0FBQyxXQUFZLFdBQVcsT0FBTyxDQUFDLElBQUk7QUFBQSxFQUMvQztBQUVBLFNBQU8sQ0FBQyxXQUFXO0FBQ2YsUUFBSSxXQUFXLE1BQU07QUFDakIsYUFBTyxDQUFDO0FBQUEsSUFDWjtBQUNBLGVBQVdDLFFBQU8sUUFBUTtBQUN0QixhQUFPQSxJQUFHLElBQUksTUFBTSxPQUFPQSxJQUFHLENBQUM7QUFBQSxJQUNuQztBQUNBLFdBQU87QUFBQSxFQUNYO0FBQ0o7QUFNTyxTQUFTLFNBQWtCLFNBQTBEO0FBQ3hGLE1BQUksWUFBWSxLQUFLO0FBQ2pCLFdBQU87QUFBQSxFQUNYO0FBRUEsU0FBTyxDQUFDLFdBQVksV0FBVyxPQUFPLE9BQU8sUUFBUSxNQUFNO0FBQy9EO0FBTU8sU0FBUyxPQUFPLGFBRXZCO0FBQ0ksTUFBSSxTQUFTO0FBQ2IsYUFBVyxRQUFRLGFBQWE7QUFDNUIsUUFBSSxZQUFZLElBQUksTUFBTSxLQUFLO0FBQzNCLGVBQVM7QUFDVDtBQUFBLElBQ0o7QUFBQSxFQUNKO0FBQ0EsTUFBSSxRQUFRO0FBQ1IsV0FBTztBQUFBLEVBQ1g7QUFFQSxTQUFPLENBQUMsV0FBVztBQUNmLGVBQVcsUUFBUSxhQUFhO0FBQzVCLFVBQUksUUFBUSxRQUFRO0FBQ2hCLGVBQU8sSUFBSSxJQUFJLFlBQVksSUFBSSxFQUFFLE9BQU8sSUFBSSxDQUFDO0FBQUEsTUFDakQ7QUFBQSxJQUNKO0FBQ0EsV0FBTztBQUFBLEVBQ1g7QUFDSjtBQU1PLElBQU0sU0FBK0MsQ0FBQzs7O0FDbEd0RCxJQUFNLFFBQVEsT0FBTyxPQUFPO0FBQUEsRUFDbEMsU0FBUyxPQUFPLE9BQU87QUFBQSxJQUN0Qix1QkFBdUI7QUFBQSxJQUN2QixzQkFBc0I7QUFBQSxJQUN0QixvQkFBb0I7QUFBQSxJQUNwQixrQkFBa0I7QUFBQSxJQUNsQixZQUFZO0FBQUEsSUFDWixvQkFBb0I7QUFBQSxJQUNwQixvQkFBb0I7QUFBQSxJQUNwQiw0QkFBNEI7QUFBQSxJQUM1QixjQUFjO0FBQUEsSUFDZCx1QkFBdUI7QUFBQSxJQUN2QixtQkFBbUI7QUFBQSxJQUNuQixlQUFlO0FBQUEsSUFDZixlQUFlO0FBQUEsSUFDZixpQkFBaUI7QUFBQSxJQUNqQixrQkFBa0I7QUFBQSxJQUNsQixnQkFBZ0I7QUFBQSxJQUNoQixpQkFBaUI7QUFBQSxJQUNqQixpQkFBaUI7QUFBQSxJQUNqQixnQkFBZ0I7QUFBQSxJQUNoQixlQUFlO0FBQUEsSUFDZixpQkFBaUI7QUFBQSxJQUNqQixrQkFBa0I7QUFBQSxJQUNsQixZQUFZO0FBQUEsSUFDWixnQkFBZ0I7QUFBQSxJQUNoQixlQUFlO0FBQUEsSUFDZixhQUFhO0FBQUEsSUFDYixpQkFBaUI7QUFBQSxJQUNqQixvQkFBb0I7QUFBQSxJQUNwQiwwQkFBMEI7QUFBQSxJQUMxQiwyQkFBMkI7QUFBQSxJQUMzQiwwQkFBMEI7QUFBQSxJQUMxQix3QkFBd0I7QUFBQSxJQUN4QixhQUFhO0FBQUEsSUFDYixlQUFlO0FBQUEsSUFDZixnQkFBZ0I7QUFBQSxJQUNoQixZQUFZO0FBQUEsSUFDWixpQkFBaUI7QUFBQSxJQUNqQixtQkFBbUI7QUFBQSxJQUNuQixvQkFBb0I7QUFBQSxJQUNwQixxQkFBcUI7QUFBQSxJQUNyQixnQkFBZ0I7QUFBQSxJQUNoQixrQkFBa0I7QUFBQSxJQUNsQixnQkFBZ0I7QUFBQSxJQUNoQixrQkFBa0I7QUFBQSxFQUNuQixDQUFDO0FBQUEsRUFDRCxLQUFLLE9BQU8sT0FBTztBQUFBLElBQ2xCLDRCQUE0QjtBQUFBLElBQzVCLHVDQUF1QztBQUFBLElBQ3ZDLHlDQUF5QztBQUFBLElBQ3pDLDBCQUEwQjtBQUFBLElBQzFCLG9DQUFvQztBQUFBLElBQ3BDLHNDQUFzQztBQUFBLElBQ3RDLG9DQUFvQztBQUFBLElBQ3BDLDBDQUEwQztBQUFBLElBQzFDLDJCQUEyQjtBQUFBLElBQzNCLCtCQUErQjtBQUFBLElBQy9CLG9CQUFvQjtBQUFBLElBQ3BCLDRCQUE0QjtBQUFBLElBQzVCLHNCQUFzQjtBQUFBLElBQ3RCLHNCQUFzQjtBQUFBLElBQ3RCLCtCQUErQjtBQUFBLElBQy9CLDZCQUE2QjtBQUFBLElBQzdCLGdDQUFnQztBQUFBLElBQ2hDLHFCQUFxQjtBQUFBLElBQ3JCLDZCQUE2QjtBQUFBLElBQzdCLDBCQUEwQjtBQUFBLElBQzFCLHVCQUF1QjtBQUFBLElBQ3ZCLHVCQUF1QjtBQUFBLElBQ3ZCLGdCQUFnQjtBQUFBLElBQ2hCLHNCQUFzQjtBQUFBLElBQ3RCLGNBQWM7QUFBQSxJQUNkLG9CQUFvQjtBQUFBLElBQ3BCLG9CQUFvQjtBQUFBLElBQ3BCLHNCQUFzQjtBQUFBLElBQ3RCLGFBQWE7QUFBQSxJQUNiLGNBQWM7QUFBQSxJQUNkLG1CQUFtQjtBQUFBLElBQ25CLG1CQUFtQjtBQUFBLElBQ25CLHlCQUF5QjtBQUFBLElBQ3pCLGVBQWU7QUFBQSxJQUNmLGlCQUFpQjtBQUFBLElBQ2pCLHVCQUF1QjtBQUFBLElBQ3ZCLHFCQUFxQjtBQUFBLElBQ3JCLHFCQUFxQjtBQUFBLElBQ3JCLHVCQUF1QjtBQUFBLElBQ3ZCLGNBQWM7QUFBQSxJQUNkLGVBQWU7QUFBQSxJQUNmLG9CQUFvQjtBQUFBLElBQ3BCLG9CQUFvQjtBQUFBLElBQ3BCLDBCQUEwQjtBQUFBLElBQzFCLGdCQUFnQjtBQUFBLElBQ2hCLDRCQUE0QjtBQUFBLElBQzVCLDRCQUE0QjtBQUFBLElBQzVCLHlEQUF5RDtBQUFBLElBQ3pELHNDQUFzQztBQUFBLElBQ3RDLG9CQUFvQjtBQUFBLElBQ3BCLHFCQUFxQjtBQUFBLElBQ3JCLHFCQUFxQjtBQUFBLElBQ3JCLHNCQUFzQjtBQUFBLElBQ3RCLGdDQUFnQztBQUFBLElBQ2hDLGtDQUFrQztBQUFBLElBQ2xDLG1DQUFtQztBQUFBLElBQ25DLG9DQUFvQztBQUFBLElBQ3BDLCtCQUErQjtBQUFBLElBQy9CLDZCQUE2QjtBQUFBLElBQzdCLHVCQUF1QjtBQUFBLElBQ3ZCLGlDQUFpQztBQUFBLElBQ2pDLDhCQUE4QjtBQUFBLElBQzlCLDRCQUE0QjtBQUFBLElBQzVCLHNDQUFzQztBQUFBLElBQ3RDLDRCQUE0QjtBQUFBLElBQzVCLHNCQUFzQjtBQUFBLElBQ3RCLGtDQUFrQztBQUFBLElBQ2xDLHNCQUFzQjtBQUFBLElBQ3RCLHdCQUF3QjtBQUFBLElBQ3hCLHdCQUF3QjtBQUFBLElBQ3hCLG1CQUFtQjtBQUFBLElBQ25CLDBCQUEwQjtBQUFBLElBQzFCLDhCQUE4QjtBQUFBLElBQzlCLHlCQUF5QjtBQUFBLElBQ3pCLDZCQUE2QjtBQUFBLElBQzdCLGlCQUFpQjtBQUFBLElBQ2pCLGdCQUFnQjtBQUFBLElBQ2hCLHNCQUFzQjtBQUFBLElBQ3RCLGVBQWU7QUFBQSxJQUNmLHlCQUF5QjtBQUFBLElBQ3pCLHdCQUF3QjtBQUFBLElBQ3hCLG9CQUFvQjtBQUFBLElBQ3BCLHFCQUFxQjtBQUFBLElBQ3JCLGlCQUFpQjtBQUFBLElBQ2pCLGlCQUFpQjtBQUFBLElBQ2pCLHNCQUFzQjtBQUFBLElBQ3RCLG1DQUFtQztBQUFBLElBQ25DLHFDQUFxQztBQUFBLElBQ3JDLHVCQUF1QjtBQUFBLElBQ3ZCLHNCQUFzQjtBQUFBLElBQ3RCLHdCQUF3QjtBQUFBLElBQ3hCLGVBQWU7QUFBQSxJQUNmLDJCQUEyQjtBQUFBLElBQzNCLDBCQUEwQjtBQUFBLElBQzFCLDZCQUE2QjtBQUFBLElBQzdCLFlBQVk7QUFBQSxJQUNaLGdCQUFnQjtBQUFBLElBQ2hCLGtCQUFrQjtBQUFBLElBQ2xCLGdCQUFnQjtBQUFBLElBQ2hCLGtCQUFrQjtBQUFBLElBQ2xCLG1CQUFtQjtBQUFBLElBQ25CLFlBQVk7QUFBQSxJQUNaLHFCQUFxQjtBQUFBLElBQ3JCLHNCQUFzQjtBQUFBLElBQ3RCLHNCQUFzQjtBQUFBLElBQ3RCLDhCQUE4QjtBQUFBLElBQzlCLGlCQUFpQjtBQUFBLElBQ2pCLHlCQUF5QjtBQUFBLElBQ3pCLDJCQUEyQjtBQUFBLElBQzNCLCtCQUErQjtBQUFBLElBQy9CLDBCQUEwQjtBQUFBLElBQzFCLDhCQUE4QjtBQUFBLElBQzlCLGlCQUFpQjtBQUFBLElBQ2pCLHVCQUF1QjtBQUFBLElBQ3ZCLGdCQUFnQjtBQUFBLElBQ2hCLDBCQUEwQjtBQUFBLElBQzFCLHlCQUF5QjtBQUFBLElBQ3pCLHNCQUFzQjtBQUFBLElBQ3RCLGtCQUFrQjtBQUFBLElBQ2xCLG1CQUFtQjtBQUFBLElBQ25CLGtCQUFrQjtBQUFBLElBQ2xCLHVCQUF1QjtBQUFBLElBQ3ZCLG9DQUFvQztBQUFBLElBQ3BDLHNDQUFzQztBQUFBLElBQ3RDLHdCQUF3QjtBQUFBLElBQ3hCLHVCQUF1QjtBQUFBLElBQ3ZCLHlCQUF5QjtBQUFBLElBQ3pCLDRCQUE0QjtBQUFBLElBQzVCLDRCQUE0QjtBQUFBLElBQzVCLGNBQWM7QUFBQSxJQUNkLGVBQWU7QUFBQSxJQUNmLGlCQUFpQjtBQUFBLEVBQ2xCLENBQUM7QUFBQSxFQUNELE9BQU8sT0FBTyxPQUFPO0FBQUEsSUFDcEIsb0JBQW9CO0FBQUEsSUFDcEIsb0JBQW9CO0FBQUEsSUFDcEIsbUJBQW1CO0FBQUEsSUFDbkIsZUFBZTtBQUFBLElBQ2YsaUJBQWlCO0FBQUEsSUFDakIsZUFBZTtBQUFBLElBQ2YsZ0JBQWdCO0FBQUEsSUFDaEIsbUJBQW1CO0FBQUEsSUFDbkIsc0JBQXNCO0FBQUEsSUFDdEIscUJBQXFCO0FBQUEsSUFDckIsb0JBQW9CO0FBQUEsRUFDckIsQ0FBQztBQUFBLEVBQ0QsS0FBSyxPQUFPLE9BQU87QUFBQSxJQUNsQiw0QkFBNEI7QUFBQSxJQUM1QiwrQkFBK0I7QUFBQSxJQUMvQiwrQkFBK0I7QUFBQSxJQUMvQixvQ0FBb0M7QUFBQSxJQUNwQyxnQ0FBZ0M7QUFBQSxJQUNoQyw2QkFBNkI7QUFBQSxJQUM3QiwwQkFBMEI7QUFBQSxJQUMxQixlQUFlO0FBQUEsSUFDZixrQkFBa0I7QUFBQSxJQUNsQixpQkFBaUI7QUFBQSxJQUNqQixxQkFBcUI7QUFBQSxJQUNyQixvQkFBb0I7QUFBQSxJQUNwQiw2QkFBNkI7QUFBQSxJQUM3QiwwQkFBMEI7QUFBQSxJQUMxQixrQkFBa0I7QUFBQSxJQUNsQixrQkFBa0I7QUFBQSxJQUNsQixrQkFBa0I7QUFBQSxJQUNsQixzQkFBc0I7QUFBQSxJQUN0QiwyQkFBMkI7QUFBQSxJQUMzQiw0QkFBNEI7QUFBQSxJQUM1QiwwQkFBMEI7QUFBQSxJQUMxQix3Q0FBd0M7QUFBQSxFQUN6QyxDQUFDO0FBQUEsRUFDRCxRQUFRLE9BQU8sT0FBTztBQUFBLElBQ3JCLDJCQUEyQjtBQUFBLElBQzNCLG9CQUFvQjtBQUFBLElBQ3BCLDRCQUE0QjtBQUFBLElBQzVCLGNBQWM7QUFBQSxJQUNkLGVBQWU7QUFBQSxJQUNmLGVBQWU7QUFBQSxJQUNmLGlCQUFpQjtBQUFBLElBQ2pCLGtCQUFrQjtBQUFBLElBQ2xCLG9CQUFvQjtBQUFBLElBQ3BCLGFBQWE7QUFBQSxJQUNiLGtCQUFrQjtBQUFBLElBQ2xCLFlBQVk7QUFBQSxJQUNaLGlCQUFpQjtBQUFBLElBQ2pCLGdCQUFnQjtBQUFBLElBQ2hCLGdCQUFnQjtBQUFBLElBQ2hCLHVCQUF1QjtBQUFBLElBQ3ZCLGVBQWU7QUFBQSxJQUNmLG9CQUFvQjtBQUFBLElBQ3BCLFlBQVk7QUFBQSxJQUNaLG9CQUFvQjtBQUFBLElBQ3BCLGtCQUFrQjtBQUFBLElBQ2xCLGtCQUFrQjtBQUFBLElBQ2xCLFlBQVk7QUFBQSxJQUNaLGNBQWM7QUFBQSxJQUNkLGVBQWU7QUFBQSxJQUNmLGlCQUFpQjtBQUFBLEVBQ2xCLENBQUM7QUFDRixDQUFDOzs7QUhuUEQsT0FBTyxTQUFTLE9BQU8sVUFBVSxDQUFDO0FBQ2xDLE9BQU8sT0FBTyxxQkFBcUI7QUFFbkMsSUFBTUMsUUFBTyxpQkFBaUIsWUFBWSxNQUFNO0FBQ2hELElBQU0sYUFBYTtBQW9DWixJQUFNLGFBQU4sTUFBNEQ7QUFBQSxFQW1CL0QsWUFBWSxNQUFTLE1BQVk7QUFDN0IsU0FBSyxPQUFPO0FBQ1osU0FBSyxPQUFPLHNCQUFRO0FBQUEsRUFDeEI7QUFDSjtBQUVBLFNBQVMsbUJBQW1CLE9BQVk7QUFDcEMsTUFBSSxZQUFZLGVBQWUsSUFBSSxNQUFNLElBQUk7QUFDN0MsTUFBSSxDQUFDLFdBQVc7QUFDWjtBQUFBLEVBQ0o7QUFFQSxNQUFJLGFBQWEsSUFBSTtBQUFBLElBQ2pCLE1BQU07QUFBQSxJQUNMLE1BQU0sUUFBUSxTQUFVLE9BQU8sTUFBTSxJQUFJLEVBQUUsTUFBTSxJQUFJLElBQUksTUFBTTtBQUFBLEVBQ3BFO0FBQ0EsTUFBSSxZQUFZLE9BQU87QUFDbkIsZUFBVyxTQUFTLE1BQU07QUFBQSxFQUM5QjtBQUVBLGNBQVksVUFBVSxPQUFPLGNBQVksQ0FBQyxTQUFTLFNBQVMsVUFBVSxDQUFDO0FBQ3ZFLE1BQUksVUFBVSxXQUFXLEdBQUc7QUFDeEIsbUJBQWUsT0FBTyxNQUFNLElBQUk7QUFBQSxFQUNwQyxPQUFPO0FBQ0gsbUJBQWUsSUFBSSxNQUFNLE1BQU0sU0FBUztBQUFBLEVBQzVDO0FBQ0o7QUFVTyxTQUFTLFdBQXNELFdBQWMsVUFBaUMsY0FBc0I7QUFDdkksTUFBSSxZQUFZLGVBQWUsSUFBSSxTQUFTLEtBQUssQ0FBQztBQUNsRCxRQUFNLGVBQWUsSUFBSSxTQUFTLFdBQVcsVUFBVSxZQUFZO0FBQ25FLFlBQVUsS0FBSyxZQUFZO0FBQzNCLGlCQUFlLElBQUksV0FBVyxTQUFTO0FBQ3ZDLFNBQU8sTUFBTSxZQUFZLFlBQVk7QUFDekM7QUFTTyxTQUFTLEdBQThDLFdBQWMsVUFBNkM7QUFDckgsU0FBTyxXQUFXLFdBQVcsVUFBVSxFQUFFO0FBQzdDO0FBU08sU0FBUyxLQUFnRCxXQUFjLFVBQTZDO0FBQ3ZILFNBQU8sV0FBVyxXQUFXLFVBQVUsQ0FBQztBQUM1QztBQU9PLFNBQVMsT0FBTyxZQUF5RDtBQUM1RSxhQUFXLFFBQVEsZUFBYSxlQUFlLE9BQU8sU0FBUyxDQUFDO0FBQ3BFO0FBS08sU0FBUyxTQUFlO0FBQzNCLGlCQUFlLE1BQU07QUFDekI7QUFXTyxTQUFTLEtBQWdELE1BQXlCLE1BQThCO0FBQ25ILFNBQU9BLE1BQUssWUFBYSxJQUFJLFdBQVcsTUFBTSxJQUFJLENBQUM7QUFDdkQ7OztBSXpKTyxTQUFTLFNBQVMsU0FBYztBQUVuQyxVQUFRO0FBQUEsSUFDSixrQkFBa0IsVUFBVTtBQUFBLElBQzVCO0FBQUEsSUFDQTtBQUFBLEVBQ0o7QUFDSjtBQU1PLFNBQVMsa0JBQTJCO0FBQ3ZDLFNBQVEsSUFBSSxXQUFXLFdBQVcsRUFBRyxZQUFZO0FBQ3JEO0FBTU8sU0FBUyxvQkFBb0I7QUFDaEMsTUFBSSxDQUFDLGVBQWUsQ0FBQyxlQUFlLENBQUM7QUFDakMsV0FBTztBQUVYLE1BQUksU0FBUztBQUViLFFBQU0sU0FBUyxJQUFJLFlBQVk7QUFDL0IsUUFBTSxhQUFhLElBQUksZ0JBQWdCO0FBQ3ZDLFNBQU8saUJBQWlCLFFBQVEsTUFBTTtBQUFFLGFBQVM7QUFBQSxFQUFPLEdBQUcsRUFBRSxRQUFRLFdBQVcsT0FBTyxDQUFDO0FBQ3hGLGFBQVcsTUFBTTtBQUNqQixTQUFPLGNBQWMsSUFBSSxZQUFZLE1BQU0sQ0FBQztBQUU1QyxTQUFPO0FBQ1g7QUFLTyxTQUFTLFlBQVksT0FBMkI7QUF0RHZELE1BQUFDO0FBdURJLE1BQUksTUFBTSxrQkFBa0IsYUFBYTtBQUNyQyxXQUFPLE1BQU07QUFBQSxFQUNqQixXQUFXLEVBQUUsTUFBTSxrQkFBa0IsZ0JBQWdCLE1BQU0sa0JBQWtCLE1BQU07QUFDL0UsWUFBT0EsTUFBQSxNQUFNLE9BQU8sa0JBQWIsT0FBQUEsTUFBOEIsU0FBUztBQUFBLEVBQ2xELE9BQU87QUFDSCxXQUFPLFNBQVM7QUFBQSxFQUNwQjtBQUNKO0FBaUNBLElBQUksVUFBVTtBQUNkLFNBQVMsaUJBQWlCLG9CQUFvQixNQUFNO0FBQUUsWUFBVTtBQUFLLENBQUM7QUFFL0QsU0FBUyxVQUFVLFVBQXNCO0FBQzVDLE1BQUksV0FBVyxTQUFTLGVBQWUsWUFBWTtBQUMvQyxhQUFTO0FBQUEsRUFDYixPQUFPO0FBQ0gsYUFBUyxpQkFBaUIsb0JBQW9CLFFBQVE7QUFBQSxFQUMxRDtBQUNKOzs7QUMxRkEsSUFBTSx3QkFBd0I7QUFDOUIsSUFBTSwyQkFBMkI7QUFDakMsSUFBSSxvQkFBb0M7QUFFeEMsSUFBTSxpQkFBb0M7QUFDMUMsSUFBTSxlQUFvQztBQUMxQyxJQUFNLGNBQW9DO0FBQzFDLElBQU0sK0JBQW9DO0FBQzFDLElBQU0sOEJBQW9DO0FBQzFDLElBQU0sY0FBb0M7QUFDMUMsSUFBTSxvQkFBb0M7QUFDMUMsSUFBTSxtQkFBb0M7QUFDMUMsSUFBTSxrQkFBb0M7QUFDMUMsSUFBTSxnQkFBb0M7QUFDMUMsSUFBTSxlQUFvQztBQUMxQyxJQUFNLGFBQW9DO0FBQzFDLElBQU0sa0JBQW9DO0FBQzFDLElBQU0scUJBQW9DO0FBQzFDLElBQU0sb0JBQW9DO0FBQzFDLElBQU0sb0JBQW9DO0FBQzFDLElBQU0saUJBQW9DO0FBQzFDLElBQU0saUJBQW9DO0FBQzFDLElBQU0sYUFBb0M7QUFDMUMsSUFBTSxxQkFBb0M7QUFDMUMsSUFBTSx5QkFBb0M7QUFDMUMsSUFBTSxlQUFvQztBQUMxQyxJQUFNLGtCQUFvQztBQUMxQyxJQUFNLGdCQUFvQztBQUMxQyxJQUFNLG9CQUFvQztBQUMxQyxJQUFNLHVCQUFvQztBQUMxQyxJQUFNLDRCQUFvQztBQUMxQyxJQUFNLHFCQUFvQztBQUMxQyxJQUFNLG1DQUFvQztBQUMxQyxJQUFNLG1CQUFvQztBQUMxQyxJQUFNLG1CQUFvQztBQUMxQyxJQUFNLDRCQUFvQztBQUMxQyxJQUFNLHFCQUFvQztBQUMxQyxJQUFNLGdCQUFvQztBQUMxQyxJQUFNLGlCQUFvQztBQUMxQyxJQUFNLGdCQUFvQztBQUMxQyxJQUFNLGFBQW9DO0FBQzFDLElBQU0sYUFBb0M7QUFDMUMsSUFBTSx5QkFBb0M7QUFDMUMsSUFBTSx1QkFBb0M7QUFDMUMsSUFBTSx3QkFBb0M7QUFDMUMsSUFBTSxxQkFBb0M7QUFDMUMsSUFBTSxtQkFBb0M7QUFDMUMsSUFBTSxtQkFBb0M7QUFDMUMsSUFBTSxjQUFvQztBQUMxQyxJQUFNLGFBQW9DO0FBQzFDLElBQU0sZUFBb0M7QUFDMUMsSUFBTSxnQkFBb0M7QUFDMUMsSUFBTSxrQkFBb0M7QUFDMUMsSUFBTSxtQkFBb0M7QUFDMUMsSUFBTSxlQUFvQztBQUMxQyxJQUFNLGNBQW9DO0FBSzFDLFNBQVMscUJBQXFCLFNBQXlDO0FBQ25FLE1BQUksQ0FBQyxTQUFTO0FBQ1YsV0FBTztBQUFBLEVBQ1g7QUFDQSxTQUFPLFFBQVEsUUFBUSxJQUFJLDhCQUFxQixJQUFHO0FBQ3ZEO0FBTUEsU0FBUyxzQkFBK0I7QUFyRnhDLE1BQUFDLEtBQUE7QUF1RkksUUFBSyxNQUFBQSxNQUFBLE9BQWUsV0FBZixnQkFBQUEsSUFBdUIsWUFBdkIsbUJBQWdDLHFDQUFvQyxNQUFNO0FBQzNFLFdBQU87QUFBQSxFQUNYO0FBR0EsV0FBUSxrQkFBZSxXQUFmLG1CQUF1QixVQUF2QixtQkFBOEIsb0JBQW1CO0FBQzdEO0FBS0EsU0FBUyxpQkFBaUIsR0FBVyxHQUFXLE9BQXFCO0FBbEdyRSxNQUFBQSxLQUFBO0FBbUdJLE9BQUssTUFBQUEsTUFBQSxPQUFlLFdBQWYsZ0JBQUFBLElBQXVCLFlBQXZCLG1CQUFnQyxrQ0FBa0M7QUFDbkUsSUFBQyxPQUFlLE9BQU8sUUFBUSxpQ0FBaUMsYUFBYSxVQUFDLEtBQUksV0FBSyxLQUFLO0FBQUEsRUFDaEc7QUFDSjtBQUdBLElBQUksbUJBQW1CO0FBTXZCLFNBQVMsb0JBQTBCO0FBQy9CLHFCQUFtQjtBQUNuQixNQUFJLG1CQUFtQjtBQUNuQixzQkFBa0IsVUFBVSxPQUFPLHdCQUF3QjtBQUMzRCx3QkFBb0I7QUFBQSxFQUN4QjtBQUNKO0FBS0EsU0FBUyxrQkFBd0I7QUExSGpDLE1BQUFBLEtBQUE7QUE0SEksUUFBSyxNQUFBQSxNQUFBLE9BQWUsV0FBZixnQkFBQUEsSUFBdUIsVUFBdkIsbUJBQThCLG9CQUFtQixPQUFPO0FBQ3pEO0FBQUEsRUFDSjtBQUNBLHFCQUFtQjtBQUN2QjtBQUtBLFNBQVMsa0JBQXdCO0FBQzdCLG9CQUFrQjtBQUN0QjtBQU9BLFNBQVMsZUFBZSxHQUFXLEdBQWlCO0FBOUlwRCxNQUFBQSxLQUFBO0FBK0lJLE1BQUksQ0FBQyxpQkFBa0I7QUFHdkIsUUFBSyxNQUFBQSxNQUFBLE9BQWUsV0FBZixnQkFBQUEsSUFBdUIsVUFBdkIsbUJBQThCLG9CQUFtQixPQUFPO0FBQ3pEO0FBQUEsRUFDSjtBQUVBLFFBQU0sZ0JBQWdCLFNBQVMsaUJBQWlCLEdBQUcsQ0FBQztBQUNwRCxRQUFNLGFBQWEscUJBQXFCLGFBQWE7QUFFckQsTUFBSSxxQkFBcUIsc0JBQXNCLFlBQVk7QUFDdkQsc0JBQWtCLFVBQVUsT0FBTyx3QkFBd0I7QUFBQSxFQUMvRDtBQUVBLE1BQUksWUFBWTtBQUNaLGVBQVcsVUFBVSxJQUFJLHdCQUF3QjtBQUNqRCx3QkFBb0I7QUFBQSxFQUN4QixPQUFPO0FBQ0gsd0JBQW9CO0FBQUEsRUFDeEI7QUFDSjtBQTRCQSxJQUFNLFlBQVksdUJBQU8sUUFBUTtBQUlwQjtBQUZiLElBQU0sVUFBTixNQUFNLFFBQU87QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQVVULFlBQVksT0FBZSxJQUFJO0FBQzNCLFNBQUssU0FBUyxJQUFJLGlCQUFpQixZQUFZLFFBQVEsSUFBSTtBQUczRCxlQUFXLFVBQVUsT0FBTyxvQkFBb0IsUUFBTyxTQUFTLEdBQUc7QUFDL0QsVUFDSSxXQUFXLGlCQUNSLE9BQVEsS0FBYSxNQUFNLE1BQU0sWUFDdEM7QUFDRSxRQUFDLEtBQWEsTUFBTSxJQUFLLEtBQWEsTUFBTSxFQUFFLEtBQUssSUFBSTtBQUFBLE1BQzNEO0FBQUEsSUFDSjtBQUFBLEVBQ0o7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQVFBLElBQUksTUFBc0I7QUFDdEIsV0FBTyxJQUFJLFFBQU8sSUFBSTtBQUFBLEVBQzFCO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsV0FBOEI7QUFDMUIsV0FBTyxLQUFLLFNBQVMsRUFBRSxjQUFjO0FBQUEsRUFDekM7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLFNBQXdCO0FBQ3BCLFdBQU8sS0FBSyxTQUFTLEVBQUUsWUFBWTtBQUFBLEVBQ3ZDO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxRQUF1QjtBQUNuQixXQUFPLEtBQUssU0FBUyxFQUFFLFdBQVc7QUFBQSxFQUN0QztBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EseUJBQXdDO0FBQ3BDLFdBQU8sS0FBSyxTQUFTLEVBQUUsNEJBQTRCO0FBQUEsRUFDdkQ7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLHdCQUF1QztBQUNuQyxXQUFPLEtBQUssU0FBUyxFQUFFLDJCQUEyQjtBQUFBLEVBQ3REO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxRQUF1QjtBQUNuQixXQUFPLEtBQUssU0FBUyxFQUFFLFdBQVc7QUFBQSxFQUN0QztBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EsY0FBNkI7QUFDekIsV0FBTyxLQUFLLFNBQVMsRUFBRSxpQkFBaUI7QUFBQSxFQUM1QztBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EsYUFBNEI7QUFDeEIsV0FBTyxLQUFLLFNBQVMsRUFBRSxnQkFBZ0I7QUFBQSxFQUMzQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQU9BLFlBQTZCO0FBQ3pCLFdBQU8sS0FBSyxTQUFTLEVBQUUsZUFBZTtBQUFBLEVBQzFDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsVUFBMkI7QUFDdkIsV0FBTyxLQUFLLFNBQVMsRUFBRSxhQUFhO0FBQUEsRUFDeEM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxTQUEwQjtBQUN0QixXQUFPLEtBQUssU0FBUyxFQUFFLFlBQVk7QUFBQSxFQUN2QztBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EsT0FBc0I7QUFDbEIsV0FBTyxLQUFLLFNBQVMsRUFBRSxVQUFVO0FBQUEsRUFDckM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxZQUE4QjtBQUMxQixXQUFPLEtBQUssU0FBUyxFQUFFLGVBQWU7QUFBQSxFQUMxQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQU9BLGVBQWlDO0FBQzdCLFdBQU8sS0FBSyxTQUFTLEVBQUUsa0JBQWtCO0FBQUEsRUFDN0M7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxjQUFnQztBQUM1QixXQUFPLEtBQUssU0FBUyxFQUFFLGlCQUFpQjtBQUFBLEVBQzVDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsY0FBZ0M7QUFDNUIsV0FBTyxLQUFLLFNBQVMsRUFBRSxpQkFBaUI7QUFBQSxFQUM1QztBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EsV0FBMEI7QUFDdEIsV0FBTyxLQUFLLFNBQVMsRUFBRSxjQUFjO0FBQUEsRUFDekM7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLFdBQTBCO0FBQ3RCLFdBQU8sS0FBSyxTQUFTLEVBQUUsY0FBYztBQUFBLEVBQ3pDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsT0FBd0I7QUFDcEIsV0FBTyxLQUFLLFNBQVMsRUFBRSxVQUFVO0FBQUEsRUFDckM7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLGVBQThCO0FBQzFCLFdBQU8sS0FBSyxTQUFTLEVBQUUsa0JBQWtCO0FBQUEsRUFDN0M7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxtQkFBc0M7QUFDbEMsV0FBTyxLQUFLLFNBQVMsRUFBRSxzQkFBc0I7QUFBQSxFQUNqRDtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EsU0FBd0I7QUFDcEIsV0FBTyxLQUFLLFNBQVMsRUFBRSxZQUFZO0FBQUEsRUFDdkM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxZQUE4QjtBQUMxQixXQUFPLEtBQUssU0FBUyxFQUFFLGVBQWU7QUFBQSxFQUMxQztBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EsVUFBeUI7QUFDckIsV0FBTyxLQUFLLFNBQVMsRUFBRSxhQUFhO0FBQUEsRUFDeEM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQVFBLFlBQVksR0FBVyxHQUEwQjtBQUM3QyxXQUFPLEtBQUssU0FBUyxFQUFFLG1CQUFtQixFQUFFLEdBQUcsRUFBRSxDQUFDO0FBQUEsRUFDdEQ7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxlQUFlLGFBQXFDO0FBQ2hELFdBQU8sS0FBSyxTQUFTLEVBQUUsc0JBQXNCLEVBQUUsWUFBWSxDQUFDO0FBQUEsRUFDaEU7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFVQSxvQkFBb0IsR0FBVyxHQUFXLEdBQVcsR0FBMEI7QUFDM0UsV0FBTyxLQUFLLFNBQVMsRUFBRSwyQkFBMkIsRUFBRSxHQUFHLEdBQUcsR0FBRyxFQUFFLENBQUM7QUFBQSxFQUNwRTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQU9BLGFBQWEsV0FBbUM7QUFDNUMsV0FBTyxLQUFLLFNBQVMsRUFBRSxvQkFBb0IsRUFBRSxVQUFVLENBQUM7QUFBQSxFQUM1RDtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQU9BLDJCQUEyQixTQUFpQztBQUN4RCxXQUFPLEtBQUssU0FBUyxFQUFFLGtDQUFrQyxFQUFFLFFBQVEsQ0FBQztBQUFBLEVBQ3hFO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFRQSxXQUFXLE9BQWUsUUFBK0I7QUFDckQsV0FBTyxLQUFLLFNBQVMsRUFBRSxrQkFBa0IsRUFBRSxPQUFPLE9BQU8sQ0FBQztBQUFBLEVBQzlEO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFRQSxXQUFXLE9BQWUsUUFBK0I7QUFDckQsV0FBTyxLQUFLLFNBQVMsRUFBRSxrQkFBa0IsRUFBRSxPQUFPLE9BQU8sQ0FBQztBQUFBLEVBQzlEO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFRQSxvQkFBb0IsR0FBVyxHQUEwQjtBQUNyRCxXQUFPLEtBQUssU0FBUyxFQUFFLDJCQUEyQixFQUFFLEdBQUcsRUFBRSxDQUFDO0FBQUEsRUFDOUQ7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxhQUFhQyxZQUFtQztBQUM1QyxXQUFPLEtBQUssU0FBUyxFQUFFLG9CQUFvQixFQUFFLFdBQUFBLFdBQVUsQ0FBQztBQUFBLEVBQzVEO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFRQSxRQUFRLE9BQWUsUUFBK0I7QUFDbEQsV0FBTyxLQUFLLFNBQVMsRUFBRSxlQUFlLEVBQUUsT0FBTyxPQUFPLENBQUM7QUFBQSxFQUMzRDtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQU9BLFNBQVMsT0FBOEI7QUFDbkMsV0FBTyxLQUFLLFNBQVMsRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLENBQUM7QUFBQSxFQUNwRDtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQU9BLFFBQVEsTUFBNkI7QUFDakMsV0FBTyxLQUFLLFNBQVMsRUFBRSxlQUFlLEVBQUUsS0FBSyxDQUFDO0FBQUEsRUFDbEQ7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLE9BQXNCO0FBQ2xCLFdBQU8sS0FBSyxTQUFTLEVBQUUsVUFBVTtBQUFBLEVBQ3JDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsT0FBc0I7QUFDbEIsV0FBTyxLQUFLLFNBQVMsRUFBRSxVQUFVO0FBQUEsRUFDckM7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLG1CQUFrQztBQUM5QixXQUFPLEtBQUssU0FBUyxFQUFFLHNCQUFzQjtBQUFBLEVBQ2pEO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxpQkFBZ0M7QUFDNUIsV0FBTyxLQUFLLFNBQVMsRUFBRSxvQkFBb0I7QUFBQSxFQUMvQztBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0Esa0JBQWlDO0FBQzdCLFdBQU8sS0FBSyxTQUFTLEVBQUUscUJBQXFCO0FBQUEsRUFDaEQ7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLGVBQThCO0FBQzFCLFdBQU8sS0FBSyxTQUFTLEVBQUUsa0JBQWtCO0FBQUEsRUFDN0M7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLGFBQTRCO0FBQ3hCLFdBQU8sS0FBSyxTQUFTLEVBQUUsZ0JBQWdCO0FBQUEsRUFDM0M7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLGFBQTRCO0FBQ3hCLFdBQU8sS0FBSyxTQUFTLEVBQUUsZ0JBQWdCO0FBQUEsRUFDM0M7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxRQUF5QjtBQUNyQixXQUFPLEtBQUssU0FBUyxFQUFFLFdBQVc7QUFBQSxFQUN0QztBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EsT0FBc0I7QUFDbEIsV0FBTyxLQUFLLFNBQVMsRUFBRSxVQUFVO0FBQUEsRUFDckM7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLFNBQXdCO0FBQ3BCLFdBQU8sS0FBSyxTQUFTLEVBQUUsWUFBWTtBQUFBLEVBQ3ZDO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxVQUF5QjtBQUNyQixXQUFPLEtBQUssU0FBUyxFQUFFLGFBQWE7QUFBQSxFQUN4QztBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EsWUFBMkI7QUFDdkIsV0FBTyxLQUFLLFNBQVMsRUFBRSxlQUFlO0FBQUEsRUFDMUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFVQSx1QkFBdUIsV0FBcUIsR0FBVyxHQUFpQjtBQTVuQjVFLFFBQUFDLEtBQUE7QUE4bkJRLFVBQUssTUFBQUEsTUFBQSxPQUFlLFdBQWYsZ0JBQUFBLElBQXVCLFVBQXZCLG1CQUE4QixvQkFBbUIsT0FBTztBQUN6RDtBQUFBLElBQ0o7QUFFQSxVQUFNLFVBQVUsU0FBUyxpQkFBaUIsR0FBRyxDQUFDO0FBQzlDLFVBQU0sYUFBYSxxQkFBcUIsT0FBTztBQUUvQyxRQUFJLENBQUMsWUFBWTtBQUViO0FBQUEsSUFDSjtBQUVBLFVBQU0saUJBQWlCO0FBQUEsTUFDbkIsSUFBSSxXQUFXO0FBQUEsTUFDZixXQUFXLE1BQU0sS0FBSyxXQUFXLFNBQVM7QUFBQSxNQUMxQyxZQUFZLENBQUM7QUFBQSxJQUNqQjtBQUNBLGFBQVMsSUFBSSxHQUFHLElBQUksV0FBVyxXQUFXLFFBQVEsS0FBSztBQUNuRCxZQUFNLE9BQU8sV0FBVyxXQUFXLENBQUM7QUFDcEMscUJBQWUsV0FBVyxLQUFLLElBQUksSUFBSSxLQUFLO0FBQUEsSUFDaEQ7QUFFQSxVQUFNLFVBQVU7QUFBQSxNQUNaO0FBQUEsTUFDQTtBQUFBLE1BQ0E7QUFBQSxNQUNBO0FBQUEsSUFDSjtBQUVBLFNBQUssU0FBUyxFQUFFLGNBQWMsT0FBTztBQUdyQyxzQkFBa0I7QUFBQSxFQUN0QjtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EsYUFBNEI7QUFDeEIsV0FBTyxLQUFLLFNBQVMsRUFBRSxnQkFBZ0I7QUFBQSxFQUMzQztBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EsUUFBdUI7QUFDbkIsV0FBTyxLQUFLLFNBQVMsRUFBRSxXQUFXO0FBQUEsRUFDdEM7QUFDSjtBQTdlQSxJQUFNLFNBQU47QUFrZkEsSUFBTSxhQUFhLElBQUksT0FBTyxFQUFFO0FBTWhDLFNBQVMsMkJBQTJCO0FBQ2hDLFFBQU0sYUFBYSxTQUFTO0FBQzVCLE1BQUksbUJBQW1CO0FBRXZCLGFBQVcsaUJBQWlCLGFBQWEsQ0FBQyxVQUFVO0FBN3JCeEQsUUFBQUEsS0FBQTtBQThyQlEsUUFBSSxHQUFDQSxNQUFBLE1BQU0saUJBQU4sZ0JBQUFBLElBQW9CLE1BQU0sU0FBUyxXQUFVO0FBQzlDO0FBQUEsSUFDSjtBQUNBLFVBQU0sZUFBZTtBQUVyQixVQUFLLGtCQUFlLFdBQWYsbUJBQXVCLFVBQXZCLG1CQUE4QixvQkFBbUIsT0FBTztBQUN6RCxZQUFNLGFBQWEsYUFBYTtBQUNoQztBQUFBLElBQ0o7QUFDQTtBQUVBLFVBQU0sZ0JBQWdCLFNBQVMsaUJBQWlCLE1BQU0sU0FBUyxNQUFNLE9BQU87QUFDNUUsVUFBTSxhQUFhLHFCQUFxQixhQUFhO0FBR3JELFFBQUkscUJBQXFCLHNCQUFzQixZQUFZO0FBQ3ZELHdCQUFrQixVQUFVLE9BQU8sd0JBQXdCO0FBQUEsSUFDL0Q7QUFFQSxRQUFJLFlBQVk7QUFDWixpQkFBVyxVQUFVLElBQUksd0JBQXdCO0FBQ2pELFlBQU0sYUFBYSxhQUFhO0FBQ2hDLDBCQUFvQjtBQUFBLElBQ3hCLE9BQU87QUFDSCxZQUFNLGFBQWEsYUFBYTtBQUNoQywwQkFBb0I7QUFBQSxJQUN4QjtBQUFBLEVBQ0osR0FBRyxLQUFLO0FBRVIsYUFBVyxpQkFBaUIsWUFBWSxDQUFDLFVBQVU7QUEzdEJ2RCxRQUFBQSxLQUFBO0FBNHRCUSxRQUFJLEdBQUNBLE1BQUEsTUFBTSxpQkFBTixnQkFBQUEsSUFBb0IsTUFBTSxTQUFTLFdBQVU7QUFDOUM7QUFBQSxJQUNKO0FBQ0EsVUFBTSxlQUFlO0FBRXJCLFVBQUssa0JBQWUsV0FBZixtQkFBdUIsVUFBdkIsbUJBQThCLG9CQUFtQixPQUFPO0FBQ3pELFlBQU0sYUFBYSxhQUFhO0FBQ2hDO0FBQUEsSUFDSjtBQUdBLFVBQU0sZ0JBQWdCLFNBQVMsaUJBQWlCLE1BQU0sU0FBUyxNQUFNLE9BQU87QUFDNUUsVUFBTSxhQUFhLHFCQUFxQixhQUFhO0FBRXJELFFBQUkscUJBQXFCLHNCQUFzQixZQUFZO0FBQ3ZELHdCQUFrQixVQUFVLE9BQU8sd0JBQXdCO0FBQUEsSUFDL0Q7QUFFQSxRQUFJLFlBQVk7QUFDWixVQUFJLENBQUMsV0FBVyxVQUFVLFNBQVMsd0JBQXdCLEdBQUc7QUFDMUQsbUJBQVcsVUFBVSxJQUFJLHdCQUF3QjtBQUFBLE1BQ3JEO0FBQ0EsWUFBTSxhQUFhLGFBQWE7QUFDaEMsMEJBQW9CO0FBQUEsSUFDeEIsT0FBTztBQUNILFlBQU0sYUFBYSxhQUFhO0FBQ2hDLDBCQUFvQjtBQUFBLElBQ3hCO0FBQUEsRUFDSixHQUFHLEtBQUs7QUFFUixhQUFXLGlCQUFpQixhQUFhLENBQUMsVUFBVTtBQTF2QnhELFFBQUFBLEtBQUE7QUEydkJRLFFBQUksR0FBQ0EsTUFBQSxNQUFNLGlCQUFOLGdCQUFBQSxJQUFvQixNQUFNLFNBQVMsV0FBVTtBQUM5QztBQUFBLElBQ0o7QUFDQSxVQUFNLGVBQWU7QUFFckIsVUFBSyxrQkFBZSxXQUFmLG1CQUF1QixVQUF2QixtQkFBOEIsb0JBQW1CLE9BQU87QUFDekQ7QUFBQSxJQUNKO0FBSUEsUUFBSSxNQUFNLGtCQUFrQixNQUFNO0FBQzlCO0FBQUEsSUFDSjtBQUVBO0FBRUEsUUFBSSxxQkFBcUIsS0FDcEIscUJBQXFCLENBQUMsa0JBQWtCLFNBQVMsTUFBTSxhQUFxQixHQUFJO0FBQ2pGLFVBQUksbUJBQW1CO0FBQ25CLDBCQUFrQixVQUFVLE9BQU8sd0JBQXdCO0FBQzNELDRCQUFvQjtBQUFBLE1BQ3hCO0FBQ0EseUJBQW1CO0FBQUEsSUFDdkI7QUFBQSxFQUNKLEdBQUcsS0FBSztBQUVSLGFBQVcsaUJBQWlCLFFBQVEsQ0FBQyxVQUFVO0FBdHhCbkQsUUFBQUEsS0FBQTtBQXV4QlEsUUFBSSxHQUFDQSxNQUFBLE1BQU0saUJBQU4sZ0JBQUFBLElBQW9CLE1BQU0sU0FBUyxXQUFVO0FBQzlDO0FBQUEsSUFDSjtBQUNBLFVBQU0sZUFBZTtBQUVyQixVQUFLLGtCQUFlLFdBQWYsbUJBQXVCLFVBQXZCLG1CQUE4QixvQkFBbUIsT0FBTztBQUN6RDtBQUFBLElBQ0o7QUFDQSx1QkFBbUI7QUFFbkIsUUFBSSxtQkFBbUI7QUFDbkIsd0JBQWtCLFVBQVUsT0FBTyx3QkFBd0I7QUFDM0QsMEJBQW9CO0FBQUEsSUFDeEI7QUFJQSxRQUFJLG9CQUFvQixHQUFHO0FBQ3ZCLFlBQU0sUUFBZ0IsQ0FBQztBQUN2QixVQUFJLE1BQU0sYUFBYSxPQUFPO0FBQzFCLG1CQUFXLFFBQVEsTUFBTSxhQUFhLE9BQU87QUFDekMsY0FBSSxLQUFLLFNBQVMsUUFBUTtBQUN0QixrQkFBTSxPQUFPLEtBQUssVUFBVTtBQUM1QixnQkFBSSxLQUFNLE9BQU0sS0FBSyxJQUFJO0FBQUEsVUFDN0I7QUFBQSxRQUNKO0FBQUEsTUFDSixXQUFXLE1BQU0sYUFBYSxPQUFPO0FBQ2pDLG1CQUFXLFFBQVEsTUFBTSxhQUFhLE9BQU87QUFDekMsZ0JBQU0sS0FBSyxJQUFJO0FBQUEsUUFDbkI7QUFBQSxNQUNKO0FBRUEsVUFBSSxNQUFNLFNBQVMsR0FBRztBQUNsQix5QkFBaUIsTUFBTSxTQUFTLE1BQU0sU0FBUyxLQUFLO0FBQUEsTUFDeEQ7QUFBQSxJQUNKO0FBQUEsRUFDSixHQUFHLEtBQUs7QUFDWjtBQUdBLElBQUksT0FBTyxXQUFXLGVBQWUsT0FBTyxhQUFhLGFBQWE7QUFDbEUsMkJBQXlCO0FBQzdCO0FBRUEsSUFBTyxpQkFBUTs7O0FWN3lCZixTQUFTLFVBQVUsV0FBbUIsT0FBWSxNQUFZO0FBQzFELE9BQUssV0FBVyxJQUFJO0FBQ3hCO0FBUUEsU0FBUyxpQkFBaUIsWUFBb0IsWUFBb0I7QUFDOUQsUUFBTSxlQUFlLGVBQU8sSUFBSSxVQUFVO0FBQzFDLFFBQU0sU0FBVSxhQUFxQixVQUFVO0FBRS9DLE1BQUksT0FBTyxXQUFXLFlBQVk7QUFDOUIsWUFBUSxNQUFNLGtCQUFrQixtQkFBVSxjQUFhO0FBQ3ZEO0FBQUEsRUFDSjtBQUVBLE1BQUk7QUFDQSxXQUFPLEtBQUssWUFBWTtBQUFBLEVBQzVCLFNBQVMsR0FBRztBQUNSLFlBQVEsTUFBTSxnQ0FBZ0MsbUJBQVUsUUFBTyxDQUFDO0FBQUEsRUFDcEU7QUFDSjtBQUtBLFNBQVMsZUFBZSxJQUFpQjtBQUNyQyxRQUFNLFVBQVUsR0FBRztBQUVuQixXQUFTLFVBQVUsU0FBUyxPQUFPO0FBQy9CLFFBQUksV0FBVztBQUNYO0FBRUosVUFBTSxZQUFZLFFBQVEsYUFBYSxXQUFXLEtBQUssUUFBUSxhQUFhLGdCQUFnQjtBQUM1RixVQUFNLGVBQWUsUUFBUSxhQUFhLG1CQUFtQixLQUFLLFFBQVEsYUFBYSx3QkFBd0IsS0FBSztBQUNwSCxVQUFNLGVBQWUsUUFBUSxhQUFhLFlBQVksS0FBSyxRQUFRLGFBQWEsaUJBQWlCO0FBQ2pHLFVBQU0sTUFBTSxRQUFRLGFBQWEsYUFBYSxLQUFLLFFBQVEsYUFBYSxrQkFBa0I7QUFFMUYsUUFBSSxjQUFjO0FBQ2QsZ0JBQVUsU0FBUztBQUN2QixRQUFJLGlCQUFpQjtBQUNqQix1QkFBaUIsY0FBYyxZQUFZO0FBQy9DLFFBQUksUUFBUTtBQUNSLFdBQUssUUFBUSxHQUFHO0FBQUEsRUFDeEI7QUFFQSxRQUFNLFVBQVUsUUFBUSxhQUFhLGFBQWEsS0FBSyxRQUFRLGFBQWEsa0JBQWtCO0FBRTlGLE1BQUksU0FBUztBQUNULGFBQVM7QUFBQSxNQUNMLE9BQU87QUFBQSxNQUNQLFNBQVM7QUFBQSxNQUNULFVBQVU7QUFBQSxNQUNWLFNBQVM7QUFBQSxRQUNMLEVBQUUsT0FBTyxNQUFNO0FBQUEsUUFDZixFQUFFLE9BQU8sTUFBTSxXQUFXLEtBQUs7QUFBQSxNQUNuQztBQUFBLElBQ0osQ0FBQyxFQUFFLEtBQUssU0FBUztBQUFBLEVBQ3JCLE9BQU87QUFDSCxjQUFVO0FBQUEsRUFDZDtBQUNKO0FBR0EsSUFBTSxnQkFBZ0IsdUJBQU8sWUFBWTtBQUN6QyxJQUFNLGdCQUFnQix1QkFBTyxZQUFZO0FBQ3pDLElBQU0sa0JBQWtCLHVCQUFPLGNBQWM7QUFReEM7QUFGTCxJQUFNLDBCQUFOLE1BQThCO0FBQUEsRUFJMUIsY0FBYztBQUNWLFNBQUssYUFBYSxJQUFJLElBQUksZ0JBQWdCO0FBQUEsRUFDOUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBU0EsSUFBSSxTQUFrQixVQUE2QztBQUMvRCxXQUFPLEVBQUUsUUFBUSxLQUFLLGFBQWEsRUFBRSxPQUFPO0FBQUEsRUFDaEQ7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLFFBQWM7QUFDVixTQUFLLGFBQWEsRUFBRSxNQUFNO0FBQzFCLFNBQUssYUFBYSxJQUFJLElBQUksZ0JBQWdCO0FBQUEsRUFDOUM7QUFDSjtBQVNLLGVBRUE7QUFKTCxJQUFNLGtCQUFOLE1BQXNCO0FBQUEsRUFNbEIsY0FBYztBQUNWLFNBQUssYUFBYSxJQUFJLG9CQUFJLFFBQVE7QUFDbEMsU0FBSyxlQUFlLElBQUk7QUFBQSxFQUM1QjtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBUUEsSUFBSSxTQUFrQixVQUE2QztBQUMvRCxRQUFJLENBQUMsS0FBSyxhQUFhLEVBQUUsSUFBSSxPQUFPLEdBQUc7QUFBRSxXQUFLLGVBQWU7QUFBQSxJQUFLO0FBQ2xFLFNBQUssYUFBYSxFQUFFLElBQUksU0FBUyxRQUFRO0FBQ3pDLFdBQU8sQ0FBQztBQUFBLEVBQ1o7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLFFBQWM7QUFDVixRQUFJLEtBQUssZUFBZSxLQUFLO0FBQ3pCO0FBRUosZUFBVyxXQUFXLFNBQVMsS0FBSyxpQkFBaUIsR0FBRyxHQUFHO0FBQ3ZELFVBQUksS0FBSyxlQUFlLEtBQUs7QUFDekI7QUFFSixZQUFNLFdBQVcsS0FBSyxhQUFhLEVBQUUsSUFBSSxPQUFPO0FBQ2hELFVBQUksWUFBWSxNQUFNO0FBQUUsYUFBSyxlQUFlO0FBQUEsTUFBSztBQUVqRCxpQkFBVyxXQUFXLFlBQVksQ0FBQztBQUMvQixnQkFBUSxvQkFBb0IsU0FBUyxjQUFjO0FBQUEsSUFDM0Q7QUFFQSxTQUFLLGFBQWEsSUFBSSxvQkFBSSxRQUFRO0FBQ2xDLFNBQUssZUFBZSxJQUFJO0FBQUEsRUFDNUI7QUFDSjtBQUVBLElBQU0sa0JBQWtCLGtCQUFrQixJQUFJLElBQUksd0JBQXdCLElBQUksSUFBSSxnQkFBZ0I7QUFLbEcsU0FBUyxnQkFBZ0IsU0FBd0I7QUFDN0MsUUFBTSxnQkFBZ0I7QUFDdEIsUUFBTSxjQUFlLFFBQVEsYUFBYSxhQUFhLEtBQUssUUFBUSxhQUFhLGtCQUFrQixLQUFLO0FBQ3hHLFFBQU0sV0FBcUIsQ0FBQztBQUU1QixNQUFJO0FBQ0osVUFBUSxRQUFRLGNBQWMsS0FBSyxXQUFXLE9BQU87QUFDakQsYUFBUyxLQUFLLE1BQU0sQ0FBQyxDQUFDO0FBRTFCLFFBQU0sVUFBVSxnQkFBZ0IsSUFBSSxTQUFTLFFBQVE7QUFDckQsYUFBVyxXQUFXO0FBQ2xCLFlBQVEsaUJBQWlCLFNBQVMsZ0JBQWdCLE9BQU87QUFDakU7QUFLTyxTQUFTLFNBQWU7QUFDM0IsWUFBVSxNQUFNO0FBQ3BCO0FBS08sU0FBUyxTQUFlO0FBQzNCLGtCQUFnQixNQUFNO0FBQ3RCLFdBQVMsS0FBSyxpQkFBaUIsbUdBQW1HLEVBQUUsUUFBUSxlQUFlO0FBQy9KOzs7QVdoTUEsT0FBTyxRQUFRO0FBQ2YsT0FBVTtBQUVWLElBQUksTUFBTztBQUNQLFdBQVMsc0JBQXNCO0FBQ25DOzs7QUNyQkE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQVlBLElBQU1DLFFBQU8saUJBQWlCLFlBQVksTUFBTTtBQUVoRCxJQUFNLG1CQUFtQjtBQUN6QixJQUFNLG9CQUFvQjtBQUMxQixJQUFNLHFCQUFxQjtBQUUzQixJQUFNLFdBQVcsV0FBWTtBQWxCN0IsTUFBQUMsS0FBQTtBQW1CSSxNQUFJO0FBRUEsU0FBSyxNQUFBQSxNQUFBLE9BQWUsV0FBZixnQkFBQUEsSUFBdUIsWUFBdkIsbUJBQWdDLGFBQWE7QUFDOUMsYUFBUSxPQUFlLE9BQU8sUUFBUSxZQUFZLEtBQU0sT0FBZSxPQUFPLE9BQU87QUFBQSxJQUN6RixZQUVVLHdCQUFlLFdBQWYsbUJBQXVCLG9CQUF2QixtQkFBeUMsZ0JBQXpDLG1CQUFzRCxhQUFhO0FBQ3pFLGFBQVEsT0FBZSxPQUFPLGdCQUFnQixVQUFVLEVBQUUsWUFBWSxLQUFNLE9BQWUsT0FBTyxnQkFBZ0IsVUFBVSxDQUFDO0FBQUEsSUFDakksWUFFVSxZQUFlLFVBQWYsbUJBQXNCLFFBQVE7QUFDcEMsYUFBTyxDQUFDLFFBQWMsT0FBZSxNQUFNLE9BQU8sT0FBTyxRQUFRLFdBQVcsTUFBTSxLQUFLLFVBQVUsR0FBRyxDQUFDO0FBQUEsSUFDekc7QUFBQSxFQUNKLFNBQVEsR0FBRztBQUFBLEVBQUM7QUFFWixVQUFRO0FBQUEsSUFBSztBQUFBLElBQ1Q7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLEVBQXdEO0FBQzVELFNBQU87QUFDWCxHQUFHO0FBRUksU0FBUyxPQUFPLEtBQWdCO0FBQ25DLHFDQUFVO0FBQ2Q7QUFPTyxTQUFTLGFBQStCO0FBQzNDLFNBQU9ELE1BQUssZ0JBQWdCO0FBQ2hDO0FBT0EsZUFBc0IsZUFBNkM7QUFDL0QsU0FBT0EsTUFBSyxrQkFBa0I7QUFDbEM7QUErQk8sU0FBUyxjQUF3QztBQUNwRCxTQUFPQSxNQUFLLGlCQUFpQjtBQUNqQztBQU9PLFNBQVMsWUFBcUI7QUFyR3JDLE1BQUFDLEtBQUE7QUFzR0ksV0FBUSxNQUFBQSxNQUFBLE9BQWUsV0FBZixnQkFBQUEsSUFBdUIsZ0JBQXZCLG1CQUFvQyxRQUFPO0FBQ3ZEO0FBT08sU0FBUyxVQUFtQjtBQTlHbkMsTUFBQUEsS0FBQTtBQStHSSxXQUFRLE1BQUFBLE1BQUEsT0FBZSxXQUFmLGdCQUFBQSxJQUF1QixnQkFBdkIsbUJBQW9DLFFBQU87QUFDdkQ7QUFPTyxTQUFTLFFBQWlCO0FBdkhqQyxNQUFBQSxLQUFBO0FBd0hJLFdBQVEsTUFBQUEsTUFBQSxPQUFlLFdBQWYsZ0JBQUFBLElBQXVCLGdCQUF2QixtQkFBb0MsUUFBTztBQUN2RDtBQU9PLFNBQVMsVUFBbUI7QUFoSW5DLE1BQUFBLEtBQUE7QUFpSUksV0FBUSxNQUFBQSxNQUFBLE9BQWUsV0FBZixnQkFBQUEsSUFBdUIsZ0JBQXZCLG1CQUFvQyxVQUFTO0FBQ3pEO0FBT08sU0FBUyxRQUFpQjtBQXpJakMsTUFBQUEsS0FBQTtBQTBJSSxXQUFRLE1BQUFBLE1BQUEsT0FBZSxXQUFmLGdCQUFBQSxJQUF1QixnQkFBdkIsbUJBQW9DLFVBQVM7QUFDekQ7QUFPTyxTQUFTLFVBQW1CO0FBbEpuQyxNQUFBQSxLQUFBO0FBbUpJLFdBQVEsTUFBQUEsTUFBQSxPQUFlLFdBQWYsZ0JBQUFBLElBQXVCLGdCQUF2QixtQkFBb0MsVUFBUztBQUN6RDtBQU9PLFNBQVMsVUFBbUI7QUEzSm5DLE1BQUFBLEtBQUE7QUE0SkksU0FBTyxTQUFTLE1BQUFBLE1BQUEsT0FBZSxXQUFmLGdCQUFBQSxJQUF1QixnQkFBdkIsbUJBQW9DLEtBQUs7QUFDN0Q7OztBQzlJQSxPQUFPLGlCQUFpQixlQUFlLGtCQUFrQjtBQUV6RCxJQUFNQyxRQUFPLGlCQUFpQixZQUFZLFdBQVc7QUFFckQsSUFBTSxrQkFBa0I7QUFFeEIsU0FBUyxnQkFBZ0IsSUFBWSxHQUFXLEdBQVcsTUFBaUI7QUFDeEUsT0FBS0EsTUFBSyxpQkFBaUIsRUFBQyxJQUFJLEdBQUcsR0FBRyxLQUFJLENBQUM7QUFDL0M7QUFFQSxTQUFTLG1CQUFtQixPQUFtQjtBQUMzQyxRQUFNLFNBQVMsWUFBWSxLQUFLO0FBR2hDLFFBQU0sb0JBQW9CLE9BQU8saUJBQWlCLE1BQU0sRUFBRSxpQkFBaUIsc0JBQXNCLEVBQUUsS0FBSztBQUV4RyxNQUFJLG1CQUFtQjtBQUNuQixVQUFNLGVBQWU7QUFDckIsVUFBTSxPQUFPLE9BQU8saUJBQWlCLE1BQU0sRUFBRSxpQkFBaUIsMkJBQTJCO0FBQ3pGLG9CQUFnQixtQkFBbUIsTUFBTSxTQUFTLE1BQU0sU0FBUyxJQUFJO0FBQUEsRUFDekUsT0FBTztBQUNILDhCQUEwQixPQUFPLE1BQU07QUFBQSxFQUMzQztBQUNKO0FBVUEsU0FBUywwQkFBMEIsT0FBbUIsUUFBcUI7QUFFdkUsTUFBSSxRQUFRLEdBQUc7QUFDWDtBQUFBLEVBQ0o7QUFHQSxVQUFRLE9BQU8saUJBQWlCLE1BQU0sRUFBRSxpQkFBaUIsdUJBQXVCLEVBQUUsS0FBSyxHQUFHO0FBQUEsSUFDdEYsS0FBSztBQUNEO0FBQUEsSUFDSixLQUFLO0FBQ0QsWUFBTSxlQUFlO0FBQ3JCO0FBQUEsRUFDUjtBQUdBLE1BQUksT0FBTyxtQkFBbUI7QUFDMUI7QUFBQSxFQUNKO0FBR0EsUUFBTSxZQUFZLE9BQU8sYUFBYTtBQUN0QyxRQUFNLGVBQWUsYUFBYSxVQUFVLFNBQVMsRUFBRSxTQUFTO0FBQ2hFLE1BQUksY0FBYztBQUNkLGFBQVMsSUFBSSxHQUFHLElBQUksVUFBVSxZQUFZLEtBQUs7QUFDM0MsWUFBTSxRQUFRLFVBQVUsV0FBVyxDQUFDO0FBQ3BDLFlBQU0sUUFBUSxNQUFNLGVBQWU7QUFDbkMsZUFBUyxJQUFJLEdBQUcsSUFBSSxNQUFNLFFBQVEsS0FBSztBQUNuQyxjQUFNLE9BQU8sTUFBTSxDQUFDO0FBQ3BCLFlBQUksU0FBUyxpQkFBaUIsS0FBSyxNQUFNLEtBQUssR0FBRyxNQUFNLFFBQVE7QUFDM0Q7QUFBQSxRQUNKO0FBQUEsTUFDSjtBQUFBLElBQ0o7QUFBQSxFQUNKO0FBR0EsTUFBSSxrQkFBa0Isb0JBQW9CLGtCQUFrQixxQkFBcUI7QUFDN0UsUUFBSSxnQkFBaUIsQ0FBQyxPQUFPLFlBQVksQ0FBQyxPQUFPLFVBQVc7QUFDeEQ7QUFBQSxJQUNKO0FBQUEsRUFDSjtBQUdBLFFBQU0sZUFBZTtBQUN6Qjs7O0FDN0ZBO0FBQUE7QUFBQTtBQUFBO0FBZ0JPLFNBQVMsUUFBUSxLQUFrQjtBQUN0QyxNQUFJO0FBQ0EsV0FBTyxPQUFPLE9BQU8sTUFBTSxHQUFHO0FBQUEsRUFDbEMsU0FBUyxHQUFHO0FBQ1IsVUFBTSxJQUFJLE1BQU0sOEJBQThCLE1BQU0sUUFBUSxHQUFHLEVBQUUsT0FBTyxFQUFFLENBQUM7QUFBQSxFQUMvRTtBQUNKOzs7QUNQQSxJQUFJLFVBQVU7QUFDZCxJQUFJLFdBQVc7QUFFZixJQUFJLFlBQVk7QUFDaEIsSUFBSSxZQUFZO0FBQ2hCLElBQUksV0FBVztBQUNmLElBQUksYUFBcUI7QUFDekIsSUFBSSxnQkFBZ0I7QUFFcEIsSUFBSSxVQUFVO0FBQ2QsSUFBTSxpQkFBaUIsZ0JBQWdCO0FBRXZDLE9BQU8sU0FBUyxPQUFPLFVBQVUsQ0FBQztBQUNsQyxPQUFPLE9BQU8sZUFBZSxDQUFDLFVBQXlCO0FBQ25ELGNBQVk7QUFDWixNQUFJLENBQUMsV0FBVztBQUVaLGdCQUFZLFdBQVc7QUFDdkIsY0FBVTtBQUFBLEVBQ2Q7QUFDSjtBQUdBLElBQUksZUFBZTtBQUNuQixTQUFTLFdBQW9CO0FBdkM3QixNQUFBQyxLQUFBO0FBd0NJLFFBQU0sTUFBTSxNQUFBQSxNQUFBLE9BQWUsV0FBZixnQkFBQUEsSUFBdUIsZ0JBQXZCLG1CQUFvQztBQUNoRCxNQUFJLE9BQU8sU0FBUyxPQUFPLFVBQVcsUUFBTztBQUU3QyxRQUFNLEtBQUssVUFBVSxhQUFhLFVBQVUsVUFBVyxPQUFlLFNBQVM7QUFDL0UsU0FBTywrQ0FBK0MsS0FBSyxFQUFFO0FBQ2pFO0FBQ0EsU0FBUyxzQkFBNEI7QUFDakMsTUFBSSxhQUFjO0FBQ2xCLE1BQUksU0FBUyxFQUFHO0FBQ2hCLFNBQU8saUJBQWlCLGFBQWEsUUFBUSxFQUFFLFNBQVMsS0FBSyxDQUFDO0FBQzlELFNBQU8saUJBQWlCLGFBQWEsUUFBUSxFQUFFLFNBQVMsS0FBSyxDQUFDO0FBQzlELFNBQU8saUJBQWlCLFdBQVcsUUFBUSxFQUFFLFNBQVMsS0FBSyxDQUFDO0FBQzVELGFBQVcsTUFBTSxDQUFDLFNBQVMsZUFBZSxVQUFVLEdBQUc7QUFDbkQsV0FBTyxpQkFBaUIsSUFBSSxlQUFlLEVBQUUsU0FBUyxLQUFLLENBQUM7QUFBQSxFQUNoRTtBQUNBLGlCQUFlO0FBQ25CO0FBRUEsb0JBQW9CO0FBRXBCLFNBQVMsaUJBQWlCLG9CQUFvQixxQkFBcUIsRUFBRSxNQUFNLEtBQUssQ0FBQztBQUVqRixJQUFJLGVBQWU7QUFDbkIsSUFBTSxjQUFjLE9BQU8sWUFBWSxNQUFNO0FBQ3pDLE1BQUksY0FBYztBQUFFLFdBQU8sY0FBYyxXQUFXO0FBQUc7QUFBQSxFQUFRO0FBQy9ELHNCQUFvQjtBQUNwQixNQUFJLEVBQUUsZUFBZSxLQUFLO0FBQUUsV0FBTyxjQUFjLFdBQVc7QUFBQSxFQUFHO0FBQ25FLEdBQUcsRUFBRTtBQUVMLFNBQVMsY0FBYyxPQUFjO0FBRWpDLE1BQUksWUFBWSxVQUFVO0FBQ3RCLFVBQU0seUJBQXlCO0FBQy9CLFVBQU0sZ0JBQWdCO0FBQ3RCLFVBQU0sZUFBZTtBQUFBLEVBQ3pCO0FBQ0o7QUFHQSxJQUFNLFlBQVk7QUFDbEIsSUFBTSxVQUFZO0FBQ2xCLElBQU0sWUFBWTtBQUVsQixTQUFTLE9BQU8sT0FBbUI7QUFJL0IsTUFBSSxXQUFtQixlQUFlLE1BQU07QUFDNUMsVUFBUSxNQUFNLE1BQU07QUFBQSxJQUNoQixLQUFLO0FBQ0Qsa0JBQVk7QUFDWixVQUFJLENBQUMsZ0JBQWdCO0FBQUUsdUJBQWUsVUFBVyxLQUFLLE1BQU07QUFBQSxNQUFTO0FBQ3JFO0FBQUEsSUFDSixLQUFLO0FBQ0Qsa0JBQVk7QUFDWixVQUFJLENBQUMsZ0JBQWdCO0FBQUUsdUJBQWUsVUFBVSxFQUFFLEtBQUssTUFBTTtBQUFBLE1BQVM7QUFDdEU7QUFBQSxJQUNKO0FBQ0ksa0JBQVk7QUFDWixVQUFJLENBQUMsZ0JBQWdCO0FBQUUsdUJBQWU7QUFBQSxNQUFTO0FBQy9DO0FBQUEsRUFDUjtBQUVBLE1BQUksV0FBVyxVQUFVLENBQUM7QUFDMUIsTUFBSSxVQUFVLGVBQWUsQ0FBQztBQUU5QixZQUFVO0FBR1YsTUFBSSxjQUFjLGFBQWEsRUFBRSxVQUFVLE1BQU0sU0FBUztBQUN0RCxnQkFBYSxLQUFLLE1BQU07QUFDeEIsZUFBWSxLQUFLLE1BQU07QUFBQSxFQUMzQjtBQUlBLE1BQ0ksY0FBYyxhQUNYLFlBRUMsYUFFSSxjQUFjLGFBQ1gsTUFBTSxXQUFXLElBRzlCO0FBQ0UsVUFBTSx5QkFBeUI7QUFDL0IsVUFBTSxnQkFBZ0I7QUFDdEIsVUFBTSxlQUFlO0FBQUEsRUFDekI7QUFHQSxNQUFJLFdBQVcsR0FBRztBQUFFLGNBQVUsS0FBSztBQUFBLEVBQUc7QUFFdEMsTUFBSSxVQUFVLEdBQUc7QUFBRSxnQkFBWSxLQUFLO0FBQUEsRUFBRztBQUd2QyxNQUFJLGNBQWMsV0FBVztBQUFFLGdCQUFZLEtBQUs7QUFBQSxFQUFHO0FBQUM7QUFDeEQ7QUFFQSxTQUFTLFlBQVksT0FBeUI7QUFFMUMsWUFBVTtBQUNWLGNBQVk7QUFHWixNQUFJLENBQUMsVUFBVSxHQUFHO0FBQ2QsUUFBSSxNQUFNLFNBQVMsZUFBZSxNQUFNLFdBQVcsS0FBSyxNQUFNLFdBQVcsR0FBRztBQUN4RTtBQUFBLElBQ0o7QUFBQSxFQUNKO0FBRUEsTUFBSSxZQUFZO0FBRVosZ0JBQVk7QUFFWjtBQUFBLEVBQ0o7QUFHQSxRQUFNLFNBQVMsWUFBWSxLQUFLO0FBSWhDLFFBQU0sUUFBUSxPQUFPLGlCQUFpQixNQUFNO0FBQzVDLFlBQ0ksTUFBTSxpQkFBaUIsbUJBQW1CLEVBQUUsS0FBSyxNQUFNLFdBRW5ELE1BQU0sVUFBVSxXQUFXLE1BQU0sV0FBVyxJQUFJLE9BQU8sZUFDcEQsTUFBTSxVQUFVLFdBQVcsTUFBTSxVQUFVLElBQUksT0FBTztBQUdyRTtBQUVBLFNBQVMsVUFBVSxPQUFtQjtBQUVsQyxZQUFVO0FBQ1YsYUFBVztBQUNYLGNBQVk7QUFDWixhQUFXO0FBQ2Y7QUFFQSxJQUFNLGdCQUFnQixPQUFPLE9BQU87QUFBQSxFQUNoQyxhQUFhO0FBQUEsRUFDYixhQUFhO0FBQUEsRUFDYixhQUFhO0FBQUEsRUFDYixhQUFhO0FBQUEsRUFDYixZQUFZO0FBQUEsRUFDWixZQUFZO0FBQUEsRUFDWixZQUFZO0FBQUEsRUFDWixZQUFZO0FBQ2hCLENBQUM7QUFFRCxTQUFTLFVBQVUsTUFBeUM7QUFDeEQsTUFBSSxNQUFNO0FBQ04sUUFBSSxDQUFDLFlBQVk7QUFBRSxzQkFBZ0IsU0FBUyxLQUFLLE1BQU07QUFBQSxJQUFRO0FBQy9ELGFBQVMsS0FBSyxNQUFNLFNBQVMsY0FBYyxJQUFJO0FBQUEsRUFDbkQsV0FBVyxDQUFDLFFBQVEsWUFBWTtBQUM1QixhQUFTLEtBQUssTUFBTSxTQUFTO0FBQUEsRUFDakM7QUFFQSxlQUFhLFFBQVE7QUFDekI7QUFFQSxTQUFTLFlBQVksT0FBeUI7QUFDMUMsTUFBSSxhQUFhLFlBQVk7QUFFekIsZUFBVztBQUNYLFdBQU8sa0JBQWtCLFVBQVU7QUFBQSxFQUN2QyxXQUFXLFNBQVM7QUFFaEIsZUFBVztBQUNYLFdBQU8sWUFBWTtBQUFBLEVBQ3ZCO0FBRUEsTUFBSSxZQUFZLFVBQVU7QUFHdEIsY0FBVSxZQUFZO0FBQ3RCO0FBQUEsRUFDSjtBQUVBLE1BQUksQ0FBQyxhQUFhLENBQUMsVUFBVSxHQUFHO0FBQzVCLFFBQUksWUFBWTtBQUFFLGdCQUFVO0FBQUEsSUFBRztBQUMvQjtBQUFBLEVBQ0o7QUFFQSxRQUFNLHFCQUFxQixRQUFRLDJCQUEyQixLQUFLO0FBQ25FLFFBQU0sb0JBQW9CLFFBQVEsMEJBQTBCLEtBQUs7QUFHakUsUUFBTSxjQUFjLFFBQVEsbUJBQW1CLEtBQUs7QUFFcEQsUUFBTSxjQUFlLE9BQU8sYUFBYSxNQUFNLFVBQVc7QUFDMUQsUUFBTSxhQUFhLE1BQU0sVUFBVTtBQUNuQyxRQUFNLFlBQVksTUFBTSxVQUFVO0FBQ2xDLFFBQU0sZUFBZ0IsT0FBTyxjQUFjLE1BQU0sVUFBVztBQUc1RCxRQUFNLGNBQWUsT0FBTyxhQUFhLE1BQU0sVUFBWSxvQkFBb0I7QUFDL0UsUUFBTSxhQUFhLE1BQU0sVUFBVyxvQkFBb0I7QUFDeEQsUUFBTSxZQUFZLE1BQU0sVUFBVyxxQkFBcUI7QUFDeEQsUUFBTSxlQUFnQixPQUFPLGNBQWMsTUFBTSxVQUFZLHFCQUFxQjtBQUVsRixNQUFJLENBQUMsY0FBYyxDQUFDLGFBQWEsQ0FBQyxnQkFBZ0IsQ0FBQyxhQUFhO0FBRTVELGNBQVU7QUFBQSxFQUNkLFdBRVMsZUFBZSxhQUFjLFdBQVUsV0FBVztBQUFBLFdBQ2xELGNBQWMsYUFBYyxXQUFVLFdBQVc7QUFBQSxXQUNqRCxjQUFjLFVBQVcsV0FBVSxXQUFXO0FBQUEsV0FDOUMsYUFBYSxZQUFhLFdBQVUsV0FBVztBQUFBLFdBRS9DLFdBQVksV0FBVSxVQUFVO0FBQUEsV0FDaEMsVUFBVyxXQUFVLFVBQVU7QUFBQSxXQUMvQixhQUFjLFdBQVUsVUFBVTtBQUFBLFdBQ2xDLFlBQWEsV0FBVSxVQUFVO0FBQUEsTUFFckMsV0FBVTtBQUNuQjs7O0FDclFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQVdBLElBQU1DLFFBQU8saUJBQWlCLFlBQVksV0FBVztBQUVyRCxJQUFNQyxjQUFhO0FBQ25CLElBQU1DLGNBQWE7QUFDbkIsSUFBTSxhQUFhO0FBS1osU0FBUyxPQUFzQjtBQUNsQyxTQUFPRixNQUFLQyxXQUFVO0FBQzFCO0FBS08sU0FBUyxPQUFzQjtBQUNsQyxTQUFPRCxNQUFLRSxXQUFVO0FBQzFCO0FBS08sU0FBUyxPQUFzQjtBQUNsQyxTQUFPRixNQUFLLFVBQVU7QUFDMUI7OztBQ3BDQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTs7O0FDd0JBLElBQUksVUFBVSxTQUFTLFVBQVU7QUFDakMsSUFBSSxlQUFvRCxPQUFPLFlBQVksWUFBWSxZQUFZLFFBQVEsUUFBUTtBQUNuSCxJQUFJO0FBQ0osSUFBSTtBQUNKLElBQUksT0FBTyxpQkFBaUIsY0FBYyxPQUFPLE9BQU8sbUJBQW1CLFlBQVk7QUFDbkYsTUFBSTtBQUNBLG1CQUFlLE9BQU8sZUFBZSxDQUFDLEdBQUcsVUFBVTtBQUFBLE1BQy9DLEtBQUssV0FBWTtBQUNiLGNBQU07QUFBQSxNQUNWO0FBQUEsSUFDSixDQUFDO0FBQ0QsdUJBQW1CLENBQUM7QUFFcEIsaUJBQWEsV0FBWTtBQUFFLFlBQU07QUFBQSxJQUFJLEdBQUcsTUFBTSxZQUFZO0FBQUEsRUFDOUQsU0FBUyxHQUFHO0FBQ1IsUUFBSSxNQUFNLGtCQUFrQjtBQUN4QixxQkFBZTtBQUFBLElBQ25CO0FBQUEsRUFDSjtBQUNKLE9BQU87QUFDSCxpQkFBZTtBQUNuQjtBQUVBLElBQUksbUJBQW1CO0FBQ3ZCLElBQUksZUFBZSxTQUFTLG1CQUFtQixPQUFxQjtBQUNoRSxNQUFJO0FBQ0EsUUFBSSxRQUFRLFFBQVEsS0FBSyxLQUFLO0FBQzlCLFdBQU8saUJBQWlCLEtBQUssS0FBSztBQUFBLEVBQ3RDLFNBQVMsR0FBRztBQUNSLFdBQU87QUFBQSxFQUNYO0FBQ0o7QUFFQSxJQUFJLG9CQUFvQixTQUFTLGlCQUFpQixPQUFxQjtBQUNuRSxNQUFJO0FBQ0EsUUFBSSxhQUFhLEtBQUssR0FBRztBQUFFLGFBQU87QUFBQSxJQUFPO0FBQ3pDLFlBQVEsS0FBSyxLQUFLO0FBQ2xCLFdBQU87QUFBQSxFQUNYLFNBQVMsR0FBRztBQUNSLFdBQU87QUFBQSxFQUNYO0FBQ0o7QUFDQSxJQUFJLFFBQVEsT0FBTyxVQUFVO0FBQzdCLElBQUksY0FBYztBQUNsQixJQUFJLFVBQVU7QUFDZCxJQUFJLFdBQVc7QUFDZixJQUFJLFdBQVc7QUFDZixJQUFJLFlBQVk7QUFDaEIsSUFBSSxZQUFZO0FBQ2hCLElBQUksaUJBQWlCLE9BQU8sV0FBVyxjQUFjLENBQUMsQ0FBQyxPQUFPO0FBRTlELElBQUksU0FBUyxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBRXRCLElBQUksUUFBaUMsU0FBUyxtQkFBbUI7QUFBRSxTQUFPO0FBQU87QUFDakYsSUFBSSxPQUFPLGFBQWEsVUFBVTtBQUUxQixRQUFNLFNBQVM7QUFDbkIsTUFBSSxNQUFNLEtBQUssR0FBRyxNQUFNLE1BQU0sS0FBSyxTQUFTLEdBQUcsR0FBRztBQUM5QyxZQUFRLFNBQVNHLGtCQUFpQixPQUFPO0FBR3JDLFdBQUssVUFBVSxDQUFDLFdBQVcsT0FBTyxVQUFVLGVBQWUsT0FBTyxVQUFVLFdBQVc7QUFDbkYsWUFBSTtBQUNBLGNBQUksTUFBTSxNQUFNLEtBQUssS0FBSztBQUMxQixrQkFDSSxRQUFRLFlBQ0wsUUFBUSxhQUNSLFFBQVEsYUFDUixRQUFRLGdCQUNWLE1BQU0sRUFBRSxLQUFLO0FBQUEsUUFDdEIsU0FBUyxHQUFHO0FBQUEsUUFBTztBQUFBLE1BQ3ZCO0FBQ0EsYUFBTztBQUFBLElBQ1g7QUFBQSxFQUNKO0FBQ0o7QUFuQlE7QUFxQlIsU0FBUyxtQkFBc0IsT0FBdUQ7QUFDbEYsTUFBSSxNQUFNLEtBQUssR0FBRztBQUFFLFdBQU87QUFBQSxFQUFNO0FBQ2pDLE1BQUksQ0FBQyxPQUFPO0FBQUUsV0FBTztBQUFBLEVBQU87QUFDNUIsTUFBSSxPQUFPLFVBQVUsY0FBYyxPQUFPLFVBQVUsVUFBVTtBQUFFLFdBQU87QUFBQSxFQUFPO0FBQzlFLE1BQUk7QUFDQSxJQUFDLGFBQXFCLE9BQU8sTUFBTSxZQUFZO0FBQUEsRUFDbkQsU0FBUyxHQUFHO0FBQ1IsUUFBSSxNQUFNLGtCQUFrQjtBQUFFLGFBQU87QUFBQSxJQUFPO0FBQUEsRUFDaEQ7QUFDQSxTQUFPLENBQUMsYUFBYSxLQUFLLEtBQUssa0JBQWtCLEtBQUs7QUFDMUQ7QUFFQSxTQUFTLHFCQUF3QixPQUFzRDtBQUNuRixNQUFJLE1BQU0sS0FBSyxHQUFHO0FBQUUsV0FBTztBQUFBLEVBQU07QUFDakMsTUFBSSxDQUFDLE9BQU87QUFBRSxXQUFPO0FBQUEsRUFBTztBQUM1QixNQUFJLE9BQU8sVUFBVSxjQUFjLE9BQU8sVUFBVSxVQUFVO0FBQUUsV0FBTztBQUFBLEVBQU87QUFDOUUsTUFBSSxnQkFBZ0I7QUFBRSxXQUFPLGtCQUFrQixLQUFLO0FBQUEsRUFBRztBQUN2RCxNQUFJLGFBQWEsS0FBSyxHQUFHO0FBQUUsV0FBTztBQUFBLEVBQU87QUFDekMsTUFBSSxXQUFXLE1BQU0sS0FBSyxLQUFLO0FBQy9CLE1BQUksYUFBYSxXQUFXLGFBQWEsWUFBWSxDQUFFLGlCQUFrQixLQUFLLFFBQVEsR0FBRztBQUFFLFdBQU87QUFBQSxFQUFPO0FBQ3pHLFNBQU8sa0JBQWtCLEtBQUs7QUFDbEM7QUFFQSxJQUFPLG1CQUFRLGVBQWUscUJBQXFCOzs7QUN6RzVDLElBQU0sY0FBTixjQUEwQixNQUFNO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBTW5DLFlBQVksU0FBa0IsU0FBd0I7QUFDbEQsVUFBTSxTQUFTLE9BQU87QUFDdEIsU0FBSyxPQUFPO0FBQUEsRUFDaEI7QUFDSjtBQWNPLElBQU0sMEJBQU4sY0FBc0MsTUFBTTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFhL0MsWUFBWSxTQUFzQyxRQUFjLE1BQWU7QUFDM0UsV0FBTyxzQkFBUSwrQ0FBK0MsY0FBYyxhQUFhLE1BQU0sR0FBRyxFQUFFLE9BQU8sT0FBTyxDQUFDO0FBQ25ILFNBQUssVUFBVTtBQUNmLFNBQUssT0FBTztBQUFBLEVBQ2hCO0FBQ0o7QUErQkEsSUFBTSxhQUFhLHVCQUFPLFNBQVM7QUFDbkMsSUFBTSxnQkFBZ0IsdUJBQU8sWUFBWTtBQTdGekM7QUE4RkEsSUFBTSxXQUFpQyxZQUFPLFlBQVAsWUFBa0IsdUJBQU8saUJBQWlCO0FBb0QxRSxJQUFNLHFCQUFOLE1BQU0sNEJBQThCLFFBQWdFO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBdUN2RyxZQUFZLFVBQXlDLGFBQTJDO0FBQzVGLFFBQUk7QUFDSixRQUFJO0FBQ0osVUFBTSxDQUFDLEtBQUssUUFBUTtBQUFFLGdCQUFVO0FBQUssZUFBUztBQUFBLElBQUssQ0FBQztBQUVwRCxRQUFLLEtBQUssWUFBb0IsT0FBTyxNQUFNLFNBQVM7QUFDaEQsWUFBTSxJQUFJLFVBQVUsbUlBQW1JO0FBQUEsSUFDM0o7QUFFQSxRQUFJLFVBQThDO0FBQUEsTUFDOUMsU0FBUztBQUFBLE1BQ1Q7QUFBQSxNQUNBO0FBQUEsTUFDQSxJQUFJLGNBQWM7QUFBRSxlQUFPLG9DQUFlO0FBQUEsTUFBTTtBQUFBLE1BQ2hELElBQUksWUFBWSxJQUFJO0FBQUUsc0JBQWMsa0JBQU07QUFBQSxNQUFXO0FBQUEsSUFDekQ7QUFFQSxVQUFNLFFBQWlDO0FBQUEsTUFDbkMsSUFBSSxPQUFPO0FBQUUsZUFBTztBQUFBLE1BQU87QUFBQSxNQUMzQixXQUFXO0FBQUEsTUFDWCxTQUFTO0FBQUEsSUFDYjtBQUdBLFNBQUssT0FBTyxpQkFBaUIsTUFBTTtBQUFBLE1BQy9CLENBQUMsVUFBVSxHQUFHO0FBQUEsUUFDVixjQUFjO0FBQUEsUUFDZCxZQUFZO0FBQUEsUUFDWixVQUFVO0FBQUEsUUFDVixPQUFPO0FBQUEsTUFDWDtBQUFBLE1BQ0EsQ0FBQyxhQUFhLEdBQUc7QUFBQSxRQUNiLGNBQWM7QUFBQSxRQUNkLFlBQVk7QUFBQSxRQUNaLFVBQVU7QUFBQSxRQUNWLE9BQU8sYUFBYSxTQUFTLEtBQUs7QUFBQSxNQUN0QztBQUFBLElBQ0osQ0FBQztBQUdELFVBQU0sV0FBVyxZQUFZLFNBQVMsS0FBSztBQUMzQyxRQUFJO0FBQ0EsZUFBUyxZQUFZLFNBQVMsS0FBSyxHQUFHLFFBQVE7QUFBQSxJQUNsRCxTQUFTLEtBQUs7QUFDVixVQUFJLE1BQU0sV0FBVztBQUNqQixnQkFBUSxJQUFJLHVEQUF1RCxHQUFHO0FBQUEsTUFDMUUsT0FBTztBQUNILGlCQUFTLEdBQUc7QUFBQSxNQUNoQjtBQUFBLElBQ0o7QUFBQSxFQUNKO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQXlEQSxPQUFPLE9BQXVDO0FBQzFDLFdBQU8sSUFBSSxvQkFBeUIsQ0FBQyxZQUFZO0FBRzdDLGNBQVEsSUFBSTtBQUFBLFFBQ1IsS0FBSyxhQUFhLEVBQUUsSUFBSSxZQUFZLHNCQUFzQixFQUFFLE1BQU0sQ0FBQyxDQUFDO0FBQUEsUUFDcEUsZUFBZSxJQUFJO0FBQUEsTUFDdkIsQ0FBQyxFQUFFLEtBQUssTUFBTSxRQUFRLEdBQUcsTUFBTSxRQUFRLENBQUM7QUFBQSxJQUM1QyxDQUFDO0FBQUEsRUFDTDtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUEyQkEsU0FBUyxRQUE0QztBQUNqRCxRQUFJLE9BQU8sU0FBUztBQUNoQixXQUFLLEtBQUssT0FBTyxPQUFPLE1BQU07QUFBQSxJQUNsQyxPQUFPO0FBQ0gsYUFBTyxpQkFBaUIsU0FBUyxNQUFNLEtBQUssS0FBSyxPQUFPLE9BQU8sTUFBTSxHQUFHLEVBQUMsU0FBUyxLQUFJLENBQUM7QUFBQSxJQUMzRjtBQUVBLFdBQU87QUFBQSxFQUNYO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBK0JBLEtBQXFDLGFBQXNILFlBQXdILGFBQW9GO0FBQ25XLFFBQUksRUFBRSxnQkFBZ0Isc0JBQXFCO0FBQ3ZDLFlBQU0sSUFBSSxVQUFVLGdFQUFnRTtBQUFBLElBQ3hGO0FBTUEsUUFBSSxDQUFDLGlCQUFXLFdBQVcsR0FBRztBQUFFLG9CQUFjO0FBQUEsSUFBaUI7QUFDL0QsUUFBSSxDQUFDLGlCQUFXLFVBQVUsR0FBRztBQUFFLG1CQUFhO0FBQUEsSUFBUztBQUVyRCxRQUFJLGdCQUFnQixZQUFZLGNBQWMsU0FBUztBQUVuRCxhQUFPLElBQUksb0JBQW1CLENBQUMsWUFBWSxRQUFRLElBQVcsQ0FBQztBQUFBLElBQ25FO0FBRUEsVUFBTSxVQUErQyxDQUFDO0FBQ3RELFNBQUssVUFBVSxJQUFJO0FBRW5CLFdBQU8sSUFBSSxvQkFBd0MsQ0FBQyxTQUFTLFdBQVc7QUFDcEUsV0FBSyxNQUFNO0FBQUEsUUFDUCxDQUFDLFVBQVU7QUFyWTNCLGNBQUFDO0FBc1lvQixjQUFJLEtBQUssVUFBVSxNQUFNLFNBQVM7QUFBRSxpQkFBSyxVQUFVLElBQUk7QUFBQSxVQUFNO0FBQzdELFdBQUFBLE1BQUEsUUFBUSxZQUFSLGdCQUFBQSxJQUFBO0FBRUEsY0FBSTtBQUNBLG9CQUFRLFlBQWEsS0FBSyxDQUFDO0FBQUEsVUFDL0IsU0FBUyxLQUFLO0FBQ1YsbUJBQU8sR0FBRztBQUFBLFVBQ2Q7QUFBQSxRQUNKO0FBQUEsUUFDQSxDQUFDLFdBQVk7QUEvWTdCLGNBQUFBO0FBZ1pvQixjQUFJLEtBQUssVUFBVSxNQUFNLFNBQVM7QUFBRSxpQkFBSyxVQUFVLElBQUk7QUFBQSxVQUFNO0FBQzdELFdBQUFBLE1BQUEsUUFBUSxZQUFSLGdCQUFBQSxJQUFBO0FBRUEsY0FBSTtBQUNBLG9CQUFRLFdBQVksTUFBTSxDQUFDO0FBQUEsVUFDL0IsU0FBUyxLQUFLO0FBQ1YsbUJBQU8sR0FBRztBQUFBLFVBQ2Q7QUFBQSxRQUNKO0FBQUEsTUFDSjtBQUFBLElBQ0osR0FBRyxPQUFPLFVBQVc7QUFFakIsVUFBSTtBQUNBLGVBQU8sMkNBQWM7QUFBQSxNQUN6QixVQUFFO0FBQ0UsY0FBTSxLQUFLLE9BQU8sS0FBSztBQUFBLE1BQzNCO0FBQUEsSUFDSixDQUFDO0FBQUEsRUFDTDtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQStCQSxNQUF1QixZQUFxRixhQUE0RTtBQUNwTCxXQUFPLEtBQUssS0FBSyxRQUFXLFlBQVksV0FBVztBQUFBLEVBQ3ZEO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQWlDQSxRQUFRLFdBQTZDLGFBQWtFO0FBQ25ILFFBQUksRUFBRSxnQkFBZ0Isc0JBQXFCO0FBQ3ZDLFlBQU0sSUFBSSxVQUFVLG1FQUFtRTtBQUFBLElBQzNGO0FBRUEsUUFBSSxDQUFDLGlCQUFXLFNBQVMsR0FBRztBQUN4QixhQUFPLEtBQUssS0FBSyxXQUFXLFdBQVcsV0FBVztBQUFBLElBQ3REO0FBRUEsV0FBTyxLQUFLO0FBQUEsTUFDUixDQUFDLFVBQVUsb0JBQW1CLFFBQVEsVUFBVSxDQUFDLEVBQUUsS0FBSyxNQUFNLEtBQUs7QUFBQSxNQUNuRSxDQUFDLFdBQVksb0JBQW1CLFFBQVEsVUFBVSxDQUFDLEVBQUUsS0FBSyxNQUFNO0FBQUUsY0FBTTtBQUFBLE1BQVEsQ0FBQztBQUFBLE1BQ2pGO0FBQUEsSUFDSjtBQUFBLEVBQ0o7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBWUEsYUF6V1MsWUFFUyxlQXVXTixRQUFPLElBQUk7QUFDbkIsV0FBTztBQUFBLEVBQ1g7QUFBQSxFQWFBLE9BQU8sSUFBc0QsUUFBd0M7QUFDakcsUUFBSSxZQUFZLE1BQU0sS0FBSyxNQUFNO0FBQ2pDLFVBQU0sVUFBVSxVQUFVLFdBQVcsSUFDL0Isb0JBQW1CLFFBQVEsU0FBUyxJQUNwQyxJQUFJLG9CQUE0QixDQUFDLFNBQVMsV0FBVztBQUNuRCxXQUFLLFFBQVEsSUFBSSxTQUFTLEVBQUUsS0FBSyxTQUFTLE1BQU07QUFBQSxJQUNwRCxHQUFHLENBQUMsVUFBMEIsVUFBVSxTQUFTLFdBQVcsS0FBSyxDQUFDO0FBQ3RFLFdBQU87QUFBQSxFQUNYO0FBQUEsRUFhQSxPQUFPLFdBQTZELFFBQXdDO0FBQ3hHLFFBQUksWUFBWSxNQUFNLEtBQUssTUFBTTtBQUNqQyxVQUFNLFVBQVUsVUFBVSxXQUFXLElBQy9CLG9CQUFtQixRQUFRLFNBQVMsSUFDcEMsSUFBSSxvQkFBNEIsQ0FBQyxTQUFTLFdBQVc7QUFDbkQsV0FBSyxRQUFRLFdBQVcsU0FBUyxFQUFFLEtBQUssU0FBUyxNQUFNO0FBQUEsSUFDM0QsR0FBRyxDQUFDLFVBQTBCLFVBQVUsU0FBUyxXQUFXLEtBQUssQ0FBQztBQUN0RSxXQUFPO0FBQUEsRUFDWDtBQUFBLEVBZUEsT0FBTyxJQUFzRCxRQUF3QztBQUNqRyxRQUFJLFlBQVksTUFBTSxLQUFLLE1BQU07QUFDakMsVUFBTSxVQUFVLFVBQVUsV0FBVyxJQUMvQixvQkFBbUIsUUFBUSxTQUFTLElBQ3BDLElBQUksb0JBQTRCLENBQUMsU0FBUyxXQUFXO0FBQ25ELFdBQUssUUFBUSxJQUFJLFNBQVMsRUFBRSxLQUFLLFNBQVMsTUFBTTtBQUFBLElBQ3BELEdBQUcsQ0FBQyxVQUEwQixVQUFVLFNBQVMsV0FBVyxLQUFLLENBQUM7QUFDdEUsV0FBTztBQUFBLEVBQ1g7QUFBQSxFQVlBLE9BQU8sS0FBdUQsUUFBd0M7QUFDbEcsUUFBSSxZQUFZLE1BQU0sS0FBSyxNQUFNO0FBQ2pDLFVBQU0sVUFBVSxJQUFJLG9CQUE0QixDQUFDLFNBQVMsV0FBVztBQUNqRSxXQUFLLFFBQVEsS0FBSyxTQUFTLEVBQUUsS0FBSyxTQUFTLE1BQU07QUFBQSxJQUNyRCxHQUFHLENBQUMsVUFBMEIsVUFBVSxTQUFTLFdBQVcsS0FBSyxDQUFDO0FBQ2xFLFdBQU87QUFBQSxFQUNYO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsT0FBTyxPQUFrQixPQUFvQztBQUN6RCxVQUFNLElBQUksSUFBSSxvQkFBc0IsTUFBTTtBQUFBLElBQUMsQ0FBQztBQUM1QyxNQUFFLE9BQU8sS0FBSztBQUNkLFdBQU87QUFBQSxFQUNYO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQVlBLE9BQU8sUUFBbUIsY0FBc0IsT0FBb0M7QUFDaEYsVUFBTSxVQUFVLElBQUksb0JBQXNCLE1BQU07QUFBQSxJQUFDLENBQUM7QUFDbEQsUUFBSSxlQUFlLE9BQU8sZ0JBQWdCLGNBQWMsWUFBWSxXQUFXLE9BQU8sWUFBWSxZQUFZLFlBQVk7QUFDdEgsa0JBQVksUUFBUSxZQUFZLEVBQUUsaUJBQWlCLFNBQVMsTUFBTSxLQUFLLFFBQVEsT0FBTyxLQUFLLENBQUM7QUFBQSxJQUNoRyxPQUFPO0FBQ0gsaUJBQVcsTUFBTSxLQUFLLFFBQVEsT0FBTyxLQUFLLEdBQUcsWUFBWTtBQUFBLElBQzdEO0FBQ0EsV0FBTztBQUFBLEVBQ1g7QUFBQSxFQWlCQSxPQUFPLE1BQWdCLGNBQXNCLE9BQWtDO0FBQzNFLFdBQU8sSUFBSSxvQkFBc0IsQ0FBQyxZQUFZO0FBQzFDLGlCQUFXLE1BQU0sUUFBUSxLQUFNLEdBQUcsWUFBWTtBQUFBLElBQ2xELENBQUM7QUFBQSxFQUNMO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsT0FBTyxPQUFrQixRQUFxQztBQUMxRCxXQUFPLElBQUksb0JBQXNCLENBQUMsR0FBRyxXQUFXLE9BQU8sTUFBTSxDQUFDO0FBQUEsRUFDbEU7QUFBQSxFQW9CQSxPQUFPLFFBQWtCLE9BQTREO0FBQ2pGLFFBQUksaUJBQWlCLHFCQUFvQjtBQUVyQyxhQUFPO0FBQUEsSUFDWDtBQUNBLFdBQU8sSUFBSSxvQkFBd0IsQ0FBQyxZQUFZLFFBQVEsS0FBSyxDQUFDO0FBQUEsRUFDbEU7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFVQSxPQUFPLGdCQUF1RDtBQUMxRCxRQUFJLFNBQTZDLEVBQUUsYUFBYSxLQUFLO0FBQ3JFLFdBQU8sVUFBVSxJQUFJLG9CQUFzQixDQUFDLFNBQVMsV0FBVztBQUM1RCxhQUFPLFVBQVU7QUFDakIsYUFBTyxTQUFTO0FBQUEsSUFDcEIsR0FBRyxDQUFDLFVBQWdCO0FBenJCNUIsVUFBQUE7QUF5ckI4QixPQUFBQSxNQUFBLE9BQU8sZ0JBQVAsZ0JBQUFBLElBQUEsYUFBcUI7QUFBQSxJQUFRLENBQUM7QUFDcEQsV0FBTztBQUFBLEVBQ1g7QUFDSjtBQU1BLFNBQVMsYUFBZ0IsU0FBNkMsT0FBZ0M7QUFDbEcsTUFBSSxzQkFBZ0Q7QUFFcEQsU0FBTyxDQUFDLFdBQWtEO0FBQ3RELFFBQUksQ0FBQyxNQUFNLFNBQVM7QUFDaEIsWUFBTSxVQUFVO0FBQ2hCLFlBQU0sU0FBUztBQUNmLGNBQVEsT0FBTyxNQUFNO0FBTXJCLFdBQUssUUFBUSxVQUFVLEtBQUssS0FBSyxRQUFRLFNBQVMsUUFBVyxDQUFDLFFBQVE7QUFDbEUsWUFBSSxRQUFRLFFBQVE7QUFDaEIsZ0JBQU07QUFBQSxRQUNWO0FBQUEsTUFDSixDQUFDO0FBQUEsSUFDTDtBQUlBLFFBQUksQ0FBQyxNQUFNLFVBQVUsQ0FBQyxRQUFRLGFBQWE7QUFBRTtBQUFBLElBQVE7QUFFckQsMEJBQXNCLElBQUksUUFBYyxDQUFDLFlBQVk7QUFDakQsVUFBSTtBQUNBLGdCQUFRLFFBQVEsWUFBYSxNQUFNLE9BQVEsS0FBSyxDQUFDO0FBQUEsTUFDckQsU0FBUyxLQUFLO0FBQ1YsZ0JBQVEsT0FBTyxJQUFJLHdCQUF3QixRQUFRLFNBQVMsS0FBSyw4Q0FBOEMsQ0FBQztBQUFBLE1BQ3BIO0FBQUEsSUFDSixDQUFDLEVBQUUsTUFBTSxDQUFDQyxZQUFZO0FBQ2xCLGNBQVEsT0FBTyxJQUFJLHdCQUF3QixRQUFRLFNBQVNBLFNBQVEsOENBQThDLENBQUM7QUFBQSxJQUN2SCxDQUFDO0FBR0QsWUFBUSxjQUFjO0FBRXRCLFdBQU87QUFBQSxFQUNYO0FBQ0o7QUFLQSxTQUFTLFlBQWUsU0FBNkMsT0FBK0Q7QUFDaEksU0FBTyxDQUFDLFVBQVU7QUFDZCxRQUFJLE1BQU0sV0FBVztBQUFFO0FBQUEsSUFBUTtBQUMvQixVQUFNLFlBQVk7QUFFbEIsUUFBSSxVQUFVLFFBQVEsU0FBUztBQUMzQixVQUFJLE1BQU0sU0FBUztBQUFFO0FBQUEsTUFBUTtBQUM3QixZQUFNLFVBQVU7QUFDaEIsY0FBUSxPQUFPLElBQUksVUFBVSwyQ0FBMkMsQ0FBQztBQUN6RTtBQUFBLElBQ0o7QUFFQSxRQUFJLFNBQVMsU0FBUyxPQUFPLFVBQVUsWUFBWSxPQUFPLFVBQVUsYUFBYTtBQUM3RSxVQUFJO0FBQ0osVUFBSTtBQUNBLGVBQVEsTUFBYztBQUFBLE1BQzFCLFNBQVMsS0FBSztBQUNWLGNBQU0sVUFBVTtBQUNoQixnQkFBUSxPQUFPLEdBQUc7QUFDbEI7QUFBQSxNQUNKO0FBRUEsVUFBSSxpQkFBVyxJQUFJLEdBQUc7QUFDbEIsWUFBSTtBQUNBLGNBQUksU0FBVSxNQUFjO0FBQzVCLGNBQUksaUJBQVcsTUFBTSxHQUFHO0FBQ3BCLGtCQUFNLGNBQWMsQ0FBQyxVQUFnQjtBQUNqQyxzQkFBUSxNQUFNLFFBQVEsT0FBTyxDQUFDLEtBQUssQ0FBQztBQUFBLFlBQ3hDO0FBQ0EsZ0JBQUksTUFBTSxRQUFRO0FBSWQsbUJBQUssYUFBYSxpQ0FBSyxVQUFMLEVBQWMsWUFBWSxJQUFHLEtBQUssRUFBRSxNQUFNLE1BQU07QUFBQSxZQUN0RSxPQUFPO0FBQ0gsc0JBQVEsY0FBYztBQUFBLFlBQzFCO0FBQUEsVUFDSjtBQUFBLFFBQ0osU0FBUTtBQUFBLFFBQUM7QUFFVCxjQUFNLFdBQW9DO0FBQUEsVUFDdEMsTUFBTSxNQUFNO0FBQUEsVUFDWixXQUFXO0FBQUEsVUFDWCxJQUFJLFVBQVU7QUFBRSxtQkFBTyxLQUFLLEtBQUs7QUFBQSxVQUFRO0FBQUEsVUFDekMsSUFBSSxRQUFRQyxRQUFPO0FBQUUsaUJBQUssS0FBSyxVQUFVQTtBQUFBLFVBQU87QUFBQSxVQUNoRCxJQUFJLFNBQVM7QUFBRSxtQkFBTyxLQUFLLEtBQUs7QUFBQSxVQUFPO0FBQUEsUUFDM0M7QUFFQSxjQUFNLFdBQVcsWUFBWSxTQUFTLFFBQVE7QUFDOUMsWUFBSTtBQUNBLGtCQUFRLE1BQU0sTUFBTSxPQUFPLENBQUMsWUFBWSxTQUFTLFFBQVEsR0FBRyxRQUFRLENBQUM7QUFBQSxRQUN6RSxTQUFTLEtBQUs7QUFDVixtQkFBUyxHQUFHO0FBQUEsUUFDaEI7QUFDQTtBQUFBLE1BQ0o7QUFBQSxJQUNKO0FBRUEsUUFBSSxNQUFNLFNBQVM7QUFBRTtBQUFBLElBQVE7QUFDN0IsVUFBTSxVQUFVO0FBQ2hCLFlBQVEsUUFBUSxLQUFLO0FBQUEsRUFDekI7QUFDSjtBQUtBLFNBQVMsWUFBZSxTQUE2QyxPQUE0RDtBQUM3SCxTQUFPLENBQUMsV0FBWTtBQUNoQixRQUFJLE1BQU0sV0FBVztBQUFFO0FBQUEsSUFBUTtBQUMvQixVQUFNLFlBQVk7QUFFbEIsUUFBSSxNQUFNLFNBQVM7QUFDZixVQUFJO0FBQ0EsWUFBSSxrQkFBa0IsZUFBZSxNQUFNLGtCQUFrQixlQUFlLE9BQU8sR0FBRyxPQUFPLE9BQU8sTUFBTSxPQUFPLEtBQUssR0FBRztBQUVySDtBQUFBLFFBQ0o7QUFBQSxNQUNKLFNBQVE7QUFBQSxNQUFDO0FBRVQsV0FBSyxRQUFRLE9BQU8sSUFBSSx3QkFBd0IsUUFBUSxTQUFTLE1BQU0sQ0FBQztBQUFBLElBQzVFLE9BQU87QUFDSCxZQUFNLFVBQVU7QUFDaEIsY0FBUSxPQUFPLE1BQU07QUFBQSxJQUN6QjtBQUFBLEVBQ0o7QUFDSjtBQU1BLFNBQVMsVUFBVSxRQUFxQyxRQUFlLE9BQTRCO0FBQy9GLFFBQU0sVUFBMkIsQ0FBQztBQUVsQyxhQUFXLFNBQVMsUUFBUTtBQUN4QixRQUFJO0FBQ0osUUFBSTtBQUNBLFVBQUksQ0FBQyxpQkFBVyxNQUFNLElBQUksR0FBRztBQUFFO0FBQUEsTUFBVTtBQUN6QyxlQUFTLE1BQU07QUFDZixVQUFJLENBQUMsaUJBQVcsTUFBTSxHQUFHO0FBQUU7QUFBQSxNQUFVO0FBQUEsSUFDekMsU0FBUTtBQUFFO0FBQUEsSUFBVTtBQUVwQixRQUFJO0FBQ0osUUFBSTtBQUNBLGVBQVMsUUFBUSxNQUFNLFFBQVEsT0FBTyxDQUFDLEtBQUssQ0FBQztBQUFBLElBQ2pELFNBQVMsS0FBSztBQUNWLGNBQVEsT0FBTyxJQUFJLHdCQUF3QixRQUFRLEtBQUssdUNBQXVDLENBQUM7QUFDaEc7QUFBQSxJQUNKO0FBRUEsUUFBSSxDQUFDLFFBQVE7QUFBRTtBQUFBLElBQVU7QUFDekIsWUFBUTtBQUFBLE9BQ0gsa0JBQWtCLFVBQVcsU0FBUyxRQUFRLFFBQVEsTUFBTSxHQUFHLE1BQU0sQ0FBQyxXQUFZO0FBQy9FLGdCQUFRLE9BQU8sSUFBSSx3QkFBd0IsUUFBUSxRQUFRLHVDQUF1QyxDQUFDO0FBQUEsTUFDdkcsQ0FBQztBQUFBLElBQ0w7QUFBQSxFQUNKO0FBRUEsU0FBTyxRQUFRLElBQUksT0FBTztBQUM5QjtBQUtBLFNBQVMsU0FBWSxHQUFTO0FBQzFCLFNBQU87QUFDWDtBQUtBLFNBQVMsUUFBUSxRQUFxQjtBQUNsQyxRQUFNO0FBQ1Y7QUFLQSxTQUFTLGFBQWEsS0FBa0I7QUFDcEMsTUFBSTtBQUNBLFFBQUksZUFBZSxTQUFTLE9BQU8sUUFBUSxZQUFZLElBQUksYUFBYSxPQUFPLFVBQVUsVUFBVTtBQUMvRixhQUFPLEtBQUs7QUFBQSxJQUNoQjtBQUFBLEVBQ0osU0FBUTtBQUFBLEVBQUM7QUFFVCxNQUFJO0FBQ0EsV0FBTyxLQUFLLFVBQVUsR0FBRztBQUFBLEVBQzdCLFNBQVE7QUFBQSxFQUFDO0FBRVQsTUFBSTtBQUNBLFdBQU8sT0FBTyxVQUFVLFNBQVMsS0FBSyxHQUFHO0FBQUEsRUFDN0MsU0FBUTtBQUFBLEVBQUM7QUFFVCxTQUFPO0FBQ1g7QUFLQSxTQUFTLGVBQWtCLFNBQStDO0FBOTRCMUUsTUFBQUY7QUErNEJJLE1BQUksT0FBMkNBLE1BQUEsUUFBUSxVQUFVLE1BQWxCLE9BQUFBLE1BQXVCLENBQUM7QUFDdkUsTUFBSSxFQUFFLGFBQWEsTUFBTTtBQUNyQixXQUFPLE9BQU8sS0FBSyxxQkFBMkIsQ0FBQztBQUFBLEVBQ25EO0FBQ0EsTUFBSSxRQUFRLFVBQVUsS0FBSyxNQUFNO0FBQzdCLFFBQUksUUFBUztBQUNiLFlBQVEsVUFBVSxJQUFJO0FBQUEsRUFDMUI7QUFDQSxTQUFPLElBQUk7QUFDZjtBQUdBLElBQUksdUJBQXVCLFFBQVE7QUFDbkMsSUFBSSx3QkFBd0IsT0FBTyx5QkFBeUIsWUFBWTtBQUNwRSx5QkFBdUIscUJBQXFCLEtBQUssT0FBTztBQUM1RCxPQUFPO0FBQ0gseUJBQXVCLFdBQXdDO0FBQzNELFFBQUk7QUFDSixRQUFJO0FBQ0osVUFBTSxVQUFVLElBQUksUUFBVyxDQUFDLEtBQUssUUFBUTtBQUFFLGdCQUFVO0FBQUssZUFBUztBQUFBLElBQUssQ0FBQztBQUM3RSxXQUFPLEVBQUUsU0FBUyxTQUFTLE9BQU87QUFBQSxFQUN0QztBQUNKOzs7QUZ0NUJBLE9BQU8sU0FBUyxPQUFPLFVBQVUsQ0FBQztBQUlsQyxJQUFNRyxRQUFPLGlCQUFpQixZQUFZLElBQUk7QUFDOUMsSUFBTSxhQUFhLGlCQUFpQixZQUFZLFVBQVU7QUFDMUQsSUFBTSxnQkFBZ0Isb0JBQUksSUFBOEI7QUFFeEQsSUFBTSxjQUFjO0FBQ3BCLElBQU0sZUFBZTtBQTBCZCxJQUFNLGVBQU4sY0FBMkIsTUFBTTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQU1wQyxZQUFZLFNBQWtCLFNBQXdCO0FBQ2xELFVBQU0sU0FBUyxPQUFPO0FBQ3RCLFNBQUssT0FBTztBQUFBLEVBQ2hCO0FBQ0o7QUFPQSxTQUFTLGFBQXFCO0FBQzFCLE1BQUk7QUFDSixLQUFHO0FBQ0MsYUFBUyxPQUFPO0FBQUEsRUFDcEIsU0FBUyxjQUFjLElBQUksTUFBTTtBQUNqQyxTQUFPO0FBQ1g7QUFjTyxTQUFTLEtBQUssU0FBK0M7QUFDaEUsUUFBTSxLQUFLLFdBQVc7QUFFdEIsUUFBTSxTQUFTLG1CQUFtQixjQUFtQjtBQUNyRCxnQkFBYyxJQUFJLElBQUksRUFBRSxTQUFTLE9BQU8sU0FBUyxRQUFRLE9BQU8sT0FBTyxDQUFDO0FBRXhFLFFBQU0sVUFBVUEsTUFBSyxhQUFhLE9BQU8sT0FBTyxFQUFFLFdBQVcsR0FBRyxHQUFHLE9BQU8sQ0FBQztBQUMzRSxNQUFJLFVBQVU7QUFFZCxVQUFRLEtBQUssQ0FBQyxRQUFRO0FBQ2xCLGNBQVU7QUFDVixrQkFBYyxPQUFPLEVBQUU7QUFDdkIsV0FBTyxRQUFRLEdBQUc7QUFBQSxFQUN0QixHQUFHLENBQUMsUUFBUTtBQUNSLGNBQVU7QUFDVixrQkFBYyxPQUFPLEVBQUU7QUFDdkIsV0FBTyxPQUFPLEdBQUc7QUFBQSxFQUNyQixDQUFDO0FBRUQsUUFBTSxTQUFTLE1BQU07QUFDakIsa0JBQWMsT0FBTyxFQUFFO0FBQ3ZCLFdBQU8sV0FBVyxjQUFjLEVBQUMsV0FBVyxHQUFFLENBQUMsRUFBRSxNQUFNLENBQUMsUUFBUTtBQUM1RCxjQUFRLE1BQU0scURBQXFELEdBQUc7QUFBQSxJQUMxRSxDQUFDO0FBQUEsRUFDTDtBQUVBLFNBQU8sY0FBYyxNQUFNO0FBQ3ZCLFFBQUksU0FBUztBQUNULGFBQU8sT0FBTztBQUFBLElBQ2xCLE9BQU87QUFDSCxhQUFPLFFBQVEsS0FBSyxNQUFNO0FBQUEsSUFDOUI7QUFBQSxFQUNKO0FBRUEsU0FBTyxPQUFPO0FBQ2xCO0FBVU8sU0FBUyxPQUFPLGVBQXVCLE1BQXNDO0FBQ2hGLFNBQU8sS0FBSyxFQUFFLFlBQVksS0FBSyxDQUFDO0FBQ3BDO0FBVU8sU0FBUyxLQUFLLGFBQXFCLE1BQXNDO0FBQzVFLFNBQU8sS0FBSyxFQUFFLFVBQVUsS0FBSyxDQUFDO0FBQ2xDOzs7QUdsSkE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQVlBLElBQU1DLFFBQU8saUJBQWlCLFlBQVksU0FBUztBQUVuRCxJQUFNLG1CQUFtQjtBQUN6QixJQUFNLGdCQUFnQjtBQVFmLFNBQVMsUUFBUSxNQUE2QjtBQUNqRCxTQUFPQSxNQUFLLGtCQUFrQixFQUFDLEtBQUksQ0FBQztBQUN4QztBQU9PLFNBQVMsT0FBd0I7QUFDcEMsU0FBT0EsTUFBSyxhQUFhO0FBQzdCOzs7QUNsQ0E7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBd0RBLElBQU1DLFFBQU8saUJBQWlCLFlBQVksT0FBTztBQUVqRCxJQUFNLFNBQVM7QUFDZixJQUFNLGFBQWE7QUFDbkIsSUFBTSxhQUFhO0FBT1osU0FBUyxTQUE0QjtBQUN4QyxTQUFPQSxNQUFLLE1BQU07QUFDdEI7QUFPTyxTQUFTLGFBQThCO0FBQzFDLFNBQU9BLE1BQUssVUFBVTtBQUMxQjtBQU9PLFNBQVMsYUFBOEI7QUFDMUMsU0FBT0EsTUFBSyxVQUFVO0FBQzFCOzs7QUN2RkE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQVlBLElBQU1DLFNBQU8saUJBQWlCLFlBQVksR0FBRztBQUc3QyxJQUFNLGdCQUFnQjtBQUN0QixJQUFNLGFBQWE7QUFFWixJQUFVO0FBQUEsQ0FBVixDQUFVQyxhQUFWO0FBRUksV0FBUyxPQUFPLFFBQXFCLFVBQXlCO0FBQ2pFLFdBQU9ELE9BQUssZUFBZSxFQUFFLE1BQU0sQ0FBQztBQUFBLEVBQ3hDO0FBRk8sRUFBQUMsU0FBUztBQUFBLEdBRkg7QUFPVixJQUFVO0FBQUEsQ0FBVixDQUFVQyxZQUFWO0FBT0ksV0FBU0MsUUFBc0I7QUFDbEMsV0FBT0gsT0FBSyxVQUFVO0FBQUEsRUFDMUI7QUFGTyxFQUFBRSxRQUFTLE9BQUFDO0FBQUEsR0FQSDs7O0F2QmRqQixPQUFPLFNBQVMsT0FBTyxVQUFVLENBQUM7QUF3RGxDLE9BQU8sT0FBTyxTQUFnQjtBQUM5QixPQUFPLE9BQU8sV0FBVztBQUt6QixPQUFPLE9BQU8seUJBQXlCLGVBQU8sdUJBQXVCLEtBQUssY0FBTTtBQUdoRixPQUFPLE9BQU8sa0JBQWtCO0FBQ2hDLE9BQU8sT0FBTyxrQkFBa0I7QUFDaEMsT0FBTyxPQUFPLGlCQUFpQjtBQUV4QixPQUFPLHFCQUFxQjtBQU81QixTQUFTLG1CQUFtQixLQUE0QjtBQUMzRCxTQUFPLE1BQU0sS0FBSyxFQUFFLFFBQVEsT0FBTyxDQUFDLEVBQy9CLEtBQUssY0FBWTtBQUNkLFFBQUksU0FBUyxJQUFJO0FBQ2IsWUFBTSxTQUFTLFNBQVMsY0FBYyxRQUFRO0FBQzlDLGFBQU8sTUFBTTtBQUNiLGVBQVMsS0FBSyxZQUFZLE1BQU07QUFBQSxJQUNwQztBQUFBLEVBQ0osQ0FBQyxFQUNBLE1BQU0sTUFBTTtBQUFBLEVBQUMsQ0FBQztBQUN2QjtBQUdBLG1CQUFtQixrQkFBa0I7IiwKICAibmFtZXMiOiBbIl9hIiwgIkVycm9yIiwgImNhbGwiLCAiRXJyb3IiLCAiX2EiLCAiQXJyYXkiLCAiTWFwIiwgIkFycmF5IiwgIk1hcCIsICJrZXkiLCAiY2FsbCIsICJfYSIsICJfYSIsICJyZXNpemFibGUiLCAiX2EiLCAiY2FsbCIsICJfYSIsICJjYWxsIiwgIl9hIiwgImNhbGwiLCAiSGlkZU1ldGhvZCIsICJTaG93TWV0aG9kIiwgImlzRG9jdW1lbnREb3RBbGwiLCAiX2EiLCAicmVhc29uIiwgInZhbHVlIiwgImNhbGwiLCAiY2FsbCIsICJjYWxsIiwgImNhbGwiLCAiSGFwdGljcyIsICJEZXZpY2UiLCAiSW5mbyJdCn0K diff --git a/v3/internal/assetserver/bundledassets/runtime.js b/v3/internal/assetserver/bundledassets/runtime.js new file mode 100644 index 000000000..5db985513 --- /dev/null +++ b/v3/internal/assetserver/bundledassets/runtime.js @@ -0,0 +1 @@ +var Ge=Object.defineProperty,Pn=Object.defineProperties;var Tn=Object.getOwnPropertyDescriptors;var Ze=Object.getOwnPropertySymbols;var Mn=Object.prototype.hasOwnProperty,Sn=Object.prototype.propertyIsEnumerable;var _e=(n,e,i)=>e in n?Ge(n,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):n[e]=i,Ke=(n,e)=>{for(var i in e||(e={}))Mn.call(e,i)&&_e(n,i,e[i]);if(Ze)for(var i of Ze(e))Sn.call(e,i)&&_e(n,i,e[i]);return n},Ye=(n,e)=>Pn(n,Tn(e));var p=(n,e)=>{for(var i in e)Ge(n,i,{get:e[i],enumerable:!0})};var fe={};p(fe,{Application:()=>xe,Browser:()=>ie,Call:()=>Ue,CancelError:()=>L,CancellablePromise:()=>H,CancelledRejectionError:()=>v,Clipboard:()=>ze,Create:()=>re,Dialogs:()=>te,Events:()=>le,Flags:()=>ve,IOS:()=>Ve,Screens:()=>je,System:()=>be,WML:()=>pe,Window:()=>E,clientId:()=>I,getTransport:()=>Qe,loadOptionalScript:()=>Cn,objectNames:()=>m,setTransport:()=>Xe});var pe={};p(pe,{Enable:()=>we,Reload:()=>dn});var ie={};p(ie,{OpenURL:()=>ne});var En="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";function Z(n=21){let e="",i=n|0;for(;i--;)e+=En[Math.random()*64|0];return e}var xn=window.location.origin+"/wails/runtime";var m=Object.freeze({Call:0,Clipboard:1,Application:2,Events:3,ContextMenu:4,Dialog:5,Window:6,Screens:7,System:8,Browser:9,CancelCall:10,IOS:11}),I=Z(),_=null;function Xe(n){_=n}function Qe(){return _}function u(n,e=""){return function(i,o=null){return An(n,i,e,o)}}async function An(n,e,i,o){var d,w;if(_)return _.call(n,e,i,o);let t=new URL(xn),r={object:n,method:e};o!=null&&(r.args=o);let a={"x-wails-client-id":I,"Content-Type":"application/json"};i&&(a["x-wails-window-name"]=i);let l=await fetch(t,{method:"POST",headers:a,body:JSON.stringify(r)});if(!l.ok)throw new Error(await l.text());return((w=(d=l.headers.get("Content-Type"))==null?void 0:d.indexOf("application/json"))!=null?w:-1)!==-1?l.json():l.text()}var Rn=u(m.Browser),Fn=0;function ne(n){return Rn(Fn,{url:n.toString()})}var te={};p(te,{Error:()=>Hn,Info:()=>jn,OpenFile:()=>Vn,Question:()=>oe,SaveFile:()=>Zn,Warning:()=>Bn});window._wails=window._wails||{};var On=u(m.Dialog),kn=0,Ln=1,In=2,Un=3,zn=4,Nn=5;function S(n,e={}){return On(n,e)}function jn(n){return S(kn,n)}function Bn(n){return S(Ln,n)}function Hn(n){return S(In,n)}function oe(n){return S(Un,n)}function Vn(n){var e;return(e=S(zn,n))!=null?e:[]}function Zn(n){return S(Nn,n)}var le={};p(le,{Emit:()=>se,Off:()=>ii,OffAll:()=>oi,On:()=>ei,OnMultiple:()=>ae,Once:()=>ni,Types:()=>Qn,WailsEvent:()=>U});var g=new Map,G=class{constructor(e,i,o){this.eventName=e,this.callback=i,this.maxCallbacks=o||-1}dispatch(e){try{this.callback(e)}catch(i){}return this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0)}};function qe(n){let e=g.get(n.eventName);e&&(e=e.filter(i=>i!==n),e.length===0?g.delete(n.eventName):g.set(n.eventName,e))}var re={};p(re,{Any:()=>D,Array:()=>Gn,ByteSlice:()=>_n,Events:()=>K,Map:()=>Kn,Nullable:()=>Yn,Struct:()=>Xn});function D(n){return n}function _n(n){return n==null?"":n}function Gn(n){return n===D?e=>e===null?[]:e:e=>{if(e===null)return[];for(let i=0;ii===null?{}:i:i=>{if(i===null)return{};for(let o in i)i[o]=e(i[o]);return i}}function Yn(n){return n===D?D:e=>e===null?null:n(e)}function Xn(n){let e=!0;for(let i in n)if(n[i]!==D){e=!1;break}return e?D:i=>{for(let o in n)o in i&&(i[o]=n[o](i[o]));return i}}var K={};var Qn=Object.freeze({Windows:Object.freeze({APMPowerSettingChange:"windows:APMPowerSettingChange",APMPowerStatusChange:"windows:APMPowerStatusChange",APMResumeAutomatic:"windows:APMResumeAutomatic",APMResumeSuspend:"windows:APMResumeSuspend",APMSuspend:"windows:APMSuspend",ApplicationStarted:"windows:ApplicationStarted",SystemThemeChanged:"windows:SystemThemeChanged",WebViewNavigationCompleted:"windows:WebViewNavigationCompleted",WindowActive:"windows:WindowActive",WindowBackgroundErase:"windows:WindowBackgroundErase",WindowClickActive:"windows:WindowClickActive",WindowClosing:"windows:WindowClosing",WindowDidMove:"windows:WindowDidMove",WindowDidResize:"windows:WindowDidResize",WindowDPIChanged:"windows:WindowDPIChanged",WindowDragDrop:"windows:WindowDragDrop",WindowDragEnter:"windows:WindowDragEnter",WindowDragLeave:"windows:WindowDragLeave",WindowDragOver:"windows:WindowDragOver",WindowEndMove:"windows:WindowEndMove",WindowEndResize:"windows:WindowEndResize",WindowFullscreen:"windows:WindowFullscreen",WindowHide:"windows:WindowHide",WindowInactive:"windows:WindowInactive",WindowKeyDown:"windows:WindowKeyDown",WindowKeyUp:"windows:WindowKeyUp",WindowKillFocus:"windows:WindowKillFocus",WindowNonClientHit:"windows:WindowNonClientHit",WindowNonClientMouseDown:"windows:WindowNonClientMouseDown",WindowNonClientMouseLeave:"windows:WindowNonClientMouseLeave",WindowNonClientMouseMove:"windows:WindowNonClientMouseMove",WindowNonClientMouseUp:"windows:WindowNonClientMouseUp",WindowPaint:"windows:WindowPaint",WindowRestore:"windows:WindowRestore",WindowSetFocus:"windows:WindowSetFocus",WindowShow:"windows:WindowShow",WindowStartMove:"windows:WindowStartMove",WindowStartResize:"windows:WindowStartResize",WindowUnFullscreen:"windows:WindowUnFullscreen",WindowZOrderChanged:"windows:WindowZOrderChanged",WindowMinimise:"windows:WindowMinimise",WindowUnMinimise:"windows:WindowUnMinimise",WindowMaximise:"windows:WindowMaximise",WindowUnMaximise:"windows:WindowUnMaximise"}),Mac:Object.freeze({ApplicationDidBecomeActive:"mac:ApplicationDidBecomeActive",ApplicationDidChangeBackingProperties:"mac:ApplicationDidChangeBackingProperties",ApplicationDidChangeEffectiveAppearance:"mac:ApplicationDidChangeEffectiveAppearance",ApplicationDidChangeIcon:"mac:ApplicationDidChangeIcon",ApplicationDidChangeOcclusionState:"mac:ApplicationDidChangeOcclusionState",ApplicationDidChangeScreenParameters:"mac:ApplicationDidChangeScreenParameters",ApplicationDidChangeStatusBarFrame:"mac:ApplicationDidChangeStatusBarFrame",ApplicationDidChangeStatusBarOrientation:"mac:ApplicationDidChangeStatusBarOrientation",ApplicationDidChangeTheme:"mac:ApplicationDidChangeTheme",ApplicationDidFinishLaunching:"mac:ApplicationDidFinishLaunching",ApplicationDidHide:"mac:ApplicationDidHide",ApplicationDidResignActive:"mac:ApplicationDidResignActive",ApplicationDidUnhide:"mac:ApplicationDidUnhide",ApplicationDidUpdate:"mac:ApplicationDidUpdate",ApplicationShouldHandleReopen:"mac:ApplicationShouldHandleReopen",ApplicationWillBecomeActive:"mac:ApplicationWillBecomeActive",ApplicationWillFinishLaunching:"mac:ApplicationWillFinishLaunching",ApplicationWillHide:"mac:ApplicationWillHide",ApplicationWillResignActive:"mac:ApplicationWillResignActive",ApplicationWillTerminate:"mac:ApplicationWillTerminate",ApplicationWillUnhide:"mac:ApplicationWillUnhide",ApplicationWillUpdate:"mac:ApplicationWillUpdate",MenuDidAddItem:"mac:MenuDidAddItem",MenuDidBeginTracking:"mac:MenuDidBeginTracking",MenuDidClose:"mac:MenuDidClose",MenuDidDisplayItem:"mac:MenuDidDisplayItem",MenuDidEndTracking:"mac:MenuDidEndTracking",MenuDidHighlightItem:"mac:MenuDidHighlightItem",MenuDidOpen:"mac:MenuDidOpen",MenuDidPopUp:"mac:MenuDidPopUp",MenuDidRemoveItem:"mac:MenuDidRemoveItem",MenuDidSendAction:"mac:MenuDidSendAction",MenuDidSendActionToItem:"mac:MenuDidSendActionToItem",MenuDidUpdate:"mac:MenuDidUpdate",MenuWillAddItem:"mac:MenuWillAddItem",MenuWillBeginTracking:"mac:MenuWillBeginTracking",MenuWillDisplayItem:"mac:MenuWillDisplayItem",MenuWillEndTracking:"mac:MenuWillEndTracking",MenuWillHighlightItem:"mac:MenuWillHighlightItem",MenuWillOpen:"mac:MenuWillOpen",MenuWillPopUp:"mac:MenuWillPopUp",MenuWillRemoveItem:"mac:MenuWillRemoveItem",MenuWillSendAction:"mac:MenuWillSendAction",MenuWillSendActionToItem:"mac:MenuWillSendActionToItem",MenuWillUpdate:"mac:MenuWillUpdate",WebViewDidCommitNavigation:"mac:WebViewDidCommitNavigation",WebViewDidFinishNavigation:"mac:WebViewDidFinishNavigation",WebViewDidReceiveServerRedirectForProvisionalNavigation:"mac:WebViewDidReceiveServerRedirectForProvisionalNavigation",WebViewDidStartProvisionalNavigation:"mac:WebViewDidStartProvisionalNavigation",WindowDidBecomeKey:"mac:WindowDidBecomeKey",WindowDidBecomeMain:"mac:WindowDidBecomeMain",WindowDidBeginSheet:"mac:WindowDidBeginSheet",WindowDidChangeAlpha:"mac:WindowDidChangeAlpha",WindowDidChangeBackingLocation:"mac:WindowDidChangeBackingLocation",WindowDidChangeBackingProperties:"mac:WindowDidChangeBackingProperties",WindowDidChangeCollectionBehavior:"mac:WindowDidChangeCollectionBehavior",WindowDidChangeEffectiveAppearance:"mac:WindowDidChangeEffectiveAppearance",WindowDidChangeOcclusionState:"mac:WindowDidChangeOcclusionState",WindowDidChangeOrderingMode:"mac:WindowDidChangeOrderingMode",WindowDidChangeScreen:"mac:WindowDidChangeScreen",WindowDidChangeScreenParameters:"mac:WindowDidChangeScreenParameters",WindowDidChangeScreenProfile:"mac:WindowDidChangeScreenProfile",WindowDidChangeScreenSpace:"mac:WindowDidChangeScreenSpace",WindowDidChangeScreenSpaceProperties:"mac:WindowDidChangeScreenSpaceProperties",WindowDidChangeSharingType:"mac:WindowDidChangeSharingType",WindowDidChangeSpace:"mac:WindowDidChangeSpace",WindowDidChangeSpaceOrderingMode:"mac:WindowDidChangeSpaceOrderingMode",WindowDidChangeTitle:"mac:WindowDidChangeTitle",WindowDidChangeToolbar:"mac:WindowDidChangeToolbar",WindowDidDeminiaturize:"mac:WindowDidDeminiaturize",WindowDidEndSheet:"mac:WindowDidEndSheet",WindowDidEnterFullScreen:"mac:WindowDidEnterFullScreen",WindowDidEnterVersionBrowser:"mac:WindowDidEnterVersionBrowser",WindowDidExitFullScreen:"mac:WindowDidExitFullScreen",WindowDidExitVersionBrowser:"mac:WindowDidExitVersionBrowser",WindowDidExpose:"mac:WindowDidExpose",WindowDidFocus:"mac:WindowDidFocus",WindowDidMiniaturize:"mac:WindowDidMiniaturize",WindowDidMove:"mac:WindowDidMove",WindowDidOrderOffScreen:"mac:WindowDidOrderOffScreen",WindowDidOrderOnScreen:"mac:WindowDidOrderOnScreen",WindowDidResignKey:"mac:WindowDidResignKey",WindowDidResignMain:"mac:WindowDidResignMain",WindowDidResize:"mac:WindowDidResize",WindowDidUpdate:"mac:WindowDidUpdate",WindowDidUpdateAlpha:"mac:WindowDidUpdateAlpha",WindowDidUpdateCollectionBehavior:"mac:WindowDidUpdateCollectionBehavior",WindowDidUpdateCollectionProperties:"mac:WindowDidUpdateCollectionProperties",WindowDidUpdateShadow:"mac:WindowDidUpdateShadow",WindowDidUpdateTitle:"mac:WindowDidUpdateTitle",WindowDidUpdateToolbar:"mac:WindowDidUpdateToolbar",WindowDidZoom:"mac:WindowDidZoom",WindowFileDraggingEntered:"mac:WindowFileDraggingEntered",WindowFileDraggingExited:"mac:WindowFileDraggingExited",WindowFileDraggingPerformed:"mac:WindowFileDraggingPerformed",WindowHide:"mac:WindowHide",WindowMaximise:"mac:WindowMaximise",WindowUnMaximise:"mac:WindowUnMaximise",WindowMinimise:"mac:WindowMinimise",WindowUnMinimise:"mac:WindowUnMinimise",WindowShouldClose:"mac:WindowShouldClose",WindowShow:"mac:WindowShow",WindowWillBecomeKey:"mac:WindowWillBecomeKey",WindowWillBecomeMain:"mac:WindowWillBecomeMain",WindowWillBeginSheet:"mac:WindowWillBeginSheet",WindowWillChangeOrderingMode:"mac:WindowWillChangeOrderingMode",WindowWillClose:"mac:WindowWillClose",WindowWillDeminiaturize:"mac:WindowWillDeminiaturize",WindowWillEnterFullScreen:"mac:WindowWillEnterFullScreen",WindowWillEnterVersionBrowser:"mac:WindowWillEnterVersionBrowser",WindowWillExitFullScreen:"mac:WindowWillExitFullScreen",WindowWillExitVersionBrowser:"mac:WindowWillExitVersionBrowser",WindowWillFocus:"mac:WindowWillFocus",WindowWillMiniaturize:"mac:WindowWillMiniaturize",WindowWillMove:"mac:WindowWillMove",WindowWillOrderOffScreen:"mac:WindowWillOrderOffScreen",WindowWillOrderOnScreen:"mac:WindowWillOrderOnScreen",WindowWillResignMain:"mac:WindowWillResignMain",WindowWillResize:"mac:WindowWillResize",WindowWillUnfocus:"mac:WindowWillUnfocus",WindowWillUpdate:"mac:WindowWillUpdate",WindowWillUpdateAlpha:"mac:WindowWillUpdateAlpha",WindowWillUpdateCollectionBehavior:"mac:WindowWillUpdateCollectionBehavior",WindowWillUpdateCollectionProperties:"mac:WindowWillUpdateCollectionProperties",WindowWillUpdateShadow:"mac:WindowWillUpdateShadow",WindowWillUpdateTitle:"mac:WindowWillUpdateTitle",WindowWillUpdateToolbar:"mac:WindowWillUpdateToolbar",WindowWillUpdateVisibility:"mac:WindowWillUpdateVisibility",WindowWillUseStandardFrame:"mac:WindowWillUseStandardFrame",WindowZoomIn:"mac:WindowZoomIn",WindowZoomOut:"mac:WindowZoomOut",WindowZoomReset:"mac:WindowZoomReset"}),Linux:Object.freeze({ApplicationStartup:"linux:ApplicationStartup",SystemThemeChanged:"linux:SystemThemeChanged",WindowDeleteEvent:"linux:WindowDeleteEvent",WindowDidMove:"linux:WindowDidMove",WindowDidResize:"linux:WindowDidResize",WindowFocusIn:"linux:WindowFocusIn",WindowFocusOut:"linux:WindowFocusOut",WindowLoadStarted:"linux:WindowLoadStarted",WindowLoadRedirected:"linux:WindowLoadRedirected",WindowLoadCommitted:"linux:WindowLoadCommitted",WindowLoadFinished:"linux:WindowLoadFinished"}),iOS:Object.freeze({ApplicationDidBecomeActive:"ios:ApplicationDidBecomeActive",ApplicationDidEnterBackground:"ios:ApplicationDidEnterBackground",ApplicationDidFinishLaunching:"ios:ApplicationDidFinishLaunching",ApplicationDidReceiveMemoryWarning:"ios:ApplicationDidReceiveMemoryWarning",ApplicationWillEnterForeground:"ios:ApplicationWillEnterForeground",ApplicationWillResignActive:"ios:ApplicationWillResignActive",ApplicationWillTerminate:"ios:ApplicationWillTerminate",WindowDidLoad:"ios:WindowDidLoad",WindowWillAppear:"ios:WindowWillAppear",WindowDidAppear:"ios:WindowDidAppear",WindowWillDisappear:"ios:WindowWillDisappear",WindowDidDisappear:"ios:WindowDidDisappear",WindowSafeAreaInsetsChanged:"ios:WindowSafeAreaInsetsChanged",WindowOrientationChanged:"ios:WindowOrientationChanged",WindowTouchBegan:"ios:WindowTouchBegan",WindowTouchMoved:"ios:WindowTouchMoved",WindowTouchEnded:"ios:WindowTouchEnded",WindowTouchCancelled:"ios:WindowTouchCancelled",WebViewDidStartNavigation:"ios:WebViewDidStartNavigation",WebViewDidFinishNavigation:"ios:WebViewDidFinishNavigation",WebViewDidFailNavigation:"ios:WebViewDidFailNavigation",WebViewDecidePolicyForNavigationAction:"ios:WebViewDecidePolicyForNavigationAction"}),Common:Object.freeze({ApplicationOpenedWithFile:"common:ApplicationOpenedWithFile",ApplicationStarted:"common:ApplicationStarted",ApplicationLaunchedWithUrl:"common:ApplicationLaunchedWithUrl",ThemeChanged:"common:ThemeChanged",WindowClosing:"common:WindowClosing",WindowDidMove:"common:WindowDidMove",WindowDidResize:"common:WindowDidResize",WindowDPIChanged:"common:WindowDPIChanged",WindowFilesDropped:"common:WindowFilesDropped",WindowFocus:"common:WindowFocus",WindowFullscreen:"common:WindowFullscreen",WindowHide:"common:WindowHide",WindowLostFocus:"common:WindowLostFocus",WindowMaximise:"common:WindowMaximise",WindowMinimise:"common:WindowMinimise",WindowToggleFrameless:"common:WindowToggleFrameless",WindowRestore:"common:WindowRestore",WindowRuntimeReady:"common:WindowRuntimeReady",WindowShow:"common:WindowShow",WindowUnFullscreen:"common:WindowUnFullscreen",WindowUnMaximise:"common:WindowUnMaximise",WindowUnMinimise:"common:WindowUnMinimise",WindowZoom:"common:WindowZoom",WindowZoomIn:"common:WindowZoomIn",WindowZoomOut:"common:WindowZoomOut",WindowZoomReset:"common:WindowZoomReset"})});window._wails=window._wails||{};window._wails.dispatchWailsEvent=Jn;var qn=u(m.Events),$n=0,U=class{constructor(e,i){this.name=e,this.data=i!=null?i:null}};function Jn(n){let e=g.get(n.name);if(!e)return;let i=new U(n.name,n.name in K?K[n.name](n.data):n.data);"sender"in n&&(i.sender=n.sender),e=e.filter(o=>!o.dispatch(i)),e.length===0?g.delete(n.name):g.set(n.name,e)}function ae(n,e,i){let o=g.get(n)||[],t=new G(n,e,i);return o.push(t),g.set(n,o),()=>qe(t)}function ei(n,e){return ae(n,e,-1)}function ni(n,e){return ae(n,e,1)}function ii(...n){n.forEach(e=>g.delete(e))}function oi(){g.clear()}function se(n,e){return qn($n,new U(n,e))}function $e(){return new MouseEvent("mousedown").buttons===0}function Je(){if(!EventTarget||!AbortSignal||!AbortController)return!1;let n=!0,e=new EventTarget,i=new AbortController;return e.addEventListener("test",()=>{n=!1},{signal:i.signal}),i.abort(),e.dispatchEvent(new CustomEvent("test")),n}function Y(n){var e;return n.target instanceof HTMLElement?n.target:!(n.target instanceof HTMLElement)&&n.target instanceof Node&&(e=n.target.parentElement)!=null?e:document.body}var en=!1;document.addEventListener("DOMContentLoaded",()=>{en=!0});function nn(n){en||document.readyState==="complete"?n():document.addEventListener("DOMContentLoaded",n)}var ti="data-file-drop-target",h="file-drop-target-active",c=null,ri=0,ai=1,si=2,li=3,di=4,ci=5,mi=6,ui=7,wi=8,pi=9,fi=10,gi=11,hi=12,Wi=13,bi=14,vi=15,yi=16,Di=17,Ci=18,Pi=19,Ti=20,Mi=21,Si=22,Ei=23,xi=24,Ai=25,Ri=26,Fi=27,Oi=28,ki=29,Li=30,Ii=31,Ui=32,zi=33,Ni=34,ji=35,Bi=36,Hi=37,Vi=38,Zi=39,_i=40,Gi=41,Ki=42,Yi=43,Xi=44,Qi=45,qi=46,$i=47,Ji=48,eo=49,no=50,io=51;function X(n){return n?n.closest("[".concat(ti,"]")):null}function oo(){var n,e,i,o;return((e=(n=window.chrome)==null?void 0:n.webview)==null?void 0:e.postMessageWithAdditionalObjects)==null?!1:((o=(i=window._wails)==null?void 0:i.flags)==null?void 0:o.enableFileDrop)===!0}function to(n,e,i){var o,t;(t=(o=window.chrome)==null?void 0:o.webview)!=null&&t.postMessageWithAdditionalObjects&&window.chrome.webview.postMessageWithAdditionalObjects("file:drop:".concat(n,":").concat(e),i)}var ce=!1;function on(){ce=!1,c&&(c.classList.remove(h),c=null)}function tn(){var n,e;((e=(n=window._wails)==null?void 0:n.flags)==null?void 0:e.enableFileDrop)!==!1&&(ce=!0)}function rn(){on()}function an(n,e){var t,r;if(!ce||((r=(t=window._wails)==null?void 0:t.flags)==null?void 0:r.enableFileDrop)===!1)return;let i=document.elementFromPoint(n,e),o=X(i);c&&c!==o&&c.classList.remove(h),o?(o.classList.add(h),c=o):c=null}var s=Symbol("caller"),Q=class Q{constructor(e=""){this[s]=u(m.Window,e);for(let i of Object.getOwnPropertyNames(Q.prototype))i!=="constructor"&&typeof this[i]=="function"&&(this[i]=this[i].bind(this))}Get(e){return new Q(e)}Position(){return this[s](ri)}Center(){return this[s](ai)}Close(){return this[s](si)}DisableSizeConstraints(){return this[s](li)}EnableSizeConstraints(){return this[s](di)}Focus(){return this[s](ci)}ForceReload(){return this[s](mi)}Fullscreen(){return this[s](ui)}GetScreen(){return this[s](wi)}GetZoom(){return this[s](pi)}Height(){return this[s](fi)}Hide(){return this[s](gi)}IsFocused(){return this[s](hi)}IsFullscreen(){return this[s](Wi)}IsMaximised(){return this[s](bi)}IsMinimised(){return this[s](vi)}Maximise(){return this[s](yi)}Minimise(){return this[s](Di)}Name(){return this[s](Ci)}OpenDevTools(){return this[s](Pi)}RelativePosition(){return this[s](Ti)}Reload(){return this[s](Mi)}Resizable(){return this[s](Si)}Restore(){return this[s](Ei)}SetPosition(e,i){return this[s](xi,{x:e,y:i})}SetAlwaysOnTop(e){return this[s](Ai,{alwaysOnTop:e})}SetBackgroundColour(e,i,o,t){return this[s](Ri,{r:e,g:i,b:o,a:t})}SetFrameless(e){return this[s](Fi,{frameless:e})}SetFullscreenButtonEnabled(e){return this[s](Oi,{enabled:e})}SetMaxSize(e,i){return this[s](ki,{width:e,height:i})}SetMinSize(e,i){return this[s](Li,{width:e,height:i})}SetRelativePosition(e,i){return this[s](Ii,{x:e,y:i})}SetResizable(e){return this[s](Ui,{resizable:e})}SetSize(e,i){return this[s](zi,{width:e,height:i})}SetTitle(e){return this[s](Ni,{title:e})}SetZoom(e){return this[s](ji,{zoom:e})}Show(){return this[s](Bi)}Size(){return this[s](Hi)}ToggleFullscreen(){return this[s](Vi)}ToggleMaximise(){return this[s](Zi)}ToggleFrameless(){return this[s](_i)}UnFullscreen(){return this[s](Gi)}UnMaximise(){return this[s](Ki)}UnMinimise(){return this[s](Yi)}Width(){return this[s](Xi)}Zoom(){return this[s](Qi)}ZoomIn(){return this[s](qi)}ZoomOut(){return this[s]($i)}ZoomReset(){return this[s](Ji)}HandlePlatformFileDrop(e,i,o){var d,w;if(((w=(d=window._wails)==null?void 0:d.flags)==null?void 0:w.enableFileDrop)===!1)return;let t=document.elementFromPoint(i,o),r=X(t);if(!r)return;let a={id:r.id,classList:Array.from(r.classList),attributes:{}};for(let y=0;y{var r,a,l;if(!((r=i.dataTransfer)!=null&&r.types.includes("Files")))return;if(i.preventDefault(),((l=(a=window._wails)==null?void 0:a.flags)==null?void 0:l.enableFileDrop)===!1){i.dataTransfer.dropEffect="none";return}e++;let o=document.elementFromPoint(i.clientX,i.clientY),t=X(o);c&&c!==t&&c.classList.remove(h),t?(t.classList.add(h),i.dataTransfer.dropEffect="copy",c=t):(i.dataTransfer.dropEffect="none",c=null)},!1),n.addEventListener("dragover",i=>{var r,a,l;if(!((r=i.dataTransfer)!=null&&r.types.includes("Files")))return;if(i.preventDefault(),((l=(a=window._wails)==null?void 0:a.flags)==null?void 0:l.enableFileDrop)===!1){i.dataTransfer.dropEffect="none";return}let o=document.elementFromPoint(i.clientX,i.clientY),t=X(o);c&&c!==t&&c.classList.remove(h),t?(t.classList.contains(h)||t.classList.add(h),i.dataTransfer.dropEffect="copy",c=t):(i.dataTransfer.dropEffect="none",c=null)},!1),n.addEventListener("dragleave",i=>{var o,t,r;(o=i.dataTransfer)!=null&&o.types.includes("Files")&&(i.preventDefault(),((r=(t=window._wails)==null?void 0:t.flags)==null?void 0:r.enableFileDrop)!==!1&&i.relatedTarget!==null&&(e--,(e===0||c&&!c.contains(i.relatedTarget))&&(c&&(c.classList.remove(h),c=null),e=0)))},!1),n.addEventListener("drop",i=>{var o,t,r;if((o=i.dataTransfer)!=null&&o.types.includes("Files")&&(i.preventDefault(),((r=(t=window._wails)==null?void 0:t.flags)==null?void 0:r.enableFileDrop)!==!1&&(e=0,c&&(c.classList.remove(h),c=null),oo()))){let a=[];if(i.dataTransfer.items){for(let l of i.dataTransfer.items)if(l.kind==="file"){let d=l.getAsFile();d&&a.push(d)}}else if(i.dataTransfer.files)for(let l of i.dataTransfer.files)a.push(l);a.length>0&&to(i.clientX,i.clientY,a)}},!1)}typeof window<"u"&&typeof document<"u"&&ao();var E=ro;function so(n,e=null){se(n,e)}function lo(n,e){let i=E.Get(n),o=i[e];if(typeof o=="function")try{o.call(i)}catch(t){}}function sn(n){let e=n.currentTarget;function i(t="Yes"){if(t!=="Yes")return;let r=e.getAttribute("wml-event")||e.getAttribute("data-wml-event"),a=e.getAttribute("wml-target-window")||e.getAttribute("data-wml-target-window")||"",l=e.getAttribute("wml-window")||e.getAttribute("data-wml-window"),d=e.getAttribute("wml-openurl")||e.getAttribute("data-wml-openurl");r!==null&&so(r),l!==null&&lo(a,l),d!==null&&ne(d)}let o=e.getAttribute("wml-confirm")||e.getAttribute("data-wml-confirm");o?oe({Title:"Confirm",Message:o,Detached:!1,Buttons:[{Label:"Yes"},{Label:"No",IsDefault:!0}]}).then(i):i()}var z=Symbol("controller"),x=Symbol("triggerMap"),C=Symbol("elementCount"),me=class{constructor(){this[z]=new AbortController}set(e,i){return{signal:this[z].signal}}reset(){this[z].abort(),this[z]=new AbortController}},ue=class{constructor(){this[x]=new WeakMap,this[C]=0}set(e,i){return this[x].has(e)||this[C]++,this[x].set(e,i),{}}reset(){if(!(this[C]<=0)){for(let e of document.body.querySelectorAll("*")){if(this[C]<=0)break;let i=this[x].get(e);i!=null&&this[C]--;for(let o of i||[])e.removeEventListener(o,sn)}this[x]=new WeakMap,this[C]=0}}},ln=Je()?new me:new ue;function co(n){let e=/\S+/g,i=n.getAttribute("wml-trigger")||n.getAttribute("data-wml-trigger")||"click",o=[],t;for(;(t=e.exec(i))!==null;)o.push(t[0]);let r=ln.set(n,o);for(let a of o)n.addEventListener(a,sn,r)}function we(){nn(dn)}function dn(){ln.reset(),document.body.querySelectorAll("[wml-event], [wml-window], [wml-openurl], [data-wml-event], [data-wml-window], [data-wml-openurl]").forEach(co)}window.wails=fe;we();var be={};p(be,{Capabilities:()=>fo,Environment:()=>go,IsAMD64:()=>bo,IsARM:()=>vo,IsARM64:()=>yo,IsDarkMode:()=>po,IsDebug:()=>We,IsLinux:()=>ho,IsMac:()=>Wo,IsWindows:()=>q,invoke:()=>P});var he=u(m.System),mo=0,uo=1,wo=2,ge=(function(){var n,e,i,o,t,r;try{if((e=(n=window.chrome)==null?void 0:n.webview)!=null&&e.postMessage)return window.chrome.webview.postMessage.bind(window.chrome.webview);if((t=(o=(i=window.webkit)==null?void 0:i.messageHandlers)==null?void 0:o.external)!=null&&t.postMessage)return window.webkit.messageHandlers.external.postMessage.bind(window.webkit.messageHandlers.external);if((r=window.wails)!=null&&r.invoke)return a=>window.wails.invoke(typeof a=="string"?a:JSON.stringify(a))}catch(a){}return null})();function P(n){ge==null||ge(n)}function po(){return he(mo)}async function fo(){return he(wo)}function go(){return he(uo)}function q(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.OS)==="windows"}function ho(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.OS)==="linux"}function Wo(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.OS)==="darwin"}function bo(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.Arch)==="amd64"}function vo(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.Arch)==="arm"}function yo(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.Arch)==="arm64"}function We(){var n,e;return!!((e=(n=window._wails)==null?void 0:n.environment)!=null&&e.Debug)}window.addEventListener("contextmenu",To);var Do=u(m.ContextMenu),Co=0;function Po(n,e,i,o){Do(Co,{id:n,x:e,y:i,data:o})}function To(n){let e=Y(n),i=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu").trim();if(i){n.preventDefault();let o=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu-data");Po(i,n.clientX,n.clientY,o)}else Mo(n,e)}function Mo(n,e){if(We())return;switch(window.getComputedStyle(e).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":n.preventDefault();return}if(e.isContentEditable)return;let i=window.getSelection(),o=i&&i.toString().length>0;if(o)for(let t=0;tN});function N(n){try{return window._wails.flags[n]}catch(e){throw new Error("Unable to retrieve flag '"+n+"': "+e,{cause:e})}}var j=!1,B=!1,Te=!1,R=!1,F=!1,T="",cn="auto",A=0,ye=$e();window._wails=window._wails||{};window._wails.setResizable=n=>{Te=n,Te||(R=F=!1,f())};var Me=!1;function So(){var i,o;let n=(o=(i=window._wails)==null?void 0:i.environment)==null?void 0:o.OS;if(n==="ios"||n==="android")return!0;let e=navigator.userAgent||navigator.vendor||window.opera||"";return/android|iphone|ipad|ipod|iemobile|wpdesktop/i.test(e)}function Se(){if(!Me&&!So()){window.addEventListener("mousedown",Pe,{capture:!0}),window.addEventListener("mousemove",Pe,{capture:!0}),window.addEventListener("mouseup",Pe,{capture:!0});for(let n of["click","contextmenu","dblclick"])window.addEventListener(n,xo,{capture:!0});Me=!0}}Se();document.addEventListener("DOMContentLoaded",Se,{once:!0});var Eo=0,mn=window.setInterval(()=>{if(Me){window.clearInterval(mn);return}Se(),++Eo>100&&window.clearInterval(mn)},50);function xo(n){(B||F)&&(n.stopImmediatePropagation(),n.stopPropagation(),n.preventDefault())}var De=0,Ao=1,Ce=2;function Pe(n){let e,i=n.buttons;switch(n.type){case"mousedown":e=De,ye||(i=A|1<zo,Quit:()=>jo,Show:()=>No});var Ee=u(m.Application),Lo=0,Io=1,Uo=2;function zo(){return Ee(Lo)}function No(){return Ee(Io)}function jo(){return Ee(Uo)}var Ue={};p(Ue,{ByID:()=>at,ByName:()=>rt,Call:()=>Ie,RuntimeError:()=>Le});var wn=Function.prototype.toString,O=typeof Reflect=="object"&&Reflect!==null&&Reflect.apply,Ae,$;if(typeof O=="function"&&typeof Object.defineProperty=="function")try{Ae=Object.defineProperty({},"length",{get:function(){throw $}}),$={},O(function(){throw 42},null,Ae)}catch(n){n!==$&&(O=null)}else O=null;var Bo=/^\s*class\b/,Fe=function(e){try{var i=wn.call(e);return Bo.test(i)}catch(o){return!1}},Re=function(e){try{return Fe(e)?!1:(wn.call(e),!0)}catch(i){return!1}},J=Object.prototype.toString,Ho="[object Object]",Vo="[object Function]",Zo="[object GeneratorFunction]",_o="[object HTMLAllCollection]",Go="[object HTML document.all class]",Ko="[object HTMLCollection]",Yo=typeof Symbol=="function"&&!!Symbol.toStringTag,Xo=!(0 in[,]),Oe=function(){return!1};typeof document=="object"&&(un=document.all,J.call(un)===J.call(document.all)&&(Oe=function(e){if((Xo||!e)&&(typeof e>"u"||typeof e=="object"))try{var i=J.call(e);return(i===_o||i===Go||i===Ko||i===Ho)&&e("")==null}catch(o){}return!1}));var un;function Qo(n){if(Oe(n))return!0;if(!n||typeof n!="function"&&typeof n!="object")return!1;try{O(n,null,Ae)}catch(e){if(e!==$)return!1}return!Fe(n)&&Re(n)}function qo(n){if(Oe(n))return!0;if(!n||typeof n!="function"&&typeof n!="object")return!1;if(Yo)return Re(n);if(Fe(n))return!1;var e=J.call(n);return e!==Vo&&e!==Zo&&!/^\[object HTML/.test(e)?!1:Re(n)}var b=O?Qo:qo;var L=class extends Error{constructor(e,i){super(e,i),this.name="CancelError"}},v=class extends Error{constructor(e,i,o){super((o!=null?o:"Unhandled rejection in cancelled promise.")+" Reason: "+$o(i),{cause:i}),this.promise=e,this.name="CancelledRejectionError"}},W=Symbol("barrier"),ke=Symbol("cancelImpl"),hn,pn=(hn=Symbol.species)!=null?hn:Symbol("speciesPolyfill"),H=class n extends Promise{constructor(e,i){let o,t;if(super((d,w)=>{o=d,t=w}),this.constructor[pn]!==Promise)throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property.");let r={promise:this,resolve:o,reject:t,get oncancelled(){return i!=null?i:null},set oncancelled(d){i=d!=null?d:void 0}},a={get root(){return a},resolving:!1,settled:!1};Object.defineProperties(this,{[W]:{configurable:!1,enumerable:!1,writable:!0,value:null},[ke]:{configurable:!1,enumerable:!1,writable:!1,value:Wn(r,a)}});let l=vn(r,a);try{e(bn(r,a),l)}catch(d){a.resolving||l(d)}}cancel(e){return new n(i=>{Promise.all([this[ke](new L("Promise cancelled.",{cause:e})),Jo(this)]).then(()=>i(),()=>i())})}cancelOn(e){return e.aborted?this.cancel(e.reason):e.addEventListener("abort",()=>{this.cancel(e.reason)},{capture:!0}),this}then(e,i,o){if(!(this instanceof n))throw new TypeError("CancellablePromise.prototype.then called on an invalid object.");if(b(e)||(e=fn),b(i)||(i=gn),e===fn&&i==gn)return new n(r=>r(this));let t={};return this[W]=t,new n((r,a)=>{super.then(l=>{var d;this[W]===t&&(this[W]=null),(d=t.resolve)==null||d.call(t);try{r(e(l))}catch(w){a(w)}},l=>{var d;this[W]===t&&(this[W]=null),(d=t.resolve)==null||d.call(t);try{r(i(l))}catch(w){a(w)}})},async r=>{try{return o==null?void 0:o(r)}finally{await this.cancel(r)}})}catch(e,i){return this.then(void 0,e,i)}finally(e,i){if(!(this instanceof n))throw new TypeError("CancellablePromise.prototype.finally called on an invalid object.");return b(e)?this.then(o=>n.resolve(e()).then(()=>o),o=>n.resolve(e()).then(()=>{throw o}),i):this.then(e,e,i)}static get[(W,ke,pn)](){return Promise}static all(e){let i=Array.from(e),o=i.length===0?n.resolve(i):new n((t,r)=>{Promise.all(i).then(t,r)},t=>ee(o,i,t));return o}static allSettled(e){let i=Array.from(e),o=i.length===0?n.resolve(i):new n((t,r)=>{Promise.allSettled(i).then(t,r)},t=>ee(o,i,t));return o}static any(e){let i=Array.from(e),o=i.length===0?n.resolve(i):new n((t,r)=>{Promise.any(i).then(t,r)},t=>ee(o,i,t));return o}static race(e){let i=Array.from(e),o=new n((t,r)=>{Promise.race(i).then(t,r)},t=>ee(o,i,t));return o}static cancel(e){let i=new n(()=>{});return i.cancel(e),i}static timeout(e,i){let o=new n(()=>{});return AbortSignal&&typeof AbortSignal=="function"&&AbortSignal.timeout&&typeof AbortSignal.timeout=="function"?AbortSignal.timeout(e).addEventListener("abort",()=>{o.cancel(i)}):setTimeout(()=>{o.cancel(i)},e),o}static sleep(e,i){return new n(o=>{setTimeout(()=>o(i),e)})}static reject(e){return new n((i,o)=>o(e))}static resolve(e){return e instanceof n?e:new n(i=>i(e))}static withResolvers(){let e={oncancelled:null};return e.promise=new n((i,o)=>{e.resolve=i,e.reject=o},i=>{var o;(o=e.oncancelled)==null||o.call(e,i)}),e}};function Wn(n,e){let i;return o=>{if(e.settled||(e.settled=!0,e.reason=o,n.reject(o),Promise.prototype.then.call(n.promise,void 0,t=>{if(t!==o)throw t})),!(!e.reason||!n.oncancelled))return i=new Promise(t=>{try{t(n.oncancelled(e.reason.cause))}catch(r){Promise.reject(new v(n.promise,r,"Unhandled exception in oncancelled callback."))}}).catch(t=>{Promise.reject(new v(n.promise,t,"Unhandled rejection in oncancelled callback."))}),n.oncancelled=null,i}}function bn(n,e){return i=>{if(!e.resolving){if(e.resolving=!0,i===n.promise){if(e.settled)return;e.settled=!0,n.reject(new TypeError("A promise cannot be resolved with itself."));return}if(i!=null&&(typeof i=="object"||typeof i=="function")){let o;try{o=i.then}catch(t){e.settled=!0,n.reject(t);return}if(b(o)){try{let a=i.cancel;if(b(a)){let l=d=>{Reflect.apply(a,i,[d])};e.reason?Wn(Ye(Ke({},n),{oncancelled:l}),e)(e.reason):n.oncancelled=l}}catch(a){}let t={root:e.root,resolving:!1,get settled(){return this.root.settled},set settled(a){this.root.settled=a},get reason(){return this.root.reason}},r=vn(n,t);try{Reflect.apply(o,i,[bn(n,t),r])}catch(a){r(a)}return}}e.settled||(e.settled=!0,n.resolve(i))}}}function vn(n,e){return i=>{if(!e.resolving)if(e.resolving=!0,e.settled){try{if(i instanceof L&&e.reason instanceof L&&Object.is(i.cause,e.reason.cause))return}catch(o){}Promise.reject(new v(n.promise,i))}else e.settled=!0,n.reject(i)}}function ee(n,e,i){let o=[];for(let t of e){let r;try{if(!b(t.then)||(r=t.cancel,!b(r)))continue}catch(l){continue}let a;try{a=Reflect.apply(r,t,[i])}catch(l){Promise.reject(new v(n,l,"Unhandled exception in cancel method."));continue}a&&o.push((a instanceof Promise?a:Promise.resolve(a)).catch(l=>{Promise.reject(new v(n,l,"Unhandled rejection in cancel method."))}))}return Promise.all(o)}function fn(n){return n}function gn(n){throw n}function $o(n){try{if(n instanceof Error||typeof n!="object"||n.toString!==Object.prototype.toString)return""+n}catch(e){}try{return JSON.stringify(n)}catch(e){}try{return Object.prototype.toString.call(n)}catch(e){}return""}function Jo(n){var i;let e=(i=n[W])!=null?i:{};return"promise"in e||Object.assign(e,k()),n[W]==null&&(e.resolve(),n[W]=e),e.promise}var k=Promise.withResolvers;k&&typeof k=="function"?k=k.bind(Promise):k=function(){let n,e;return{promise:new Promise((o,t)=>{n=o,e=t}),resolve:n,reject:e}};window._wails=window._wails||{};var et=u(m.Call),nt=u(m.CancelCall),V=new Map,it=0,ot=0,Le=class extends Error{constructor(e,i){super(e,i),this.name="RuntimeError"}};function tt(){let n;do n=Z();while(V.has(n));return n}function Ie(n){let e=tt(),i=H.withResolvers();V.set(e,{resolve:i.resolve,reject:i.reject});let o=et(it,Object.assign({"call-id":e},n)),t=!0;o.then(a=>{t=!1,V.delete(e),i.resolve(a)},a=>{t=!1,V.delete(e),i.reject(a)});let r=()=>(V.delete(e),nt(ot,{"call-id":e}).catch(a=>{}));return i.oncancelled=()=>t?r():o.then(r),i.promise}function rt(n,...e){return Ie({methodName:n,args:e})}function at(n,...e){return Ie({methodID:n,args:e})}var ze={};p(ze,{SetText:()=>dt,Text:()=>ct});var yn=u(m.Clipboard),st=0,lt=1;function dt(n){return yn(st,{text:n})}function ct(){return yn(lt)}var je={};p(je,{GetAll:()=>pt,GetCurrent:()=>gt,GetPrimary:()=>ft});var Ne=u(m.Screens),mt=0,ut=1,wt=2;function pt(){return Ne(mt)}function ft(){return Ne(ut)}function gt(){return Ne(wt)}var Ve={};p(Ve,{Device:()=>He,Haptics:()=>Be});var Dn=u(m.IOS),ht=0,Wt=1,Be;(e=>{function n(i="medium"){return Dn(ht,{style:i})}e.Impact=n})(Be||(Be={}));var He;(e=>{function n(){return Dn(Wt)}e.Info=n})(He||(He={}));window._wails=window._wails||{};window._wails.invoke=P;window._wails.clientId=I;window._wails.handlePlatformFileDrop=E.HandlePlatformFileDrop.bind(E);window._wails.handleDragEnter=tn;window._wails.handleDragLeave=rn;window._wails.handleDragOver=an;P("wails:runtime:ready");function Cn(n){return fetch(n,{method:"HEAD"}).then(e=>{if(e.ok){let i=document.createElement("script");i.src=n,document.head.appendChild(i)}}).catch(()=>{})}Cn("/wails/custom.js");export{xe as Application,ie as Browser,Ue as Call,L as CancelError,H as CancellablePromise,v as CancelledRejectionError,ze as Clipboard,re as Create,te as Dialogs,le as Events,ve as Flags,Ve as IOS,je as Screens,be as System,pe as WML,E as Window,I as clientId,Qe as getTransport,Cn as loadOptionalScript,m as objectNames,Xe as setTransport}; diff --git a/v3/internal/assetserver/bundledassets/runtime_dev.go b/v3/internal/assetserver/bundledassets/runtime_dev.go new file mode 100644 index 000000000..2c9621a66 --- /dev/null +++ b/v3/internal/assetserver/bundledassets/runtime_dev.go @@ -0,0 +1,8 @@ +//go:build !production + +package bundledassets + +import _ "embed" + +//go:embed runtime.debug.js +var RuntimeJS []byte diff --git a/v3/internal/assetserver/bundledassets/runtime_production.go b/v3/internal/assetserver/bundledassets/runtime_production.go new file mode 100644 index 000000000..9614a7b13 --- /dev/null +++ b/v3/internal/assetserver/bundledassets/runtime_production.go @@ -0,0 +1,8 @@ +//go:build production + +package bundledassets + +import _ "embed" + +//go:embed runtime.js +var RuntimeJS []byte diff --git a/v3/internal/assetserver/common.go b/v3/internal/assetserver/common.go new file mode 100644 index 000000000..a41139dff --- /dev/null +++ b/v3/internal/assetserver/common.go @@ -0,0 +1,66 @@ +package assetserver + +import ( + "context" + "fmt" + "log/slog" + "net/http" + "strings" +) + +const ( + HeaderHost = "Host" + HeaderContentType = "Content-Type" + HeaderContentLength = "Content-Length" + HeaderUserAgent = "User-Agent" + // TODO: Is this needed? + HeaderCacheControl = "Cache-Control" + HeaderUpgrade = "Upgrade" + + WailsUserAgentValue = "wails.io" +) + +type assetServerLogger struct{} + +var assetServerLoggerKey assetServerLogger + +// ServeFile writes the provided blob to rw as an HTTP 200 response, ensuring appropriate +// Content-Length and Content-Type headers are set. +// +// If the Content-Type header is not already present, ServeFile determines an appropriate +// MIME type from the filename and blob and sets the Content-Type header. It then writes +// the 200 status and the blob body to the response, returning any error encountered while +// writing the body. +func ServeFile(rw http.ResponseWriter, filename string, blob []byte) error { + header := rw.Header() + header.Set(HeaderContentLength, fmt.Sprintf("%d", len(blob))) + if mimeType := header.Get(HeaderContentType); mimeType == "" { + mimeType = GetMimetype(filename, blob) + header.Set(HeaderContentType, mimeType) + } + + rw.WriteHeader(http.StatusOK) + _, err := rw.Write(blob) + return err +} + +func isWebSocket(req *http.Request) bool { + upgrade := req.Header.Get(HeaderUpgrade) + return strings.EqualFold(upgrade, "websocket") +} + +func contextWithLogger(ctx context.Context, logger *slog.Logger) context.Context { + return context.WithValue(ctx, assetServerLoggerKey, logger) +} + +func logInfo(ctx context.Context, message string, args ...interface{}) { + if logger, _ := ctx.Value(assetServerLoggerKey).(*slog.Logger); logger != nil { + logger.Info(message, args...) + } +} + +func logError(ctx context.Context, message string, args ...interface{}) { + if logger, _ := ctx.Value(assetServerLoggerKey).(*slog.Logger); logger != nil { + logger.Error(message, args...) + } +} \ No newline at end of file diff --git a/v3/internal/assetserver/content_type_sniffer.go b/v3/internal/assetserver/content_type_sniffer.go new file mode 100644 index 000000000..fd51a6101 --- /dev/null +++ b/v3/internal/assetserver/content_type_sniffer.go @@ -0,0 +1,142 @@ +package assetserver + +import ( + "net/http" +) + +// newContentTypeSniffer creates a contentTypeSniffer that wraps the provided http.ResponseWriter. +// The returned sniffer does not allocate a close notification channel; it will be initialized lazily by CloseNotify. +func newContentTypeSniffer(rw http.ResponseWriter) *contentTypeSniffer { + return &contentTypeSniffer{ + rw: rw, + } +} + +type contentTypeSniffer struct { + rw http.ResponseWriter + prefix []byte + closeChannel chan bool // lazily allocated only if CloseNotify is called + status int + headerCommitted bool + headerWritten bool +} + +// Unwrap returns the wrapped [http.ResponseWriter] for use with [http.ResponseController]. +func (rw *contentTypeSniffer) Unwrap() http.ResponseWriter { + return rw.rw +} + +func (rw *contentTypeSniffer) Header() http.Header { + return rw.rw.Header() +} + +func (rw *contentTypeSniffer) Write(chunk []byte) (int, error) { + if !rw.headerCommitted { + rw.WriteHeader(http.StatusOK) + } + + if rw.headerWritten { + return rw.rw.Write(chunk) + } + + if len(chunk) == 0 { + return 0, nil + } + + // Cut away at most 512 bytes from chunk, and not less than 0. + cut := max(min(len(chunk), 512-len(rw.prefix)), 0) + if cut >= 512 { + // Avoid copying data if a full prefix is available on first non-zero write. + cut = len(chunk) + rw.prefix = chunk + chunk = nil + } else if cut > 0 { + // First write had less than 512 bytes -- copy data to the prefix buffer. + if rw.prefix == nil { + // Preallocate space for the prefix to be used for sniffing. + rw.prefix = make([]byte, 0, 512) + } + rw.prefix = append(rw.prefix, chunk[:cut]...) + chunk = chunk[cut:] + } + + if len(rw.prefix) < 512 { + return cut, nil + } + + if _, err := rw.complete(); err != nil { + return cut, err + } + + n, err := rw.rw.Write(chunk) + return cut + n, err +} + +func (rw *contentTypeSniffer) WriteHeader(code int) { + if rw.headerCommitted { + return + } + + rw.status = code + rw.headerCommitted = true + + if _, hasType := rw.Header()[HeaderContentType]; hasType { + rw.rw.WriteHeader(rw.status) + rw.headerWritten = true + } +} + +// sniff sniffs the content type from the stored prefix if necessary, +// then writes the header. +func (rw *contentTypeSniffer) sniff() { + if rw.headerWritten || !rw.headerCommitted { + return + } + + m := rw.Header() + if _, hasType := m[HeaderContentType]; !hasType { + m.Set(HeaderContentType, http.DetectContentType(rw.prefix)) + } + + rw.rw.WriteHeader(rw.status) + rw.headerWritten = true +} + +// complete sniffs the content type if necessary, writes the header +// and sends the data prefix that has been stored for sniffing. +// +// Whoever creates a contentTypeSniffer instance +// is responsible for calling complete after the nested handler has returned. +func (rw *contentTypeSniffer) complete() (n int, err error) { + rw.sniff() + + if rw.headerWritten && len(rw.prefix) > 0 { + n, err = rw.rw.Write(rw.prefix) + rw.prefix = nil + } + + return +} + +// CloseNotify implements the http.CloseNotifier interface. +// The channel is lazily allocated to avoid allocation overhead for requests +// that don't use this deprecated interface. +func (rw *contentTypeSniffer) CloseNotify() <-chan bool { + if rw.closeChannel == nil { + rw.closeChannel = make(chan bool, 1) + } + return rw.closeChannel +} + +func (rw *contentTypeSniffer) closeClient() { + if rw.closeChannel != nil { + rw.closeChannel <- true + } +} + +// Flush implements the http.Flusher interface. +func (rw *contentTypeSniffer) Flush() { + if f, ok := rw.rw.(http.Flusher); ok { + f.Flush() + } +} \ No newline at end of file diff --git a/v3/internal/assetserver/defaults/index.en.html b/v3/internal/assetserver/defaults/index.en.html new file mode 100644 index 000000000..72c0f567d --- /dev/null +++ b/v3/internal/assetserver/defaults/index.en.html @@ -0,0 +1,350 @@ + + + + + + Page Not Found - Wails + + + +
        + +
        + +
        + + +
        +
        +
        +
        ⚠️
        + Missing index.html file +
        +
        +

        No index.html file was found in the embedded assets. This page appears when the WebView window cannot find HTML content to display.

        +
          +
        • + 1 + + If you are using the Assets option in your application, ensure you have an index.html file in your project's embedded assets directory. +
          + View Example → +
          +//go:embed all:frontend/dist +var assets embed.FS + +func main() { + // ... + app := application.New(application.Options{ + // ... + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + }) + // ... +} +
          +
          +
          +
        • +
        • + 2 + If the file doesn't exist but should, verify that your build process is configured to correctly include the HTML file in the embedded assets directory. +
        • +
        • + 3 + + An alternative solution is to use the HTML option in the WebviewWindow Options. +
          + View Example → +
          +func main() { + + // ... + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + // ... + HTML: "<h1>Hello World!<h1>", + }) + // ... +} +
          +
          +
          +
        • +
        +
        + +
        + + +
        +
        +
        + + + +
        + + \ No newline at end of file diff --git a/v3/internal/assetserver/defaults/index.zh.html b/v3/internal/assetserver/defaults/index.zh.html new file mode 100644 index 000000000..45e5fb93c --- /dev/null +++ b/v3/internal/assetserver/defaults/index.zh.html @@ -0,0 +1,302 @@ + + + + + + 页面未找到 - Wails + + + +
        + +
        + +
        + + +
        +
        +
        +
        + 未找到 index.html 文件 +
        请按照以下步骤解决此问题
        +
        +
        +

        + 系统提示:在嵌入资源中未能找到 index.html 文件。 +
        + 不用担心,这个问题很容易解决。 +

        +
          +
        • + 1 + + 如果您在应用程序中使用了 Assets 选项,请确保您的项目嵌入资源目录中有 index.html 文件。 +
          + 查看示例 ➜ +
          +//go:embed all:frontend/dist +var assets embed.FS + +func main() { + // ... + app := application.New(application.Options{ + // ... + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + }) + // ... +} +
          +
          +
          +
        • +
        • + 2 + 如果文件应该存在但不存在,请验证您的构建过程是否配置正确,以确保 HTML 文件包含在嵌入资源目录中。 +
        • +
        • + 3 + + 另一种解决方案是在 WebviewWindow 选项中使用 HTML 选项。 +
          + 查看示例 ➜ +
          +func main() { + + // ... + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + // ... + HTML: "<h1>Hello World!<h1>", + }) + // ... +} +
          +
          +
          +
        • +
        +
        + +
        + + +
        +
        +
        + + +
        +

        需要帮助?查看我们的文档或加入我们的社区

        +
        +
        + + diff --git a/v3/internal/assetserver/fallback_response_writer.go b/v3/internal/assetserver/fallback_response_writer.go new file mode 100644 index 000000000..c26d3cc52 --- /dev/null +++ b/v3/internal/assetserver/fallback_response_writer.go @@ -0,0 +1,80 @@ +package assetserver + +import ( + "maps" + "net/http" +) + +// fallbackResponseWriter wraps a [http.ResponseWriter]. +// If the main handler returns status code 404, +// its response is discarded +// and the request is forwarded to the fallback handler. +type fallbackResponseWriter struct { + rw http.ResponseWriter + req *http.Request + fallback http.Handler + + header http.Header + headerWritten bool + complete bool +} + +// Unwrap returns the wrapped [http.ResponseWriter] for use with [http.ResponseController]. +func (fw *fallbackResponseWriter) Unwrap() http.ResponseWriter { + return fw.rw +} + +func (fw *fallbackResponseWriter) Header() http.Header { + if fw.header == nil { + // Preserve original header in case we get a 404 response. + fw.header = fw.rw.Header().Clone() + } + return fw.header +} + +func (fw *fallbackResponseWriter) Write(chunk []byte) (int, error) { + if fw.complete { + // Fallback triggered, discard further writes. + return len(chunk), nil + } + + if !fw.headerWritten { + fw.WriteHeader(http.StatusOK) + } + + return fw.rw.Write(chunk) +} + +func (fw *fallbackResponseWriter) WriteHeader(statusCode int) { + if fw.headerWritten { + return + } + fw.headerWritten = true + + if statusCode == http.StatusNotFound { + // Protect fallback header from external modifications. + if fw.header == nil { + fw.header = fw.rw.Header().Clone() + } + + // Invoke fallback handler. + fw.complete = true + fw.fallback.ServeHTTP(fw.rw, fw.req) + return + } + + if fw.header != nil { + // Apply headers and forward original map to the main handler. + maps.Copy(fw.rw.Header(), fw.header) + fw.header = fw.rw.Header() + } + + fw.rw.WriteHeader(statusCode) +} + +// Flush implements the http.Flusher interface. +func (rw *fallbackResponseWriter) Flush() { + if f, ok := rw.rw.(http.Flusher); ok { + f.Flush() + } +} diff --git a/v3/internal/assetserver/fs.go b/v3/internal/assetserver/fs.go new file mode 100644 index 000000000..20ccf3fab --- /dev/null +++ b/v3/internal/assetserver/fs.go @@ -0,0 +1,76 @@ +package assetserver + +import ( + "embed" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" +) + +// findEmbedRootPath finds the root path in the embed FS. It's the directory which contains all the files. +func findEmbedRootPath(fileSystem embed.FS) (string, error) { + stopErr := errors.New("files or multiple dirs found") + + fPath := "" + err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + fPath = path + if entries, dErr := fs.ReadDir(fileSystem, path); dErr != nil { + return dErr + } else if len(entries) <= 1 { + return nil + } + } + + return stopErr + }) + + if err != nil && !errors.Is(err, stopErr) { + return "", err + } + + return fPath, nil +} + +func findPathToFile(fileSystem fs.FS, file string) (string, error) { + stat, _ := fs.Stat(fileSystem, file) + if stat != nil { + return ".", nil + } + var indexFiles []string + err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if strings.HasSuffix(path, file) { + indexFiles = append(indexFiles, path) + } + return nil + }) + if err != nil { + return "", err + } + + if len(indexFiles) > 1 { + selected := indexFiles[0] + for _, f := range indexFiles { + if len(f) < len(selected) { + selected = f + } + } + path, _ := filepath.Split(selected) + return path, nil + } + if len(indexFiles) > 0 { + path, _ := filepath.Split(indexFiles[0]) + return path, nil + } + return "", fmt.Errorf("%s: %w", file, os.ErrNotExist) +} diff --git a/v3/internal/assetserver/middleware.go b/v3/internal/assetserver/middleware.go new file mode 100644 index 000000000..b3826ab7d --- /dev/null +++ b/v3/internal/assetserver/middleware.go @@ -0,0 +1,20 @@ +package assetserver + +import ( + "net/http" +) + +// Middleware defines a HTTP middleware that can be applied to the AssetServer. +// The handler passed as next is the next handler in the chain. One can decide to call the next handler +// or implement a specialized handling. +type Middleware func(next http.Handler) http.Handler + +// ChainMiddleware allows chaining multiple middlewares to one middleware. +func ChainMiddleware(middleware ...Middleware) Middleware { + return func(h http.Handler) http.Handler { + for i := len(middleware) - 1; i >= 0; i-- { + h = middleware[i](h) + } + return h + } +} diff --git a/v3/internal/assetserver/mimecache.go b/v3/internal/assetserver/mimecache.go new file mode 100644 index 000000000..9034a4a0f --- /dev/null +++ b/v3/internal/assetserver/mimecache.go @@ -0,0 +1,116 @@ +package assetserver + +import ( + "net/http" + "path/filepath" + "sync" +) + +var ( + // mimeCache uses sync.Map for better concurrent read performance + // since reads are far more common than writes + mimeCache sync.Map + + // mimeTypesByExt maps file extensions to MIME types for common web formats. + // This approach is preferred over content-based detection because: + // 1. Extension-based lookup is O(1) vs O(n) content scanning + // 2. Web assets typically have correct extensions + // 3. stdlib's http.DetectContentType handles remaining cases adequately + // 4. Saves ~208KB binary size by not using github.com/wailsapp/mimetype + mimeTypesByExt = map[string]string{ + // HTML + ".htm": "text/html; charset=utf-8", + ".html": "text/html; charset=utf-8", + + // CSS/JS + ".css": "text/css; charset=utf-8", + ".js": "text/javascript; charset=utf-8", + ".mjs": "text/javascript; charset=utf-8", + ".ts": "application/x-typescript; charset=utf-8", + ".tsx": "application/x-typescript; charset=utf-8", + ".jsx": "text/javascript; charset=utf-8", + + // Data formats + ".json": "application/json", + ".xml": "text/xml; charset=utf-8", + ".yaml": "text/yaml; charset=utf-8", + ".yml": "text/yaml; charset=utf-8", + ".toml": "text/toml; charset=utf-8", + + // Images + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".webp": "image/webp", + ".avif": "image/avif", + ".svg": "image/svg+xml", + ".ico": "image/x-icon", + ".bmp": "image/bmp", + ".tiff": "image/tiff", + ".tif": "image/tiff", + + // Fonts + ".woff": "font/woff", + ".woff2": "font/woff2", + ".ttf": "font/ttf", + ".otf": "font/otf", + ".eot": "application/vnd.ms-fontobject", + + // Audio + ".mp3": "audio/mpeg", + ".wav": "audio/wav", + ".ogg": "audio/ogg", + ".m4a": "audio/mp4", + ".aac": "audio/aac", + ".flac": "audio/flac", + ".opus": "audio/opus", + + // Video + ".mp4": "video/mp4", + ".webm": "video/webm", + ".ogv": "video/ogg", + ".mov": "video/quicktime", + ".avi": "video/x-msvideo", + ".mkv": "video/x-matroska", + ".m4v": "video/mp4", + + // Documents + ".pdf": "application/pdf", + ".txt": "text/plain; charset=utf-8", + ".md": "text/markdown; charset=utf-8", + + // Archives + ".zip": "application/zip", + ".gz": "application/gzip", + ".tar": "application/x-tar", + + // WebAssembly + ".wasm": "application/wasm", + + // Source maps + ".map": "application/json", + } +) + +// "application/octet-stream". +func GetMimetype(filename string, data []byte) string { + // Fast path: check extension map first (no lock needed) + if result := mimeTypesByExt[filepath.Ext(filename)]; result != "" { + return result + } + + // Check cache (lock-free read) + if cached, ok := mimeCache.Load(filename); ok { + return cached.(string) + } + + // Slow path: use stdlib content-based detection and cache + result := http.DetectContentType(data) + if result == "" { + result = "application/octet-stream" + } + + mimeCache.Store(filename, result) + return result +} \ No newline at end of file diff --git a/v3/internal/assetserver/mimecache_test.go b/v3/internal/assetserver/mimecache_test.go new file mode 100644 index 000000000..48e5943fa --- /dev/null +++ b/v3/internal/assetserver/mimecache_test.go @@ -0,0 +1,46 @@ +package assetserver + +import ( + "testing" +) + +func TestGetMimetype(t *testing.T) { + type args struct { + filename string + data []byte + } + bomUTF8 := []byte{0xef, 0xbb, 0xbf} + var emptyMsg []byte + css := []byte("body{margin:0;padding:0;background-color:#d579b2}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;background-color:#ededed}#nav{padding:30px}#nav a{font-weight:700;color:#2c\n3e50}#nav a.router-link-exact-active{color:#42b983}.hello[data-v-4e26ad49]{margin:10px 0}") + html := []byte("title") + bomHtml := append(bomUTF8, html...) + svg := []byte("") + svgWithComment := append([]byte(""), svg...) + svgWithCommentAndControlChars := append([]byte(" \r\n "), svgWithComment...) + svgWithBomCommentAndControlChars := append(bomUTF8, append([]byte(" \r\n "), svgWithComment...)...) + + tests := []struct { + name string + args args + want string + }{ + {"nil data", args{"nil.svg", nil}, "image/svg+xml"}, + {"empty data", args{"empty.html", emptyMsg}, "text/html; charset=utf-8"}, + {"css", args{"test.css", css}, "text/css; charset=utf-8"}, + {"js", args{"test.js", []byte("let foo = 'bar'; console.log(foo);")}, "text/javascript; charset=utf-8"}, + {"mjs", args{"test.mjs", []byte("let foo = 'bar'; console.log(foo);")}, "text/javascript; charset=utf-8"}, + {"html-utf8", args{"test_utf8.html", html}, "text/html; charset=utf-8"}, + {"html-bom-utf8", args{"test_bom_utf8.html", bomHtml}, "text/html; charset=utf-8"}, + {"svg", args{"test.svg", svg}, "image/svg+xml"}, + {"svg-w-comment", args{"test_comment.svg", svgWithComment}, "image/svg+xml"}, + {"svg-w-control-comment", args{"test_control_comment.svg", svgWithCommentAndControlChars}, "image/svg+xml"}, + {"svg-w-bom-control-comment", args{"test_bom_control_comment.svg", svgWithBomCommentAndControlChars}, "image/svg+xml"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetMimetype(tt.args.filename, tt.args.data); got != tt.want { + t.Errorf("GetMimetype() = '%v', want '%v'", got, tt.want) + } + }) + } +} diff --git a/v3/internal/assetserver/mimetype_stdlib_test.go b/v3/internal/assetserver/mimetype_stdlib_test.go new file mode 100644 index 000000000..18a20131d --- /dev/null +++ b/v3/internal/assetserver/mimetype_stdlib_test.go @@ -0,0 +1,277 @@ +package assetserver + +import ( + "net/http" + "path/filepath" + "strings" + "testing" +) + +// TestMimeTypeDetection_WebFormats validates that extension-based detection +// plus stdlib fallback correctly handles all common web asset formats. +// This test ensures we can safely remove the github.com/wailsapp/mimetype dependency. +func TestMimeTypeDetection_WebFormats(t *testing.T) { + // webMimeTests covers all common web formats that Wails applications typically serve + webMimeTests := []struct { + name string + filename string + data []byte + wantPrefix string // Use prefix matching since charset may vary + }{ + // === TEXT FORMATS (extension-based) === + {"HTML file", "index.html", []byte(""), "text/html"}, + {"HTM file", "page.htm", []byte(""), "text/html"}, + {"CSS file", "styles.css", []byte(".class { color: red; }"), "text/css"}, + {"JavaScript file", "app.js", []byte("function test() {}"), "text/javascript"}, + {"ES Module file", "module.mjs", []byte("export default {}"), "text/javascript"}, + {"JSON file", "data.json", []byte(`{"key": "value"}`), "application/json"}, + {"XML file", "data.xml", []byte(""), "text/xml"}, + + // === IMAGE FORMATS (extension-based) === + {"PNG file", "image.png", pngData, "image/png"}, + {"JPEG file", "photo.jpg", jpegData, "image/jpeg"}, + {"JPEG alt ext", "photo.jpeg", jpegData, "image/jpeg"}, + {"GIF file", "anim.gif", gifData, "image/gif"}, + {"WebP file", "image.webp", webpData, "image/webp"}, + {"AVIF file", "image.avif", avifData, "image/avif"}, + {"SVG file", "icon.svg", []byte(""), "image/svg+xml"}, + {"PDF file", "doc.pdf", pdfData, "application/pdf"}, + + // === WASM (extension-based) === + {"WASM file", "app.wasm", wasmData, "application/wasm"}, + + // === FONT FORMATS (need detection or extension map) === + {"WOFF file", "font.woff", woffData, "font/woff"}, + {"WOFF2 file", "font.woff2", woff2Data, "font/woff2"}, + {"TTF file", "font.ttf", ttfData, "font/ttf"}, + {"OTF file", "font.otf", otfData, "font/otf"}, + {"EOT file", "font.eot", eotData, "application/vnd.ms-fontobject"}, + + // === AUDIO/VIDEO (common web formats) === + {"MP3 file", "audio.mp3", mp3Data, "audio/mpeg"}, + {"MP4 file", "video.mp4", mp4Data, "video/mp4"}, + {"WebM file", "video.webm", webmData, "video/webm"}, + {"OGG file", "audio.ogg", oggData, "audio/ogg"}, + + // === ARCHIVES (sometimes served by web apps) === + {"ZIP file", "archive.zip", zipData, "application/zip"}, + {"GZIP file", "data.gz", gzipData, "application/"}, + + // === SOURCE MAPS (common in dev mode) === + {"Source map", "app.js.map", []byte(`{"version":3}`), "application/json"}, + + // === ICO (favicon) === + {"ICO file", "favicon.ico", icoData, "image/"}, + + // === FALLBACK TESTS === + {"Unknown binary", "data.bin", []byte{0x00, 0x01, 0x02, 0x03}, "application/octet-stream"}, + {"Plain text (no ext)", "readme", []byte("Hello World"), "text/plain"}, + } + + for _, tt := range webMimeTests { + t.Run(tt.name, func(t *testing.T) { + got := getMimeTypeStdlib(tt.filename, tt.data) + if !hasPrefix(got, tt.wantPrefix) { + t.Errorf("getMimeTypeStdlib(%q) = %q, want prefix %q", tt.filename, got, tt.wantPrefix) + } + }) + } +} + +// getMimeTypeStdlib is the proposed replacement that uses only stdlib +func getMimeTypeStdlib(filename string, data []byte) string { + // Fast path: check extension map first + if result := extMimeTypes[filepath.Ext(filename)]; result != "" { + return result + } + + // Fallback to stdlib content-based detection + result := http.DetectContentType(data) + if result == "" { + result = "application/octet-stream" + } + return result +} + +// extMimeTypes is an expanded map covering all common web formats +// This replaces the need for the mimetype library for web assets +var extMimeTypes = map[string]string{ + // HTML + ".htm": "text/html; charset=utf-8", + ".html": "text/html; charset=utf-8", + + // CSS/JS + ".css": "text/css; charset=utf-8", + ".js": "text/javascript; charset=utf-8", + ".mjs": "text/javascript; charset=utf-8", + ".ts": "application/x-typescript; charset=utf-8", + ".tsx": "application/x-typescript; charset=utf-8", + ".jsx": "text/javascript; charset=utf-8", + + // Data formats + ".json": "application/json", + ".xml": "text/xml; charset=utf-8", + ".yaml": "text/yaml; charset=utf-8", + ".yml": "text/yaml; charset=utf-8", + ".toml": "text/toml; charset=utf-8", + + // Images + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".webp": "image/webp", + ".avif": "image/avif", + ".svg": "image/svg+xml", + ".ico": "image/x-icon", + ".bmp": "image/bmp", + ".tiff": "image/tiff", + ".tif": "image/tiff", + + // Fonts + ".woff": "font/woff", + ".woff2": "font/woff2", + ".ttf": "font/ttf", + ".otf": "font/otf", + ".eot": "application/vnd.ms-fontobject", + + // Audio + ".mp3": "audio/mpeg", + ".wav": "audio/wav", + ".ogg": "audio/ogg", + ".m4a": "audio/mp4", + ".aac": "audio/aac", + ".flac": "audio/flac", + ".opus": "audio/opus", + + // Video + ".mp4": "video/mp4", + ".webm": "video/webm", + ".ogv": "video/ogg", + ".mov": "video/quicktime", + ".avi": "video/x-msvideo", + ".mkv": "video/x-matroska", + ".m4v": "video/mp4", + + // Documents + ".pdf": "application/pdf", + ".txt": "text/plain; charset=utf-8", + ".md": "text/markdown; charset=utf-8", + + // Archives + ".zip": "application/zip", + ".gz": "application/gzip", + ".tar": "application/x-tar", + + // WebAssembly + ".wasm": "application/wasm", + + // Source maps + ".map": "application/json", +} + +func hasPrefix(s, prefix string) bool { + return len(s) >= len(prefix) && s[:len(prefix)] == prefix +} + +// Magic bytes for various formats +var ( + // PNG: 89 50 4E 47 0D 0A 1A 0A + pngData = []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D} + + // JPEG: FF D8 FF + jpegData = []byte{0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46} + + // GIF: 47 49 46 38 + gifData = []byte{0x47, 0x49, 0x46, 0x38, 0x39, 0x61} + + // WebP: 52 49 46 46 ... 57 45 42 50 + webpData = []byte{0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50} + + // AVIF: ... ftypavif or ftypavis + avifData = []byte{0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66} + + // PDF: 25 50 44 46 + pdfData = []byte{0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E} + + // WASM: 00 61 73 6D + wasmData = []byte{0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00} + + // WOFF: 77 4F 46 46 + woffData = []byte{0x77, 0x4F, 0x46, 0x46, 0x00, 0x01, 0x00, 0x00} + + // WOFF2: 77 4F 46 32 + woff2Data = []byte{0x77, 0x4F, 0x46, 0x32, 0x00, 0x01, 0x00, 0x00} + + // TTF: 00 01 00 00 + ttfData = []byte{0x00, 0x01, 0x00, 0x00, 0x00} + + // OTF: 4F 54 54 4F (OTTO) + otfData = []byte{0x4F, 0x54, 0x54, 0x4F, 0x00} + + // EOT: varies, but starts with size bytes then magic + eotData = []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00} + + // MP3: FF FB or FF FA or ID3 + mp3Data = []byte{0xFF, 0xFB, 0x90, 0x00} + + // MP4: ... ftyp + mp4Data = []byte{0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6F, 0x6D} + + // WebM: 1A 45 DF A3 (EBML header) + webmData = []byte{0x1A, 0x45, 0xDF, 0xA3} + + // OGG: 4F 67 67 53 + oggData = []byte{0x4F, 0x67, 0x67, 0x53, 0x00, 0x02} + + // ZIP: 50 4B 03 04 + zipData = []byte{0x50, 0x4B, 0x03, 0x04} + + // GZIP: 1F 8B + gzipData = []byte{0x1F, 0x8B, 0x08} + + // ICO: 00 00 01 00 + icoData = []byte{0x00, 0x00, 0x01, 0x00, 0x01, 0x00} +) + +// TestMimeTypeExtensionMapCompleteness checks that all extensions in the +// original mimeTypesByExt are covered by the expanded extMimeTypes +func TestMimeTypeExtensionMapCompleteness(t *testing.T) { + for ext, mime := range mimeTypesByExt { + if newMime, ok := extMimeTypes[ext]; !ok { + t.Errorf("extension %q missing from extMimeTypes (was: %q)", ext, mime) + } else if newMime != mime { + // Allow differences as long as they're equivalent (compare base MIME type) + mimeBase := mime + if idx := strings.Index(mime, ";"); idx > 0 { + mimeBase = mime[:idx] + } + if !hasPrefix(newMime, mimeBase) { + t.Logf("extension %q changed: %q -> %q (verify this is correct)", ext, mime, newMime) + } + } + } +} + +// BenchmarkMimeType_StdlibOnly benchmarks the stdlib-only implementation +func BenchmarkMimeType_StdlibOnly(b *testing.B) { + testCases := []struct { + name string + filename string + data []byte + }{ + {"ExtHit_JS", "app.js", []byte("function() {}")}, + {"ExtHit_CSS", "styles.css", []byte(".class { }")}, + {"ExtHit_PNG", "image.png", pngData}, + {"ExtMiss_Binary", "data.bin", []byte{0x00, 0x01, 0x02}}, + {"ContentDetect_PNG", "unknown", pngData}, + } + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + for b.Loop() { + _ = getMimeTypeStdlib(tc.filename, tc.data) + } + }) + } +} diff --git a/v3/internal/assetserver/options.go b/v3/internal/assetserver/options.go new file mode 100644 index 000000000..ae0570880 --- /dev/null +++ b/v3/internal/assetserver/options.go @@ -0,0 +1,38 @@ +package assetserver + +import ( + "errors" + "log/slog" + "net/http" +) + +// Options defines the configuration of the AssetServer. +type Options struct { + // Handler which serves all the content to the WebView. + Handler http.Handler + + // Middleware is a HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default + // request handler dynamically, e.g. implement specialized Routing etc. + // The Middleware is called to build a new `http.Handler` used by the AssetSever and it also receives the default + // handler used by the AssetServer as an argument. + // + // This middleware injects itself before any of Wails internal middlewares. + // + // If not defined, the default AssetServer request chain is executed. + // + // Multiple Middlewares can be chained together with: + // ChainMiddleware(middleware ...Middleware) Middleware + Middleware Middleware + + // Logger is the logger used by the AssetServer. If not defined, no logging will be done. + Logger *slog.Logger +} + +// Validate the options +func (o Options) Validate() error { + if o.Handler == nil && o.Middleware == nil { + return errors.New("AssetServer options invalid: either Handler or Middleware must be set") + } + + return nil +} diff --git a/v3/internal/assetserver/ringqueue.go b/v3/internal/assetserver/ringqueue.go new file mode 100644 index 000000000..b94e7cd5c --- /dev/null +++ b/v3/internal/assetserver/ringqueue.go @@ -0,0 +1,101 @@ +// Code from https://github.com/erikdubbelboer/ringqueue +/* +The MIT License (MIT) + +Copyright (c) 2015 Erik Dubbelboer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package assetserver + +type ringqueue[T any] struct { + nodes []T + head int + tail int + cnt int + + minSize int +} + +func newRingqueue[T any](minSize uint) *ringqueue[T] { + if minSize < 2 { + minSize = 2 + } + return &ringqueue[T]{ + nodes: make([]T, minSize), + minSize: int(minSize), + } +} + +func (q *ringqueue[T]) resize(n int) { + nodes := make([]T, n) + if q.head < q.tail { + copy(nodes, q.nodes[q.head:q.tail]) + } else { + copy(nodes, q.nodes[q.head:]) + copy(nodes[len(q.nodes)-q.head:], q.nodes[:q.tail]) + } + + q.tail = q.cnt % n + q.head = 0 + q.nodes = nodes +} + +func (q *ringqueue[T]) Add(i T) { + if q.cnt == len(q.nodes) { + // Also tested a grow rate of 1.5, see: http://stackoverflow.com/questions/2269063/buffer-growth-strategy + // In Go this resulted in a higher memory usage. + q.resize(q.cnt * 2) + } + q.nodes[q.tail] = i + q.tail = (q.tail + 1) % len(q.nodes) + q.cnt++ +} + +func (q *ringqueue[T]) Peek() (T, bool) { + if q.cnt == 0 { + var none T + return none, false + } + return q.nodes[q.head], true +} + +func (q *ringqueue[T]) Remove() (T, bool) { + if q.cnt == 0 { + var none T + return none, false + } + i := q.nodes[q.head] + q.head = (q.head + 1) % len(q.nodes) + q.cnt-- + + if n := len(q.nodes) / 2; n > q.minSize && q.cnt <= n { + q.resize(n) + } + + return i, true +} + +func (q *ringqueue[T]) Cap() int { + return cap(q.nodes) +} + +func (q *ringqueue[T]) Len() int { + return q.cnt +} diff --git a/v3/internal/assetserver/webview/request.go b/v3/internal/assetserver/webview/request.go new file mode 100644 index 000000000..18ff29890 --- /dev/null +++ b/v3/internal/assetserver/webview/request.go @@ -0,0 +1,17 @@ +package webview + +import ( + "io" + "net/http" +) + +type Request interface { + URL() (string, error) + Method() (string, error) + Header() (http.Header, error) + Body() (io.ReadCloser, error) + + Response() ResponseWriter + + Close() error +} diff --git a/v3/internal/assetserver/webview/request_android.go b/v3/internal/assetserver/webview/request_android.go new file mode 100644 index 000000000..aabe2aacb --- /dev/null +++ b/v3/internal/assetserver/webview/request_android.go @@ -0,0 +1,102 @@ +//go:build android + +package webview + +import ( + "bytes" + "io" + "net/http" +) + +// Request interface for Android asset requests +// On Android, requests are handled via JNI from Java's WebViewAssetLoader + +// androidRequest implements the Request interface for Android +type androidRequest struct { + url string + method string + headers http.Header + body io.ReadCloser + rw *androidResponseWriter +} + +// NewRequestFromJNI creates a new request from JNI parameters +func NewRequestFromJNI(url string, method string, headersJSON string) Request { + return &androidRequest{ + url: url, + method: method, + headers: http.Header{}, + body: http.NoBody, + } +} + +func (r *androidRequest) URL() (string, error) { + return r.url, nil +} + +func (r *androidRequest) Method() (string, error) { + return r.method, nil +} + +func (r *androidRequest) Header() (http.Header, error) { + return r.headers, nil +} + +func (r *androidRequest) Body() (io.ReadCloser, error) { + return r.body, nil +} + +func (r *androidRequest) Response() ResponseWriter { + if r.rw == nil { + r.rw = &androidResponseWriter{} + } + return r.rw +} + +func (r *androidRequest) Close() error { + if r.body != nil { + return r.body.Close() + } + return nil +} + +// androidResponseWriter implements ResponseWriter for Android +type androidResponseWriter struct { + statusCode int + headers http.Header + body bytes.Buffer + finished bool +} + +func (w *androidResponseWriter) Header() http.Header { + if w.headers == nil { + w.headers = http.Header{} + } + return w.headers +} + +func (w *androidResponseWriter) Write(data []byte) (int, error) { + return w.body.Write(data) +} + +func (w *androidResponseWriter) WriteHeader(statusCode int) { + w.statusCode = statusCode +} + +func (w *androidResponseWriter) Finish() error { + w.finished = true + return nil +} + +// Code returns the HTTP status code of the response +func (w *androidResponseWriter) Code() int { + if w.statusCode == 0 { + return 200 + } + return w.statusCode +} + +// GetResponseData returns the response data for JNI +func (w *androidResponseWriter) GetResponseData() []byte { + return w.body.Bytes() +} diff --git a/v3/internal/assetserver/webview/request_darwin.go b/v3/internal/assetserver/webview/request_darwin.go new file mode 100644 index 000000000..dd2d0232a --- /dev/null +++ b/v3/internal/assetserver/webview/request_darwin.go @@ -0,0 +1,250 @@ +//go:build darwin && !ios + +package webview + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework WebKit + +#import +#import +#include + +static void URLSchemeTaskRetain(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + [urlSchemeTask retain]; +} + +static void URLSchemeTaskRelease(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + [urlSchemeTask release]; +} + +static const char * URLSchemeTaskRequestURL(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + return [urlSchemeTask.request.URL.absoluteString UTF8String]; + } +} + +static const char * URLSchemeTaskRequestMethod(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + return [urlSchemeTask.request.HTTPMethod UTF8String]; + } +} + +static const char * URLSchemeTaskRequestHeadersJSON(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + NSData *headerData = [NSJSONSerialization dataWithJSONObject: urlSchemeTask.request.allHTTPHeaderFields options:0 error: nil]; + if (!headerData) { + return nil; + } + + NSString* headerString = [[[NSString alloc] initWithData:headerData encoding:NSUTF8StringEncoding] autorelease]; + const char * headerJSON = [headerString UTF8String]; + + return strdup(headerJSON); + } +} + +static bool URLSchemeTaskRequestBodyBytes(void *wkUrlSchemeTask, const void **body, int *bodyLen) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + if (!urlSchemeTask.request.HTTPBody) { + return false; + } + + *body = urlSchemeTask.request.HTTPBody.bytes; + *bodyLen = urlSchemeTask.request.HTTPBody.length; + return true; + } +} + +static bool URLSchemeTaskRequestBodyStreamOpen(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + if (!urlSchemeTask.request.HTTPBodyStream) { + return false; + } + + [urlSchemeTask.request.HTTPBodyStream open]; + return true; + } +} + +static void URLSchemeTaskRequestBodyStreamClose(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + if (!urlSchemeTask.request.HTTPBodyStream) { + return; + } + + [urlSchemeTask.request.HTTPBodyStream close]; + } +} + +static int URLSchemeTaskRequestBodyStreamRead(void *wkUrlSchemeTask, void *buf, int bufLen) { + id urlSchemeTask = (id) wkUrlSchemeTask; + + @autoreleasepool { + NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream; + if (!stream) { + return -2; + } + + NSStreamStatus status = stream.streamStatus; + if (status == NSStreamStatusAtEnd || !stream.hasBytesAvailable) { + return 0; + } else if (status != NSStreamStatusOpen) { + return -3; + } + + return [stream read:buf maxLength:bufLen]; + } +} +*/ +import "C" + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" + "unsafe" + + "encoding/json" +) + +// NewRequest creates as new WebViewRequest based on a pointer to an `id` +func NewRequest(wkURLSchemeTask unsafe.Pointer) Request { + C.URLSchemeTaskRetain(wkURLSchemeTask) + return newRequestFinalizer(&request{task: wkURLSchemeTask}) +} + +var _ Request = &request{} + +type request struct { + task unsafe.Pointer + + header http.Header + body io.ReadCloser + rw *responseWriter +} + +func (r *request) URL() (string, error) { + return C.GoString(C.URLSchemeTaskRequestURL(r.task)), nil +} + +func (r *request) Method() (string, error) { + return C.GoString(C.URLSchemeTaskRequestMethod(r.task)), nil +} + +func (r *request) Header() (http.Header, error) { + if r.header != nil { + return r.header, nil + } + + header := http.Header{} + if cHeaders := C.URLSchemeTaskRequestHeadersJSON(r.task); cHeaders != nil { + if headers := C.GoString(cHeaders); headers != "" { + var h map[string]string + if err := json.Unmarshal([]byte(headers), &h); err != nil { + return nil, fmt.Errorf("unable to unmarshal request headers: %s", err) + } + + for k, v := range h { + header.Add(k, v) + } + } + C.free(unsafe.Pointer(cHeaders)) + } + r.header = header + return header, nil +} + +func (r *request) Body() (io.ReadCloser, error) { + if r.body != nil { + return r.body, nil + } + + var body unsafe.Pointer + var bodyLen C.int + if C.URLSchemeTaskRequestBodyBytes(r.task, &body, &bodyLen) { + if body != nil && bodyLen > 0 { + r.body = io.NopCloser(bytes.NewReader(C.GoBytes(body, bodyLen))) + } else { + r.body = http.NoBody + } + } else if C.URLSchemeTaskRequestBodyStreamOpen(r.task) { + r.body = &requestBodyStreamReader{task: r.task} + } + + return r.body, nil +} + +func (r *request) Response() ResponseWriter { + if r.rw != nil { + return r.rw + } + + r.rw = &responseWriter{r: r} + return r.rw +} + +func (r *request) Close() error { + var err error + if r.body != nil { + err = r.body.Close() + } + r.Response().Finish() + C.URLSchemeTaskRelease(r.task) + return err +} + +var _ io.ReadCloser = &requestBodyStreamReader{} + +type requestBodyStreamReader struct { + task unsafe.Pointer + closed bool +} + +// Read implements io.Reader +func (r *requestBodyStreamReader) Read(p []byte) (n int, err error) { + var content unsafe.Pointer + var contentLen int + if p != nil { + content = unsafe.Pointer(&p[0]) + contentLen = len(p) + } + + res := C.URLSchemeTaskRequestBodyStreamRead(r.task, content, C.int(contentLen)) + if res > 0 { + return int(res), nil + } + + switch res { + case 0: + return 0, io.EOF + case -1: + return 0, errors.New("body: stream error") + case -2: + return 0, errors.New("body: no stream defined") + case -3: + return 0, io.ErrClosedPipe + default: + return 0, fmt.Errorf("body: unknown error %d", res) + } +} + +func (r *requestBodyStreamReader) Close() error { + if r.closed { + return nil + } + r.closed = true + + C.URLSchemeTaskRequestBodyStreamClose(r.task) + return nil +} diff --git a/v3/internal/assetserver/webview/request_finalizer.go b/v3/internal/assetserver/webview/request_finalizer.go new file mode 100644 index 000000000..6a8c6a928 --- /dev/null +++ b/v3/internal/assetserver/webview/request_finalizer.go @@ -0,0 +1,40 @@ +package webview + +import ( + "runtime" + "sync/atomic" +) + +var _ Request = &requestFinalizer{} + +type requestFinalizer struct { + Request + closed int32 +} + +// newRequestFinalizer returns a request with a runtime finalizer to make sure it will be closed from the finalizer +// if it has not been already closed. +// It also makes sure Close() of the wrapping request is only called once. +func newRequestFinalizer(r Request) Request { + rf := &requestFinalizer{Request: r} + // Make sure to async release since it might block the finalizer goroutine for a longer period + runtime.SetFinalizer(rf, func(obj *requestFinalizer) { rf.close(true) }) + return rf +} + +func (r *requestFinalizer) Close() error { + return r.close(false) +} + +func (r *requestFinalizer) close(asyncRelease bool) error { + if atomic.CompareAndSwapInt32(&r.closed, 0, 1) { + runtime.SetFinalizer(r, nil) + if asyncRelease { + go r.Request.Close() + return nil + } else { + return r.Request.Close() + } + } + return nil +} diff --git a/v3/internal/assetserver/webview/request_ios.go b/v3/internal/assetserver/webview/request_ios.go new file mode 100644 index 000000000..81d23b0bc --- /dev/null +++ b/v3/internal/assetserver/webview/request_ios.go @@ -0,0 +1,248 @@ +//go:build ios + +package webview + +/* +#cgo CFLAGS: -x objective-c -fobjc-arc +#cgo LDFLAGS: -framework Foundation -framework WebKit -framework CoreFoundation + +#import +#import +#import +#include + +static void URLSchemeTaskRetain(void *wkUrlSchemeTask) { + id urlSchemeTask = (__bridge id) wkUrlSchemeTask; + CFRetain((CFTypeRef)urlSchemeTask); +} + +static void URLSchemeTaskRelease(void *wkUrlSchemeTask) { + id urlSchemeTask = (__bridge id) wkUrlSchemeTask; + CFRelease((CFTypeRef)urlSchemeTask); +} + +static const char * URLSchemeTaskRequestURL(void *wkUrlSchemeTask) { + id urlSchemeTask = (__bridge id) wkUrlSchemeTask; + @autoreleasepool { + return [urlSchemeTask.request.URL.absoluteString UTF8String]; + } +} + +static const char * URLSchemeTaskRequestMethod(void *wkUrlSchemeTask) { + id urlSchemeTask = (__bridge id) wkUrlSchemeTask; + @autoreleasepool { + return [urlSchemeTask.request.HTTPMethod UTF8String]; + } +} + +static const char * URLSchemeTaskRequestHeadersJSON(void *wkUrlSchemeTask) { + id urlSchemeTask = (__bridge id) wkUrlSchemeTask; + @autoreleasepool { + NSData *headerData = [NSJSONSerialization dataWithJSONObject:urlSchemeTask.request.allHTTPHeaderFields options:0 error:nil]; + if (!headerData) { + return nil; + } + NSString *headerString = [[NSString alloc] initWithData:headerData encoding:NSUTF8StringEncoding]; + const char *headerJSON = [headerString UTF8String]; + return strdup(headerJSON); + } +} + +static bool URLSchemeTaskRequestBodyBytes(void *wkUrlSchemeTask, const void **body, int *bodyLen) { + id urlSchemeTask = (__bridge id) wkUrlSchemeTask; + @autoreleasepool { + if (!urlSchemeTask.request.HTTPBody) { + return false; + } + *body = urlSchemeTask.request.HTTPBody.bytes; + *bodyLen = urlSchemeTask.request.HTTPBody.length; + return true; + } +} + +static bool URLSchemeTaskRequestBodyStreamOpen(void *wkUrlSchemeTask) { + id urlSchemeTask = (__bridge id) wkUrlSchemeTask; + @autoreleasepool { + if (!urlSchemeTask.request.HTTPBodyStream) { + return false; + } + + [urlSchemeTask.request.HTTPBodyStream open]; + return true; + } +} + +static void URLSchemeTaskRequestBodyStreamClose(void *wkUrlSchemeTask) { + id urlSchemeTask = (__bridge id) wkUrlSchemeTask; + @autoreleasepool { + if (!urlSchemeTask.request.HTTPBodyStream) { + return; + } + + [urlSchemeTask.request.HTTPBodyStream close]; + } +} + +static int URLSchemeTaskRequestBodyStreamRead(void *wkUrlSchemeTask, void *buf, int bufLen) { + id urlSchemeTask = (__bridge id) wkUrlSchemeTask; + + @autoreleasepool { + NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream; + if (!stream) { + return -2; + } + + NSStreamStatus status = stream.streamStatus; + if (status == NSStreamStatusAtEnd || !stream.hasBytesAvailable) { + return 0; + } else if (status != NSStreamStatusOpen) { + return -3; + } + + return [stream read:buf maxLength:bufLen]; + } +} +*/ +import "C" + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" + "unsafe" + + "encoding/json" +) + +// NewRequest creates as new WebViewRequest based on a pointer to an `id` +func NewRequest(wkURLSchemeTask unsafe.Pointer) Request { + C.URLSchemeTaskRetain(wkURLSchemeTask) + return newRequestFinalizer(&request{task: wkURLSchemeTask}) +} + +var _ Request = &request{} + +type request struct { + task unsafe.Pointer + + header http.Header + body io.ReadCloser + rw *responseWriter +} + +func (r *request) URL() (string, error) { + return C.GoString(C.URLSchemeTaskRequestURL(r.task)), nil +} + +func (r *request) Method() (string, error) { + return C.GoString(C.URLSchemeTaskRequestMethod(r.task)), nil +} + +func (r *request) Header() (http.Header, error) { + if r.header != nil { + return r.header, nil + } + + header := http.Header{} + if cHeaders := C.URLSchemeTaskRequestHeadersJSON(r.task); cHeaders != nil { + if headers := C.GoString(cHeaders); headers != "" { + var h map[string]string + if err := json.Unmarshal([]byte(headers), &h); err != nil { + return nil, fmt.Errorf("unable to unmarshal request headers: %s", err) + } + + for k, v := range h { + header.Add(k, v) + } + } + C.free(unsafe.Pointer(cHeaders)) + } + r.header = header + return header, nil +} + +func (r *request) Body() (io.ReadCloser, error) { + if r.body != nil { + return r.body, nil + } + + var body unsafe.Pointer + var bodyLen C.int + if C.URLSchemeTaskRequestBodyBytes(r.task, &body, &bodyLen) { + if body != nil && bodyLen > 0 { + r.body = io.NopCloser(bytes.NewReader(C.GoBytes(body, bodyLen))) + } else { + r.body = http.NoBody + } + } else if C.URLSchemeTaskRequestBodyStreamOpen(r.task) { + r.body = &requestBodyStreamReader{task: r.task} + } + + return r.body, nil +} + +func (r *request) Response() ResponseWriter { + if r.rw != nil { + return r.rw + } + + r.rw = &responseWriter{r: r} + return r.rw +} + +func (r *request) Close() error { + var err error + if r.body != nil { + err = r.body.Close() + } + r.Response().Finish() + C.URLSchemeTaskRelease(r.task) + return err +} + +var _ io.ReadCloser = &requestBodyStreamReader{} + +type requestBodyStreamReader struct { + task unsafe.Pointer + closed bool +} + +// Read implements io.Reader +func (r *requestBodyStreamReader) Read(p []byte) (n int, err error) { + var content unsafe.Pointer + var contentLen int + if p != nil { + content = unsafe.Pointer(&p[0]) + contentLen = len(p) + } + + res := C.URLSchemeTaskRequestBodyStreamRead(r.task, content, C.int(contentLen)) + if res > 0 { + return int(res), nil + } + + switch res { + case 0: + return 0, io.EOF + case -1: + return 0, errors.New("body: stream error") + case -2: + return 0, errors.New("body: no stream defined") + case -3: + return 0, io.ErrClosedPipe + default: + return 0, fmt.Errorf("body: unknown error %d", res) + } +} + +func (r *requestBodyStreamReader) Close() error { + if r.closed { + return nil + } + r.closed = true + + C.URLSchemeTaskRequestBodyStreamClose(r.task) + return nil +} diff --git a/v3/internal/assetserver/webview/request_linux.go b/v3/internal/assetserver/webview/request_linux.go new file mode 100644 index 000000000..192e9df5d --- /dev/null +++ b/v3/internal/assetserver/webview/request_linux.go @@ -0,0 +1,82 @@ +//go:build linux && !android + +package webview + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 gio-unix-2.0 + +#include "gtk/gtk.h" +#include "webkit2/webkit2.h" +*/ +import "C" + +import ( + "io" + "net/http" + "unsafe" +) + +// NewRequest creates as new WebViewRequest based on a pointer to an `WebKitURISchemeRequest` +func NewRequest(webKitURISchemeRequest unsafe.Pointer) Request { + webkitReq := (*C.WebKitURISchemeRequest)(webKitURISchemeRequest) + C.g_object_ref(C.gpointer(webkitReq)) + + req := &request{req: webkitReq} + return newRequestFinalizer(req) +} + +var _ Request = &request{} + +type request struct { + req *C.WebKitURISchemeRequest + + header http.Header + body io.ReadCloser + rw *responseWriter +} + +func (r *request) URL() (string, error) { + return C.GoString(C.webkit_uri_scheme_request_get_uri(r.req)), nil +} + +func (r *request) Method() (string, error) { + return webkit_uri_scheme_request_get_http_method(r.req), nil +} + +func (r *request) Header() (http.Header, error) { + if r.header != nil { + return r.header, nil + } + + r.header = webkit_uri_scheme_request_get_http_headers(r.req) + return r.header, nil +} + +func (r *request) Body() (io.ReadCloser, error) { + if r.body != nil { + return r.body, nil + } + + r.body = webkit_uri_scheme_request_get_http_body(r.req) + + return r.body, nil +} + +func (r *request) Response() ResponseWriter { + if r.rw != nil { + return r.rw + } + + r.rw = &responseWriter{req: r.req} + return r.rw +} + +func (r *request) Close() error { + var err error + if r.body != nil { + err = r.body.Close() + } + r.Response().Finish() + C.g_object_unref(C.gpointer(r.req)) + return err +} diff --git a/v3/internal/assetserver/webview/request_linux_purego.go b/v3/internal/assetserver/webview/request_linux_purego.go new file mode 100644 index 000000000..34937d25f --- /dev/null +++ b/v3/internal/assetserver/webview/request_linux_purego.go @@ -0,0 +1,93 @@ +//go:build linux && purego && !android + +package webview + +import ( + "io" + "net/http" + + "github.com/ebitengine/purego" +) + +// NewRequest creates as new WebViewRequest based on a pointer to an `WebKitURISchemeRequest` +// +// Please make sure to call Release() when finished using the request. +func NewRequest(webKitURISchemeRequest uintptr) Request { + webkitReq := webKitURISchemeRequest + req := &request{req: webkitReq} + req.AddRef() + return req +} + +var _ Request = &request{} + +type request struct { + req uintptr + + header http.Header + body io.ReadCloser + rw *responseWriter +} + +func (r *request) AddRef() error { + var objectRef func(uintptr) + purego.RegisterLibFunc(&objectRef, gtk, "g_object_ref") + objectRef(r.req) + return nil +} + +func (r *request) Release() error { + var objectUnref func(uintptr) + purego.RegisterLibFunc(&objectUnref, gtk, "g_object_unref") + objectUnref(r.req) + return nil +} + +func (r *request) URL() (string, error) { + var getUri func(uintptr) string + purego.RegisterLibFunc(&getUri, webkit, "webkit_uri_scheme_request_get_uri") + return getUri(r.req), nil +} + +func (r *request) Method() (string, error) { + return webkit_uri_scheme_request_get_http_method(r.req), nil +} + +func (r *request) Header() (http.Header, error) { + if r.header != nil { + return r.header, nil + } + + r.header = webkit_uri_scheme_request_get_http_headers(r.req) + return r.header, nil +} + +func (r *request) Body() (io.ReadCloser, error) { + if r.body != nil { + return r.body, nil + } + + // WebKit2GTK has currently no support for request bodies. + r.body = http.NoBody + + return r.body, nil +} + +func (r *request) Response() ResponseWriter { + if r.rw != nil { + return r.rw + } + + r.rw = &responseWriter{req: r.req} + return r.rw +} + +func (r *request) Close() error { + var err error + if r.body != nil { + err = r.body.Close() + } + r.Response().Finish() + r.Release() + return err +} diff --git a/v3/internal/assetserver/webview/request_windows.go b/v3/internal/assetserver/webview/request_windows.go new file mode 100644 index 000000000..9f68af2e1 --- /dev/null +++ b/v3/internal/assetserver/webview/request_windows.go @@ -0,0 +1,218 @@ +//go:build windows + +package webview + +import ( + "errors" + "fmt" + "io" + "net/http" + + "github.com/wailsapp/go-webview2/pkg/edge" +) + +// NewRequest creates as new WebViewRequest for chromium. This Method must be called from the Main-Thread! +func NewRequest(env *edge.ICoreWebView2Environment, args *edge.ICoreWebView2WebResourceRequestedEventArgs, invokeSync func(fn func())) (Request, error) { + req, err := args.GetRequest() + if err != nil { + return nil, fmt.Errorf("GetRequest failed: %s", err) + } + defer req.Release() + + r := &request{ + invokeSync: invokeSync, + } + + code := http.StatusInternalServerError + r.response, err = env.CreateWebResourceResponse(nil, code, http.StatusText(code), "") + if err != nil { + return nil, fmt.Errorf("CreateWebResourceResponse failed: %s", err) + } + + if err := args.PutResponse(r.response); err != nil { + r.finishResponse() + return nil, fmt.Errorf("PutResponse failed: %s", err) + } + + r.deferral, err = args.GetDeferral() + if err != nil { + r.finishResponse() + return nil, fmt.Errorf("GetDeferral failed: %s", err) + } + + r.url, r.urlErr = req.GetUri() + r.method, r.methodErr = req.GetMethod() + r.header, r.headerErr = getHeaders(req) + + if content, err := req.GetContent(); err != nil { + r.bodyErr = err + } else if content != nil { + // It is safe to access Content from another Thread: https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/threading-model#thread-safety + r.body = &iStreamReleaseCloser{stream: content} + } + + return r, nil +} + +var _ Request = &request{} + +type request struct { + response *edge.ICoreWebView2WebResourceResponse + deferral *edge.ICoreWebView2Deferral + + url string + urlErr error + + method string + methodErr error + + header http.Header + headerErr error + + body io.ReadCloser + bodyErr error + rw *responseWriter + + invokeSync func(fn func()) +} + +func (r *request) URL() (string, error) { + return r.url, r.urlErr +} + +func (r *request) Method() (string, error) { + return r.method, r.methodErr +} + +func (r *request) Header() (http.Header, error) { + return r.header, r.headerErr +} + +func (r *request) Body() (io.ReadCloser, error) { + return r.body, r.bodyErr +} + +func (r *request) Response() ResponseWriter { + if r.rw != nil { + return r.rw + } + + r.rw = &responseWriter{req: r} + return r.rw +} + +func (r *request) Close() error { + var errs []error + if r.body != nil { + if err := r.body.Close(); err != nil { + errs = append(errs, err) + } + r.body = nil + } + + if err := r.Response().Finish(); err != nil { + errs = append(errs, err) + } + + return combineErrs(errs) +} + +// finishResponse must be called on the main-thread +func (r *request) finishResponse() error { + var errs []error + if r.response != nil { + if err := r.response.Release(); err != nil { + errs = append(errs, err) + } + r.response = nil + } + if r.deferral != nil { + if err := r.deferral.Complete(); err != nil { + errs = append(errs, err) + } + + if err := r.deferral.Release(); err != nil { + errs = append(errs, err) + } + r.deferral = nil + } + return combineErrs(errs) +} + +type iStreamReleaseCloser struct { + stream *edge.IStream + closed bool +} + +func (i *iStreamReleaseCloser) Read(p []byte) (int, error) { + if i.closed { + return 0, io.ErrClosedPipe + } + return i.stream.Read(p) +} + +func (i *iStreamReleaseCloser) Close() error { + if i.closed { + return nil + } + i.closed = true + return i.stream.Release() +} + +func getHeaders(req *edge.ICoreWebView2WebResourceRequest) (http.Header, error) { + header := http.Header{} + headers, err := req.GetHeaders() + if err != nil { + return nil, fmt.Errorf("GetHeaders Error: %s", err) + } + defer headers.Release() + + headersIt, err := headers.GetIterator() + if err != nil { + return nil, fmt.Errorf("GetIterator Error: %s", err) + } + defer headersIt.Release() + + for { + has, err := headersIt.HasCurrentHeader() + if err != nil { + return nil, fmt.Errorf("HasCurrentHeader Error: %s", err) + } + if !has { + break + } + + name, value, err := headersIt.GetCurrentHeader() + if err != nil { + return nil, fmt.Errorf("GetCurrentHeader Error: %s", err) + } + + header.Set(name, value) + if _, err := headersIt.MoveNext(); err != nil { + return nil, fmt.Errorf("MoveNext Error: %s", err) + } + } + + // WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other + // requests including IPC calls. + // So prevent 304 status codes by removing the headers that are used in combinationwith caching. + header.Del("If-Modified-Since") + header.Del("If-None-Match") + return header, nil +} + +func combineErrs(errs []error) error { + err := errors.Join(errs...) + + if err != nil { + // errors.Join wraps even a single error. + // Check the filtered error list, + // and if it has just one element return it directly. + errs = err.(interface{ Unwrap() []error }).Unwrap() + if len(errs) == 1 { + return errs[0] + } + } + + return err +} diff --git a/v3/internal/assetserver/webview/responsewriter.go b/v3/internal/assetserver/webview/responsewriter.go new file mode 100644 index 000000000..2fc7ede51 --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter.go @@ -0,0 +1,28 @@ +package webview + +import ( + "errors" + "net/http" +) + +const ( + HeaderContentLength = "Content-Length" + HeaderContentType = "Content-Type" +) + +var ( + errRequestStopped = errors.New("request has been stopped") + errResponseFinished = errors.New("response has been finished") +) + +// A ResponseWriter interface is used by an HTTP handler to +// construct an HTTP response for the WebView. +type ResponseWriter interface { + http.ResponseWriter + + // Finish the response and flush all data. A Finish after the request has already been finished has no effect. + Finish() error + + // Code returns the HTTP status code of the response + Code() int +} diff --git a/v3/internal/assetserver/webview/responsewriter_darwin.go b/v3/internal/assetserver/webview/responsewriter_darwin.go new file mode 100644 index 000000000..ff29a08bb --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter_darwin.go @@ -0,0 +1,156 @@ +//go:build darwin && !ios + +package webview + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework WebKit + +#import +#import + +typedef void (^schemeTaskCaller)(id); + +static bool urlSchemeTaskCall(void *wkUrlSchemeTask, schemeTaskCaller fn) { + id urlSchemeTask = (id) wkUrlSchemeTask; + if (urlSchemeTask == nil) { + return false; + } + + @autoreleasepool { + @try { + fn(urlSchemeTask); + } @catch (NSException *exception) { + // This is very bad to detect a stopped schemeTask this should be implemented in a better way + // But it seems to be very tricky to not deadlock when keeping a lock curing executing fn() + // It seems like those call switch the thread back to the main thread and then deadlocks when they reentrant want + // to get the lock again to start another request or stop it. + if ([exception.reason isEqualToString: @"This task has already been stopped"]) { + return false; + } + + @throw exception; + } + + return true; + } +} + +static bool URLSchemeTaskDidReceiveData(void *wkUrlSchemeTask, void* data, int datalength) { + return urlSchemeTaskCall( + wkUrlSchemeTask, + ^(id urlSchemeTask) { + NSData *nsdata = [NSData dataWithBytes:data length:datalength]; + [urlSchemeTask didReceiveData:nsdata]; + }); +} + +static bool URLSchemeTaskDidFinish(void *wkUrlSchemeTask) { + return urlSchemeTaskCall( + wkUrlSchemeTask, + ^(id urlSchemeTask) { + [urlSchemeTask didFinish]; + }); +} + +static bool URLSchemeTaskDidReceiveResponse(void *wkUrlSchemeTask, int statusCode, void *headersString, int headersStringLength) { + return urlSchemeTaskCall( + wkUrlSchemeTask, + ^(id urlSchemeTask) { + NSData *nsHeadersJSON = [NSData dataWithBytes:headersString length:headersStringLength]; + NSDictionary *headerFields = [NSJSONSerialization JSONObjectWithData:nsHeadersJSON options: NSJSONReadingMutableContainers error: nil]; + NSHTTPURLResponse *response = [[[NSHTTPURLResponse alloc] initWithURL:urlSchemeTask.request.URL statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:headerFields] autorelease]; + + [urlSchemeTask didReceiveResponse:response]; + }); +} +*/ +import "C" + +import ( + "net/http" + "unsafe" + + "encoding/json" +) + +var _ ResponseWriter = &responseWriter{} + +type responseWriter struct { + r *request + + header http.Header + wroteHeader bool + code int + + finished bool +} + +func (rw *responseWriter) Header() http.Header { + if rw.header == nil { + rw.header = http.Header{} + } + return rw.header +} + +func (rw *responseWriter) Write(buf []byte) (int, error) { + if rw.finished { + return 0, errResponseFinished + } + + rw.WriteHeader(http.StatusOK) + + var content unsafe.Pointer + var contentLen int + if buf != nil && len(buf) > 0 { + content = unsafe.Pointer(&buf[0]) + contentLen = len(buf) + } + + if !C.URLSchemeTaskDidReceiveData(rw.r.task, content, C.int(contentLen)) { + return 0, errRequestStopped + } + return contentLen, nil +} + +func (rw *responseWriter) WriteHeader(code int) { + rw.code = code + if rw.wroteHeader || rw.finished { + return + } + rw.wroteHeader = true + + header := map[string]string{} + for k := range rw.Header() { + header[k] = rw.Header().Get(k) + } + headerData, _ := json.Marshal(header) + + var headers unsafe.Pointer + var headersLen int + if len(headerData) != 0 { + headers = unsafe.Pointer(&headerData[0]) + headersLen = len(headerData) + } + + C.URLSchemeTaskDidReceiveResponse(rw.r.task, C.int(code), headers, C.int(headersLen)) +} + +func (rw *responseWriter) Finish() error { + if !rw.wroteHeader { + rw.WriteHeader(http.StatusNotImplemented) + } + + if rw.finished { + return nil + } + rw.finished = true + + C.URLSchemeTaskDidFinish(rw.r.task) + + return nil +} + +func (rw *responseWriter) Code() int { + return rw.code +} diff --git a/v3/internal/assetserver/webview/responsewriter_ios.go b/v3/internal/assetserver/webview/responsewriter_ios.go new file mode 100644 index 000000000..119ef1628 --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter_ios.go @@ -0,0 +1,172 @@ +//go:build ios + +package webview + +/* +#cgo CFLAGS: -x objective-c -fobjc-arc +#cgo LDFLAGS: -framework Foundation -framework WebKit + +#import +#import + +typedef void (^schemeTaskCaller)(id); + +static bool urlSchemeTaskCall(void *wkUrlSchemeTask, schemeTaskCaller fn) { + id urlSchemeTask = (__bridge id) wkUrlSchemeTask; + if (urlSchemeTask == nil) { + return false; + } + + @autoreleasepool { + @try { + fn(urlSchemeTask); + } @catch (NSException *exception) { + // This is very bad to detect a stopped schemeTask this should be implemented in a better way + // But it seems to be very tricky to not deadlock when keeping a lock curing executing fn() + // It seems like those call switch the thread back to the main thread and then deadlocks when they reentrant want + // to get the lock again to start another request or stop it. + if ([exception.reason isEqualToString: @"This task has already been stopped"]) { + return false; + } + + @throw exception; + } + + return true; + } +} + +static bool URLSchemeTaskDidReceiveData(void *wkUrlSchemeTask, void* data, int datalength) { + return urlSchemeTaskCall( + wkUrlSchemeTask, + ^(id urlSchemeTask) { + NSData *nsdata = [NSData dataWithBytes:data length:datalength]; + [urlSchemeTask didReceiveData:nsdata]; + }); +} + +static bool URLSchemeTaskDidFinish(void *wkUrlSchemeTask) { + return urlSchemeTaskCall( + wkUrlSchemeTask, + ^(id urlSchemeTask) { + [urlSchemeTask didFinish]; + }); +} + +static bool URLSchemeTaskDidReceiveResponse(void *wkUrlSchemeTask, int statusCode, void *headersString, int headersStringLength) { + return urlSchemeTaskCall( + wkUrlSchemeTask, + ^(id urlSchemeTask) { + NSData *nsHeadersJSON = [NSData dataWithBytes:headersString length:headersStringLength]; + NSDictionary *headerFields = [NSJSONSerialization JSONObjectWithData:nsHeadersJSON options:NSJSONReadingMutableContainers error:nil]; + NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:urlSchemeTask.request.URL statusCode:statusCode HTTPVersion:nil headerFields:headerFields]; + + [urlSchemeTask didReceiveResponse:response]; + }); +} +*/ +import "C" + +import ( + "fmt" + "net/http" + "strings" + "unsafe" + + "encoding/json" +) + +var _ ResponseWriter = &responseWriter{} + +type responseWriter struct { + r *request + + header http.Header + wroteHeader bool + code int + + finished bool +} + +func (rw *responseWriter) Header() http.Header { + if rw.header == nil { + rw.header = http.Header{} + } + return rw.header +} + +func (rw *responseWriter) Write(buf []byte) (int, error) { + if rw.finished { + return 0, errResponseFinished + } + + rw.WriteHeader(http.StatusOK) + + // Debug logging for CSS files + if url, err := rw.r.URL(); err == nil && (strings.Contains(url, ".css") || strings.Contains(url, "style")) { + preview := string(buf) + if len(preview) > 100 { + preview = preview[:100] + "..." + } + fmt.Printf("🎨 CSS Write: URL=%s Size=%d Preview=%s\n", url, len(buf), preview) + } + + var content unsafe.Pointer + var contentLen int + if buf != nil { + content = unsafe.Pointer(&buf[0]) + contentLen = len(buf) + } + + if !C.URLSchemeTaskDidReceiveData(rw.r.task, content, C.int(contentLen)) { + return 0, errRequestStopped + } + return contentLen, nil +} + +func (rw *responseWriter) WriteHeader(code int) { + rw.code = code + if rw.wroteHeader || rw.finished { + return + } + rw.wroteHeader = true + + header := map[string]string{} + for k := range rw.Header() { + header[k] = rw.Header().Get(k) + } + headerData, _ := json.Marshal(header) + + // Debug logging for CSS files + if url, err := rw.r.URL(); err == nil && (strings.Contains(url, ".css") || strings.Contains(url, "style")) { + fmt.Printf("🎨 CSS Response: URL=%s Code=%d Headers=%s\n", url, code, string(headerData)) + } + + var headers unsafe.Pointer + var headersLen int + if len(headerData) != 0 { + headers = unsafe.Pointer(&headerData[0]) + headersLen = len(headerData) + } + + C.URLSchemeTaskDidReceiveResponse(rw.r.task, C.int(code), headers, C.int(headersLen)) +} + +func (rw *responseWriter) Finish() error { + if !rw.wroteHeader { + rw.WriteHeader(http.StatusNotImplemented) + } + + if rw.finished { + return nil + } + rw.finished = true + + C.URLSchemeTaskDidFinish(rw.r.task) + + return nil +} + +func (rw *responseWriter) Code() int { + return rw.code +} diff --git a/v3/internal/assetserver/webview/responsewriter_linux.go b/v3/internal/assetserver/webview/responsewriter_linux.go new file mode 100644 index 000000000..ca232afc0 --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter_linux.go @@ -0,0 +1,135 @@ +//go:build linux && !android + +package webview + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 gio-unix-2.0 + +#include "gtk/gtk.h" +#include "webkit2/webkit2.h" +#include "gio/gunixinputstream.h" + +*/ +import "C" +import ( + "fmt" + "io" + "net/http" + "os" + "strconv" + "syscall" + "unsafe" +) + +type responseWriter struct { + req *C.WebKitURISchemeRequest + + header http.Header + wroteHeader bool + finished bool + code int + + w io.WriteCloser + wErr error +} + +func (rw *responseWriter) Code() int { + return rw.code +} + +func (rw *responseWriter) Header() http.Header { + if rw.header == nil { + rw.header = http.Header{} + } + return rw.header +} + +func (rw *responseWriter) Write(buf []byte) (int, error) { + if rw.finished { + return 0, errResponseFinished + } + + rw.WriteHeader(http.StatusOK) + if rw.wErr != nil { + return 0, rw.wErr + } + return rw.w.Write(buf) +} + +func (rw *responseWriter) WriteHeader(code int) { + rw.code = code + if rw.wroteHeader || rw.finished { + return + } + rw.wroteHeader = true + + contentLength := int64(-1) + if sLen := rw.Header().Get(HeaderContentLength); sLen != "" { + if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 { + contentLength = pLen + } + } + + // We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the + // read FD is given to the InputStream and will be closed there. + // Furthermore we especially don't want to have the FD_CLOEXEC + rFD, w, err := pipe() + if err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err)) + return + } + rw.w = w + + stream := C.g_unix_input_stream_new(C.int(rFD), C.gboolean(1)) + defer C.g_object_unref(C.gpointer(stream)) + + if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err)) + return + } +} + +func (rw *responseWriter) Finish() error { + if !rw.wroteHeader { + rw.WriteHeader(http.StatusNotImplemented) + } + + if rw.finished { + return nil + } + rw.finished = true + if rw.w != nil { + rw.w.Close() + } + return nil +} + +func (rw *responseWriter) finishWithError(code int, err error) { + if rw.w != nil { + rw.w.Close() + rw.w = &nopCloser{io.Discard} + } + rw.wErr = err + + msg := C.CString(err.Error()) + gerr := C.g_error_new_literal(C.g_quark_from_string(msg), C.int(code), msg) + C.webkit_uri_scheme_request_finish_error(rw.req, gerr) + C.g_error_free(gerr) + C.free(unsafe.Pointer(msg)) +} + +type nopCloser struct { + io.Writer +} + +func (nopCloser) Close() error { return nil } + +func pipe() (r int, w *os.File, err error) { + var p [2]int + e := syscall.Pipe2(p[0:], 0) + if e != nil { + return 0, nil, fmt.Errorf("pipe2: %s", e) + } + + return p[0], os.NewFile(uintptr(p[1]), "|1"), nil +} diff --git a/v3/internal/assetserver/webview/responsewriter_linux_purego.go b/v3/internal/assetserver/webview/responsewriter_linux_purego.go new file mode 100644 index 000000000..804765226 --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter_linux_purego.go @@ -0,0 +1,179 @@ +//go:build linux && purego && !android + +package webview + +import ( + "fmt" + "io" + "net/http" + "os" + "strconv" + "syscall" + + "github.com/ebitengine/purego" +) + +const ( + gtk3 = "libgtk-3.so" + gtk4 = "libgtk-4.so" +) + +var ( + gtk uintptr + webkit uintptr + version int +) + +func init() { + var err error + // gtk, err = purego.Dlopen(gtk4, purego.RTLD_NOW|purego.RTLD_GLOBAL) + // if err == nil { + // version = 4 + // return + // } + // log.Println("Failed to open GTK4: Falling back to GTK3") + gtk, err = purego.Dlopen(gtk3, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) + } + version = 3 + + var webkit4 string = "libwebkit2gtk-4.1.so" + webkit, err = purego.Dlopen(webkit4, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) + } +} + +type responseWriter struct { + req uintptr + + header http.Header + wroteHeader bool + finished bool + code int + w io.WriteCloser + wErr error +} + +func (rw *responseWriter) Code() int { + return rw.code +} + +func (rw *responseWriter) Header() http.Header { + if rw.header == nil { + rw.header = http.Header{} + } + return rw.header +} + +func (rw *responseWriter) Write(buf []byte) (int, error) { + if rw.finished { + return 0, errResponseFinished + } + + rw.WriteHeader(http.StatusOK) + if rw.wErr != nil { + return 0, rw.wErr + } + return rw.w.Write(buf) +} + +func (rw *responseWriter) WriteHeader(code int) { + rw.code = code + + // TODO? Is this ever called? I don't think so! + if rw.wroteHeader || rw.finished { + return + } + rw.wroteHeader = true + + contentLength := int64(-1) + if sLen := rw.Header().Get(HeaderContentLength); sLen != "" { + if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 { + contentLength = pLen + } + } + // We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the + // read FD is given to the InputStream and will be closed there. + // Furthermore we especially don't want to have the FD_CLOEXEC + rFD, w, err := pipe() + if err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err)) + return + } + rw.w = w + + var newStream func(int, bool) uintptr + purego.RegisterLibFunc(&newStream, gtk, "g_unix_input_stream_new") + var unRef func(uintptr) + purego.RegisterLibFunc(&unRef, gtk, "g_object_unref") + stream := newStream(rFD, true) + + /* var reqFinish func(uintptr, uintptr, uintptr, uintptr, int64) int + purego.RegisterLibFunc(&reqFinish, webkit, "webkit_uri_scheme_request_finish") + + header := rw.Header() + defer unRef(stream) + if err := reqFinish(rw.req, code, header, stream, contentLength); err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err)) + } + */ + if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err)) + return + } +} + +func (rw *responseWriter) Finish() { + if !rw.wroteHeader { + rw.WriteHeader(http.StatusNotImplemented) + } + + if rw.finished { + return + } + rw.finished = true + if rw.w != nil { + rw.w.Close() + } +} + +func (rw *responseWriter) finishWithError(code int, err error) { + if rw.w != nil { + rw.w.Close() + rw.w = &nopCloser{io.Discard} + } + rw.wErr = err + + var newLiteral func(uint32, string, int, string) uintptr // is this correct? + purego.RegisterLibFunc(&newLiteral, gtk, "g_error_new_literal") + var newQuark func(string) uintptr + purego.RegisterLibFunc(&newQuark, gtk, "g_quark_from_string") + var freeError func(uintptr) + purego.RegisterLibFunc(&freeError, gtk, "g_error_free") + var finishError func(uintptr, uintptr) + purego.RegisterLibFunc(&finishError, webkit, "webkit_uri_scheme_request_finish_error") + + msg := string(err.Error()) + //gquark := newQuark(msg) + gerr := newLiteral(1, msg, code, msg) + finishError(rw.req, gerr) + freeError(gerr) +} + +type nopCloser struct { + io.Writer +} + +func (nopCloser) Close() error { return nil } + +func pipe() (r int, w *os.File, err error) { + var p [2]int + e := syscall.Pipe2(p[0:], 0) + if e != nil { + return 0, nil, fmt.Errorf("pipe2: %s", e) + } + + return p[0], os.NewFile(uintptr(p[1]), "|1"), nil +} diff --git a/v3/internal/assetserver/webview/responsewriter_windows.go b/v3/internal/assetserver/webview/responsewriter_windows.go new file mode 100644 index 000000000..c003f00bd --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter_windows.go @@ -0,0 +1,109 @@ +//go:build windows + +package webview + +import ( + "bytes" + "errors" + "fmt" + "net/http" + "strings" +) + +var _ http.ResponseWriter = &responseWriter{} + +type responseWriter struct { + req *request + + header http.Header + wroteHeader bool + code int + body *bytes.Buffer + + finished bool +} + +func (rw *responseWriter) Header() http.Header { + if rw.header == nil { + rw.header = http.Header{} + } + return rw.header +} + +func (rw *responseWriter) Write(buf []byte) (int, error) { + if rw.finished { + return 0, errResponseFinished + } + + rw.WriteHeader(http.StatusOK) + + return rw.body.Write(buf) +} + +func (rw *responseWriter) WriteHeader(code int) { + if rw.wroteHeader || rw.finished { + return + } + rw.wroteHeader = true + + if rw.body == nil { + rw.body = &bytes.Buffer{} + } + + rw.code = code +} + +func (rw *responseWriter) Finish() error { + if !rw.wroteHeader { + rw.WriteHeader(http.StatusNotImplemented) + } + + if rw.finished { + return nil + } + rw.finished = true + + var errs []error + + code := rw.code + if code == http.StatusNotModified { + // WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other + // requests including IPC calls. + errs = append(errs, errors.New("AssetServer returned 304 - StatusNotModified which are going to hang WebView2, changed code to 505 - StatusInternalServerError")) + code = http.StatusInternalServerError + } + + rw.req.invokeSync(func() { + resp := rw.req.response + + hdrs, err := resp.GetHeaders() + if err != nil { + errs = append(errs, fmt.Errorf("Resp.GetHeaders failed: %s", err)) + } else { + for k, v := range rw.header { + if err := hdrs.AppendHeader(k, strings.Join(v, ",")); err != nil { + errs = append(errs, fmt.Errorf("Resp.AppendHeader failed: %s", err)) + } + } + hdrs.Release() + } + + if err := resp.PutStatusCode(code); err != nil { + errs = append(errs, fmt.Errorf("Resp.PutStatusCode failed: %s", err)) + } + + if err := resp.PutByteContent(rw.body.Bytes()); err != nil { + errs = append(errs, fmt.Errorf("Resp.PutByteContent failed: %s", err)) + } + + if err := rw.req.finishResponse(); err != nil { + errs = append(errs, fmt.Errorf("Resp.finishResponse failed: %s", err)) + } + }) + + return combineErrs(errs) +} + +func (rw *responseWriter) Code() int { + return rw.code +} diff --git a/v3/internal/assetserver/webview/webkit2.go b/v3/internal/assetserver/webview/webkit2.go new file mode 100644 index 000000000..dfc79ef7e --- /dev/null +++ b/v3/internal/assetserver/webview/webkit2.go @@ -0,0 +1,137 @@ +//go:build linux && !android + +package webview + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 libsoup-3.0 + +#include "gtk/gtk.h" +#include "webkit2/webkit2.h" +#include "libsoup/soup.h" +*/ +import "C" + +import ( + "fmt" + "io" + "net/http" + "strings" + "unsafe" +) + +const Webkit2MinMinorVersion = 40 + +func webkit_uri_scheme_request_get_http_method(req *C.WebKitURISchemeRequest) string { + method := C.GoString(C.webkit_uri_scheme_request_get_http_method(req)) + return strings.ToUpper(method) +} + +func webkit_uri_scheme_request_get_http_headers(req *C.WebKitURISchemeRequest) http.Header { + hdrs := C.webkit_uri_scheme_request_get_http_headers(req) + + var iter C.SoupMessageHeadersIter + C.soup_message_headers_iter_init(&iter, hdrs) + + var name *C.char + var value *C.char + + h := http.Header{} + for C.soup_message_headers_iter_next(&iter, &name, &value) != 0 { + h.Add(C.GoString(name), C.GoString(value)) + } + + return h +} + +func webkit_uri_scheme_request_finish(req *C.WebKitURISchemeRequest, code int, header http.Header, stream *C.GInputStream, streamLength int64) error { + resp := C.webkit_uri_scheme_response_new(stream, C.gint64(streamLength)) + defer C.g_object_unref(C.gpointer(resp)) + + cReason := C.CString(http.StatusText(code)) + C.webkit_uri_scheme_response_set_status(resp, C.guint(code), cReason) + C.free(unsafe.Pointer(cReason)) + + cMimeType := C.CString(header.Get(HeaderContentType)) + C.webkit_uri_scheme_response_set_content_type(resp, cMimeType) + C.free(unsafe.Pointer(cMimeType)) + + hdrs := C.soup_message_headers_new(C.SOUP_MESSAGE_HEADERS_RESPONSE) + for name, values := range header { + cName := C.CString(name) + for _, value := range values { + cValue := C.CString(value) + C.soup_message_headers_append(hdrs, cName, cValue) + C.free(unsafe.Pointer(cValue)) + } + C.free(unsafe.Pointer(cName)) + } + + C.webkit_uri_scheme_response_set_http_headers(resp, hdrs) + + C.webkit_uri_scheme_request_finish_with_response(req, resp) + return nil +} + +func webkit_uri_scheme_request_get_http_body(req *C.WebKitURISchemeRequest) io.ReadCloser { + stream := C.webkit_uri_scheme_request_get_http_body(req) + if stream == nil { + return http.NoBody + } + return &webkitRequestBody{stream: stream} +} + +type webkitRequestBody struct { + stream *C.GInputStream + closed bool +} + +// Read implements io.Reader +func (r *webkitRequestBody) Read(p []byte) (int, error) { + if r.closed { + return 0, io.ErrClosedPipe + } + + var content unsafe.Pointer + var contentLen int + if p != nil { + content = unsafe.Pointer(&p[0]) + contentLen = len(p) + } + + var n C.gsize + var gErr *C.GError + res := C.g_input_stream_read_all(r.stream, content, C.gsize(contentLen), &n, nil, &gErr) + if res == 0 { + return 0, formatGError("stream read failed", gErr) + } else if n == 0 { + return 0, io.EOF + } + return int(n), nil +} + +func (r *webkitRequestBody) Close() error { + if r.closed { + return nil + } + r.closed = true + + // https://docs.gtk.org/gio/method.InputStream.close.html + // Streams will be automatically closed when the last reference is dropped, but you might want to call this function + // to make sure resources are released as early as possible. + var err error + var gErr *C.GError + if C.g_input_stream_close(r.stream, nil, &gErr) == 0 { + err = formatGError("stream close failed", gErr) + } + C.g_object_unref(C.gpointer(r.stream)) + r.stream = nil + return err +} + +func formatGError(msg string, gErr *C.GError, args ...any) error { + if gErr != nil && gErr.message != nil { + msg += ": " + C.GoString(gErr.message) + C.g_error_free(gErr) + } + return fmt.Errorf(msg, args...) +} diff --git a/v3/internal/assetserver/webview/webkit2_purego.go b/v3/internal/assetserver/webview/webkit2_purego.go new file mode 100644 index 000000000..28832c8bd --- /dev/null +++ b/v3/internal/assetserver/webview/webkit2_purego.go @@ -0,0 +1,158 @@ +//go:build linux && purego + +package webview + +import ( + "net/http" + "strings" + + "github.com/ebitengine/purego" +) + +func webkit_uri_scheme_request_get_http_method(req uintptr) string { + var getMethod func(uintptr) string + purego.RegisterLibFunc(&getMethod, gtk, "webkit_uri_scheme_request_get_http_method") + return strings.ToUpper(getMethod(req)) +} + +func webkit_uri_scheme_request_get_http_headers(req uintptr) http.Header { + var getHeaders func(uintptr) uintptr + purego.RegisterLibFunc(&getUri, webkit, "webkit_uri_scheme_request_get_http_headers") + + hdrs := getHeaders(req) + + var headersIterInit func(uintptr, uintptr) uintptr + purego.RegisterLibFunc(&headersIterInit, gtk, "soup_message_headers_iter_init") + + // TODO: How do we get a struct? + /* + typedef struct { + SoupMessageHeaders *hdrs; + int index_common; + int index_uncommon; + } SoupMessageHeadersIterReal; + */ + iter := make([]byte, 12) + headersIterInit(&iter, hdrs) + + var iterNext func(uintptr, *string, *string) int + purego.RegisterLibFunc(&iterNext, gtk, "soup_message_headers_iter_next") + + var name string + var value string + h := http.Header{} + + for iterNext(&iter, &name, &value) != 0 { + h.Add(name, value) + } + + return h +} + +func webkit_uri_scheme_request_finish(req uintptr, code int, header http.Header, stream uintptr, streamLength int64) error { + + var newResponse func(uintptr, int64) string + purego.RegisterLibFunc(&newResponse, webkit, "webkit_uri_scheme_response_new") + var unRef func(uintptr) + purego.RegisterLibFunc(&unRef, gtk, "g_object_unref") + + resp := newResponse(stream, streamLength) + defer unRef(resp) + + var setStatus func(uintptr, int, string) + purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_status") + + setStatus(resp, code, cReason) + + var setContentType func(uintptr, string) + purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_content_type") + + setContentType(resp, header.Get(HeaderContentType)) + + soup := gtk + var soupHeadersNew func(int) uintptr + purego.RegisterLibFunc(&unRef, soup, "soup_message_headers_new") + var soupHeadersAppend func(uintptr, string, string) + purego.RegisterLibFunc(&unRef, soup, "soup_message_headers_append") + + hdrs := soupHeadersNew(SOUP_MESSAGE_HEADERS_RESPONSE) + for name, values := range header { + for _, value := range values { + soupHeadersAppend(hdrs, name, value) + } + } + + var setHttpHeaders func(uintptr, uintptr) + purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_http_headers") + + setHttpHeaders(resp, hdrs) + var finishWithResponse func(uintptr, uintptr) + purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_request_finish_with_response") + finishWithResponse(req, resp) + + return nil +} + +func webkit_uri_scheme_request_get_http_body(req *C.WebKitURISchemeRequest) io.ReadCloser { + stream := C.webkit_uri_scheme_request_get_http_body(req) + if stream == nil { + return http.NoBody + } + return &webkitRequestBody{stream: stream} +} + +type webkitRequestBody struct { + stream *C.GInputStream + closed bool +} + +// Read implements io.Reader +func (r *webkitRequestBody) Read(p []byte) (int, error) { + if r.closed { + return 0, io.ErrClosedPipe + } + + var content unsafe.Pointer + var contentLen int + if p != nil { + content = unsafe.Pointer(&p[0]) + contentLen = len(p) + } + + var n C.gsize + var gErr *C.GError + res := C.g_input_stream_read_all(r.stream, content, C.gsize(contentLen), &n, nil, &gErr) + if res == 0 { + return 0, formatGError("stream read failed", gErr) + } else if n == 0 { + return 0, io.EOF + } + return int(n), nil +} + +func (r *webkitRequestBody) Close() error { + if r.closed { + return nil + } + r.closed = true + + // https://docs.gtk.org/gio/method.InputStream.close.html + // Streams will be automatically closed when the last reference is dropped, but you might want to call this function + // to make sure resources are released as early as possible. + var err error + var gErr *C.GError + if C.g_input_stream_close(r.stream, nil, &gErr) == 0 { + err = formatGError("stream close failed", gErr) + } + C.g_object_unref(C.gpointer(r.stream)) + r.stream = nil + return err +} + +func formatGError(msg string, gErr *C.GError, args ...any) error { + if gErr != nil && gErr.message != nil { + msg += ": " + C.GoString(gErr.message) + C.g_error_free(gErr) + } + return fmt.Errorf(msg, args...) +} diff --git a/v3/internal/buildinfo/buildinfo.go b/v3/internal/buildinfo/buildinfo.go new file mode 100644 index 000000000..930831f1f --- /dev/null +++ b/v3/internal/buildinfo/buildinfo.go @@ -0,0 +1,40 @@ +package buildinfo + +import ( + "fmt" + "runtime/debug" + "slices" + + "github.com/samber/lo" +) + +type Info struct { + Development bool + Version string + BuildSettings map[string]string + wailsPackage *debug.Module +} + +func Get() (*Info, error) { + + var result Info + + // BuildInfo contains the build info for the application + var BuildInfo *debug.BuildInfo + + var ok bool + BuildInfo, ok = debug.ReadBuildInfo() + if !ok { + return nil, fmt.Errorf("could not read build info from binary") + } + result.BuildSettings = lo.Associate(BuildInfo.Settings, func(setting debug.BuildSetting) (string, string) { + return setting.Key, setting.Value + }) + result.Version = BuildInfo.Main.Version + result.Development = -1 != slices.IndexFunc(BuildInfo.Settings, func(setting debug.BuildSetting) bool { + return setting.Key == "vcs" && setting.Value == "git" + }) + + return &result, nil + +} diff --git a/v3/internal/buildinfo/buildinfo_test.go b/v3/internal/buildinfo/buildinfo_test.go new file mode 100644 index 000000000..ab79f63f8 --- /dev/null +++ b/v3/internal/buildinfo/buildinfo_test.go @@ -0,0 +1,13 @@ +package buildinfo + +import ( + "testing" +) + +func TestGet(t *testing.T) { + result, err := Get() + if err != nil { + t.Error(err) + } + _ = result +} diff --git a/v3/internal/capabilities/capabilities.go b/v3/internal/capabilities/capabilities.go new file mode 100644 index 000000000..af9428bb2 --- /dev/null +++ b/v3/internal/capabilities/capabilities.go @@ -0,0 +1,16 @@ +package capabilities + +import "encoding/json" + +type Capabilities struct { + HasNativeDrag bool +} + +func (c Capabilities) AsBytes() []byte { + // JSON encode + result, err := json.Marshal(c) + if err != nil { + return []byte("{}") + } + return result +} diff --git a/v3/internal/capabilities/capabilities_darwin.go b/v3/internal/capabilities/capabilities_darwin.go new file mode 100644 index 000000000..5cd0b600c --- /dev/null +++ b/v3/internal/capabilities/capabilities_darwin.go @@ -0,0 +1,9 @@ +//go:build darwin + +package capabilities + +func newCapabilities(_ string) Capabilities { + c := Capabilities{} + c.HasNativeDrag = false + return c +} diff --git a/v3/internal/capabilities/capabilities_linux.go b/v3/internal/capabilities/capabilities_linux.go new file mode 100644 index 000000000..b0debdbb0 --- /dev/null +++ b/v3/internal/capabilities/capabilities_linux.go @@ -0,0 +1,11 @@ +//go:build linux + +package capabilities + +func NewCapabilities() Capabilities { + c := Capabilities{} + // For now, assume Linux has native drag support + // TODO: Implement proper WebKit version detection + c.HasNativeDrag = true + return c +} diff --git a/v3/internal/capabilities/capabilities_windows.go b/v3/internal/capabilities/capabilities_windows.go new file mode 100644 index 000000000..78591dcf6 --- /dev/null +++ b/v3/internal/capabilities/capabilities_windows.go @@ -0,0 +1,22 @@ +//go:build windows + +package capabilities + +import "github.com/wailsapp/go-webview2/webviewloader" + +type version string + +func (v version) IsAtLeast(input string) bool { + result, err := webviewloader.CompareBrowserVersions(string(v), input) + if err != nil { + return false + } + return result >= 0 +} + +func NewCapabilities(webview2version string) Capabilities { + webview2 := version(webview2version) + c := Capabilities{} + c.HasNativeDrag = webview2.IsAtLeast("113.0.0.0") + return c +} diff --git a/v3/internal/changelog/changelog.go b/v3/internal/changelog/changelog.go new file mode 100644 index 000000000..2690ea674 --- /dev/null +++ b/v3/internal/changelog/changelog.go @@ -0,0 +1,80 @@ +package changelog + +import ( + "fmt" + "io" + "os" + "strings" +) + +// ProcessorResult contains the results of processing a changelog file +type ProcessorResult struct { + Entry *ChangelogEntry + ValidationResult ValidationResult + HasContent bool +} + +// Processor handles the complete changelog processing pipeline +type Processor struct { + parser *Parser + validator *Validator +} + +// NewProcessor creates a new changelog processor with parser and validator +func NewProcessor() *Processor { + return &Processor{ + parser: NewParser(), + validator: NewValidator(), + } +} + +// ProcessFile processes a changelog file and returns the parsed and validated entry +func (p *Processor) ProcessFile(filePath string) (*ProcessorResult, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, fmt.Errorf("failed to open changelog file %s: %w", filePath, err) + } + defer file.Close() + + return p.ProcessReader(file) +} + +// ProcessReader processes changelog content from a reader +func (p *Processor) ProcessReader(reader io.Reader) (*ProcessorResult, error) { + // Parse the content + entry, err := p.parser.ParseContent(reader) + if err != nil { + return nil, fmt.Errorf("failed to parse changelog content: %w", err) + } + + // Validate the parsed entry + validationResult := p.validator.ValidateEntry(entry) + + result := &ProcessorResult{ + Entry: entry, + ValidationResult: validationResult, + HasContent: entry.HasContent(), + } + + return result, nil +} + +// ProcessString processes changelog content from a string +func (p *Processor) ProcessString(content string) (*ProcessorResult, error) { + return p.ProcessReader(strings.NewReader(content)) +} + +// ValidateFile validates a changelog file without parsing it into an entry +func (p *Processor) ValidateFile(filePath string) (ValidationResult, error) { + content, err := os.ReadFile(filePath) + if err != nil { + return ValidationResult{Valid: false}, fmt.Errorf("failed to read changelog file %s: %w", filePath, err) + } + + return p.validator.ValidateContent(string(content)), nil +} + +// ValidateString validates changelog content from a string +func (p *Processor) ValidateString(content string) ValidationResult { + return p.validator.ValidateContent(content) +} diff --git a/v3/internal/changelog/changelog_test.go b/v3/internal/changelog/changelog_test.go new file mode 100644 index 000000000..88a2ddbd0 --- /dev/null +++ b/v3/internal/changelog/changelog_test.go @@ -0,0 +1,296 @@ +package changelog + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestNewProcessor(t *testing.T) { + processor := NewProcessor() + if processor == nil { + t.Fatal("NewProcessor() returned nil") + } + if processor.parser == nil { + t.Error("parser not initialized") + } + if processor.validator == nil { + t.Error("validator not initialized") + } +} + +func TestProcessString_ValidContent(t *testing.T) { + processor := NewProcessor() + content := `# Unreleased Changes + +## Added +- Add support for custom window icons in application options +- Add new SetWindowIcon() method to runtime API (#1234) + +## Fixed +- Fix memory leak in event system during window close operations (#5678)` + + result, err := processor.ProcessString(content) + if err != nil { + t.Fatalf("ProcessString() returned error: %v", err) + } + + if !result.HasContent { + t.Error("Result should have content") + } + + if !result.ValidationResult.Valid { + t.Errorf("Validation should pass, got errors: %s", result.ValidationResult.GetValidationSummary()) + } + + // Check parsed content + if len(result.Entry.Added) != 2 { + t.Errorf("Expected 2 Added items, got %d", len(result.Entry.Added)) + } + if len(result.Entry.Fixed) != 1 { + t.Errorf("Expected 1 Fixed item, got %d", len(result.Entry.Fixed)) + } +} + +func TestProcessString_InvalidContent(t *testing.T) { + processor := NewProcessor() + content := `# Unreleased Changes + +## Added +- Short +- TODO: add proper description + +## InvalidSection +- This section is invalid` + + result, err := processor.ProcessString(content) + if err != nil { + t.Fatalf("ProcessString() returned error: %v", err) + } + + if result.ValidationResult.Valid { + t.Error("Validation should fail for invalid content") + } + + // The parser will parse the Added section correctly (2 items) + // The InvalidSection won't be parsed since it's not a valid section name + if len(result.Entry.Added) != 2 { + t.Errorf("Expected 2 Added items, got %d", len(result.Entry.Added)) + } + + // Should have validation errors + if len(result.ValidationResult.Errors) == 0 { + t.Error("Should have validation errors") + } +} + +func TestProcessString_EmptyContent(t *testing.T) { + processor := NewProcessor() + content := `# Unreleased Changes + +## Added + + +## Changed +` + + result, err := processor.ProcessString(content) + if err != nil { + t.Fatalf("ProcessString() returned error: %v", err) + } + + if result.HasContent { + t.Error("Result should not have content") + } + + if result.ValidationResult.Valid { + t.Error("Validation should fail for empty content") + } +} + +func TestProcessFile_ValidFile(t *testing.T) { + processor := NewProcessor() + + // Create a temporary file + tmpDir := t.TempDir() + filePath := filepath.Join(tmpDir, "test_changelog.md") + + content := `# Unreleased Changes + +## Added +- Add support for custom window icons in application options +- Add new SetWindowIcon() method to runtime API (#1234) + +## Fixed +- Fix memory leak in event system during window close operations (#5678)` + + err := os.WriteFile(filePath, []byte(content), 0644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + result, err := processor.ProcessFile(filePath) + if err != nil { + t.Fatalf("ProcessFile() returned error: %v", err) + } + + if !result.HasContent { + t.Error("Result should have content") + } + + if !result.ValidationResult.Valid { + t.Errorf("Validation should pass, got errors: %s", result.ValidationResult.GetValidationSummary()) + } + + // Check parsed content + if len(result.Entry.Added) != 2 { + t.Errorf("Expected 2 Added items, got %d", len(result.Entry.Added)) + } + if len(result.Entry.Fixed) != 1 { + t.Errorf("Expected 1 Fixed item, got %d", len(result.Entry.Fixed)) + } +} + +func TestProcessFile_NonexistentFile(t *testing.T) { + processor := NewProcessor() + + result, err := processor.ProcessFile("/nonexistent/file.md") + if err == nil { + t.Error("ProcessFile() should return error for nonexistent file") + } + if result != nil { + t.Error("ProcessFile() should return nil result for nonexistent file") + } +} + +func TestValidateString_ValidContent(t *testing.T) { + processor := NewProcessor() + content := `# Unreleased Changes + +## Added +- Add support for custom window icons in application options + +## Fixed +- Fix memory leak in event system during window close operations` + + result := processor.ValidateString(content) + if !result.Valid { + t.Errorf("ValidateString() should be valid, got errors: %s", result.GetValidationSummary()) + } +} + +func TestValidateString_InvalidContent(t *testing.T) { + processor := NewProcessor() + content := `# Unreleased Changes + +## InvalidSection +- This section is invalid + +- Bullet point outside section` + + result := processor.ValidateString(content) + if result.Valid { + t.Error("ValidateString() should be invalid") + } + + if len(result.Errors) == 0 { + t.Error("ValidateString() should return validation errors") + } +} + +func TestValidateFile_ValidFile(t *testing.T) { + processor := NewProcessor() + + // Create a temporary file + tmpDir := t.TempDir() + filePath := filepath.Join(tmpDir, "test_changelog.md") + + content := `# Unreleased Changes + +## Added +- Add support for custom window icons in application options + +## Fixed +- Fix memory leak in event system during window close operations` + + err := os.WriteFile(filePath, []byte(content), 0644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + result, err := processor.ValidateFile(filePath) + if err != nil { + t.Fatalf("ValidateFile() returned error: %v", err) + } + + if !result.Valid { + t.Errorf("ValidateFile() should be valid, got errors: %s", result.GetValidationSummary()) + } +} + +func TestValidateFile_NonexistentFile(t *testing.T) { + processor := NewProcessor() + + result, err := processor.ValidateFile("/nonexistent/file.md") + if err == nil { + t.Error("ValidateFile() should return error for nonexistent file") + } + if result.Valid { + t.Error("ValidateFile() should return invalid result for nonexistent file") + } +} + +func TestProcessorResult_Integration(t *testing.T) { + processor := NewProcessor() + content := `# Unreleased Changes + +## Added +- Add comprehensive changelog processing system +- Add validation for Keep a Changelog format compliance + +## Changed +- Update changelog workflow to use automated processing + +## Fixed +- Fix parsing issues with various markdown bullet styles +- Fix validation edge cases for empty content sections` + + result, err := processor.ProcessString(content) + if err != nil { + t.Fatalf("ProcessString() returned error: %v", err) + } + + // Test that we can format the result for different outputs + changelogFormat := result.Entry.FormatForChangelog() + if !strings.Contains(changelogFormat, "### Added") { + t.Error("FormatForChangelog() should contain Added section") + } + if !strings.Contains(changelogFormat, "### Changed") { + t.Error("FormatForChangelog() should contain Changed section") + } + if !strings.Contains(changelogFormat, "### Fixed") { + t.Error("FormatForChangelog() should contain Fixed section") + } + + releaseFormat := result.Entry.FormatForRelease() + if !strings.Contains(releaseFormat, "## ✨ Added") { + t.Error("FormatForRelease() should contain Added section with emoji") + } + if !strings.Contains(releaseFormat, "## 🔄 Changed") { + t.Error("FormatForRelease() should contain Changed section with emoji") + } + if !strings.Contains(releaseFormat, "## 🐛 Fixed") { + t.Error("FormatForRelease() should contain Fixed section with emoji") + } + + // Test validation summary + if !result.ValidationResult.Valid { + t.Errorf("Validation should pass, got: %s", result.ValidationResult.GetValidationSummary()) + } + + summary := result.ValidationResult.GetValidationSummary() + if !strings.Contains(summary, "Validation passed") { + t.Errorf("Validation summary should indicate success, got: %s", summary) + } +} diff --git a/v3/internal/changelog/parser.go b/v3/internal/changelog/parser.go new file mode 100644 index 000000000..3557e26a0 --- /dev/null +++ b/v3/internal/changelog/parser.go @@ -0,0 +1,239 @@ +package changelog + +import ( + "bufio" + "fmt" + "io" + "regexp" + "strings" + "time" +) + +// ChangelogEntry represents a parsed changelog entry following Keep a Changelog format +type ChangelogEntry struct { + Version string `json:"version"` + Date time.Time `json:"date"` + Added []string `json:"added"` + Changed []string `json:"changed"` + Fixed []string `json:"fixed"` + Deprecated []string `json:"deprecated"` + Removed []string `json:"removed"` + Security []string `json:"security"` +} + +// Parser handles parsing of UNRELEASED_CHANGELOG.md files +type Parser struct { + // sectionRegex matches section headers like "## Added", "## Changed", etc. + sectionRegex *regexp.Regexp + // bulletRegex matches bullet points (- or *) + bulletRegex *regexp.Regexp +} + +// NewParser creates a new changelog parser +func NewParser() *Parser { + return &Parser{ + sectionRegex: regexp.MustCompile(`^##\s+(Added|Changed|Fixed|Deprecated|Removed|Security)\s*$`), + bulletRegex: regexp.MustCompile(`^[\s]*[-*]\s+(.+)$`), + } +} + +// ParseContent parses changelog content from a reader and returns a ChangelogEntry +func (p *Parser) ParseContent(reader io.Reader) (*ChangelogEntry, error) { + entry := &ChangelogEntry{ + Added: []string{}, + Changed: []string{}, + Fixed: []string{}, + Deprecated: []string{}, + Removed: []string{}, + Security: []string{}, + } + + scanner := bufio.NewScanner(reader) + var currentSection string + var inExampleSection bool + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + // Skip empty lines and comments + if line == "" || strings.HasPrefix(line, "") { + continue + } + + // Skip the main title + if strings.HasPrefix(line, "# Unreleased Changes") { + continue + } + + // Check if we're entering the example section + if strings.HasPrefix(line, "---") || strings.HasPrefix(line, "### Example Entries") { + inExampleSection = true + continue + } + + // Skip example section content + if inExampleSection { + continue + } + + // Check for section headers + if strings.HasPrefix(line, "##") { + if matches := p.sectionRegex.FindStringSubmatch(line); len(matches) > 1 { + currentSection = strings.ToLower(matches[1]) + } else { + // Invalid section header - reset current section + currentSection = "" + } + continue + } + + // Parse bullet points + if matches := p.bulletRegex.FindStringSubmatch(line); len(matches) > 1 { + content := strings.TrimSpace(matches[1]) + if content == "" { + continue + } + + switch currentSection { + case "added": + entry.Added = append(entry.Added, content) + case "changed": + entry.Changed = append(entry.Changed, content) + case "fixed": + entry.Fixed = append(entry.Fixed, content) + case "deprecated": + entry.Deprecated = append(entry.Deprecated, content) + case "removed": + entry.Removed = append(entry.Removed, content) + case "security": + entry.Security = append(entry.Security, content) + } + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading changelog content: %w", err) + } + + return entry, nil +} + +// HasContent checks if the changelog entry contains any actual content +func (entry *ChangelogEntry) HasContent() bool { + return len(entry.Added) > 0 || + len(entry.Changed) > 0 || + len(entry.Fixed) > 0 || + len(entry.Deprecated) > 0 || + len(entry.Removed) > 0 || + len(entry.Security) > 0 +} + +// FormatForChangelog formats the entry for insertion into the main changelog +func (entry *ChangelogEntry) FormatForChangelog() string { + var builder strings.Builder + + if len(entry.Added) > 0 { + builder.WriteString("### Added\n") + for _, item := range entry.Added { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Changed) > 0 { + builder.WriteString("### Changed\n") + for _, item := range entry.Changed { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Fixed) > 0 { + builder.WriteString("### Fixed\n") + for _, item := range entry.Fixed { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Deprecated) > 0 { + builder.WriteString("### Deprecated\n") + for _, item := range entry.Deprecated { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Removed) > 0 { + builder.WriteString("### Removed\n") + for _, item := range entry.Removed { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Security) > 0 { + builder.WriteString("### Security\n") + for _, item := range entry.Security { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + return strings.TrimSpace(builder.String()) +} + +// FormatForRelease formats the entry for GitHub release notes +func (entry *ChangelogEntry) FormatForRelease() string { + var builder strings.Builder + + if len(entry.Added) > 0 { + builder.WriteString("## ✨ Added\n") + for _, item := range entry.Added { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Changed) > 0 { + builder.WriteString("## 🔄 Changed\n") + for _, item := range entry.Changed { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Fixed) > 0 { + builder.WriteString("## 🐛 Fixed\n") + for _, item := range entry.Fixed { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Deprecated) > 0 { + builder.WriteString("## ⚠️ Deprecated\n") + for _, item := range entry.Deprecated { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Removed) > 0 { + builder.WriteString("## 🗑️ Removed\n") + for _, item := range entry.Removed { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Security) > 0 { + builder.WriteString("## 🔒 Security\n") + for _, item := range entry.Security { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + return strings.TrimSpace(builder.String()) +} diff --git a/v3/internal/changelog/parser_test.go b/v3/internal/changelog/parser_test.go new file mode 100644 index 000000000..fbdb35c8c --- /dev/null +++ b/v3/internal/changelog/parser_test.go @@ -0,0 +1,468 @@ +package changelog + +import ( + "strings" + "testing" +) + +func TestNewParser(t *testing.T) { + parser := NewParser() + if parser == nil { + t.Fatal("NewParser() returned nil") + } + if parser.sectionRegex == nil { + t.Error("sectionRegex not initialized") + } + if parser.bulletRegex == nil { + t.Error("bulletRegex not initialized") + } +} + +func TestParseContent_EmptyContent(t *testing.T) { + parser := NewParser() + reader := strings.NewReader("") + + entry, err := parser.ParseContent(reader) + if err != nil { + t.Fatalf("ParseContent() returned error: %v", err) + } + + if entry.HasContent() { + t.Error("Empty content should not have content") + } +} + +func TestParseContent_OnlyComments(t *testing.T) { + parser := NewParser() + content := `# Unreleased Changes + + + + +## Added + + +## Changed +` + + reader := strings.NewReader(content) + entry, err := parser.ParseContent(reader) + if err != nil { + t.Fatalf("ParseContent() returned error: %v", err) + } + + if entry.HasContent() { + t.Error("Content with only comments should not have content") + } +} + +func TestParseContent_BasicSections(t *testing.T) { + parser := NewParser() + content := `# Unreleased Changes + +## Added +- New feature A +- New feature B + +## Changed +- Changed feature C + +## Fixed +- Fixed bug D +- Fixed bug E + +## Deprecated +- Deprecated feature F + +## Removed +- Removed feature G + +## Security +- Security fix H` + + reader := strings.NewReader(content) + entry, err := parser.ParseContent(reader) + if err != nil { + t.Fatalf("ParseContent() returned error: %v", err) + } + + // Test Added section + if len(entry.Added) != 2 { + t.Errorf("Expected 2 Added items, got %d", len(entry.Added)) + } + if entry.Added[0] != "New feature A" { + t.Errorf("Expected 'New feature A', got '%s'", entry.Added[0]) + } + if entry.Added[1] != "New feature B" { + t.Errorf("Expected 'New feature B', got '%s'", entry.Added[1]) + } + + // Test Changed section + if len(entry.Changed) != 1 { + t.Errorf("Expected 1 Changed item, got %d", len(entry.Changed)) + } + if entry.Changed[0] != "Changed feature C" { + t.Errorf("Expected 'Changed feature C', got '%s'", entry.Changed[0]) + } + + // Test Fixed section + if len(entry.Fixed) != 2 { + t.Errorf("Expected 2 Fixed items, got %d", len(entry.Fixed)) + } + if entry.Fixed[0] != "Fixed bug D" { + t.Errorf("Expected 'Fixed bug D', got '%s'", entry.Fixed[0]) + } + if entry.Fixed[1] != "Fixed bug E" { + t.Errorf("Expected 'Fixed bug E', got '%s'", entry.Fixed[1]) + } + + // Test Deprecated section + if len(entry.Deprecated) != 1 { + t.Errorf("Expected 1 Deprecated item, got %d", len(entry.Deprecated)) + } + if entry.Deprecated[0] != "Deprecated feature F" { + t.Errorf("Expected 'Deprecated feature F', got '%s'", entry.Deprecated[0]) + } + + // Test Removed section + if len(entry.Removed) != 1 { + t.Errorf("Expected 1 Removed item, got %d", len(entry.Removed)) + } + if entry.Removed[0] != "Removed feature G" { + t.Errorf("Expected 'Removed feature G', got '%s'", entry.Removed[0]) + } + + // Test Security section + if len(entry.Security) != 1 { + t.Errorf("Expected 1 Security item, got %d", len(entry.Security)) + } + if entry.Security[0] != "Security fix H" { + t.Errorf("Expected 'Security fix H', got '%s'", entry.Security[0]) + } + + // Test HasContent + if !entry.HasContent() { + t.Error("Entry should have content") + } +} + +func TestParseContent_WithExampleSection(t *testing.T) { + parser := NewParser() + content := `# Unreleased Changes + +## Added +- Real feature A + +## Changed +- Real change B + +--- + +### Example Entries: + +**Added:** +- Example feature that should be ignored +- Another example that should be ignored + +**Fixed:** +- Example fix that should be ignored` + + reader := strings.NewReader(content) + entry, err := parser.ParseContent(reader) + if err != nil { + t.Fatalf("ParseContent() returned error: %v", err) + } + + // Should only have the real entries, not the examples + if len(entry.Added) != 1 { + t.Errorf("Expected 1 Added item, got %d", len(entry.Added)) + } + if entry.Added[0] != "Real feature A" { + t.Errorf("Expected 'Real feature A', got '%s'", entry.Added[0]) + } + + if len(entry.Changed) != 1 { + t.Errorf("Expected 1 Changed item, got %d", len(entry.Changed)) + } + if entry.Changed[0] != "Real change B" { + t.Errorf("Expected 'Real change B', got '%s'", entry.Changed[0]) + } + + // Should not have any Fixed items from examples + if len(entry.Fixed) != 0 { + t.Errorf("Expected 0 Fixed items, got %d", len(entry.Fixed)) + } +} + +func TestParseContent_DifferentBulletStyles(t *testing.T) { + parser := NewParser() + content := `# Unreleased Changes + +## Added +- Feature with dash +* Feature with asterisk + - Indented feature with dash + * Indented feature with asterisk + +## Fixed +- Feature with extra spaces +* Another with extra spaces` + + reader := strings.NewReader(content) + entry, err := parser.ParseContent(reader) + if err != nil { + t.Fatalf("ParseContent() returned error: %v", err) + } + + expectedAdded := []string{ + "Feature with dash", + "Feature with asterisk", + "Indented feature with dash", + "Indented feature with asterisk", + } + + if len(entry.Added) != len(expectedAdded) { + t.Errorf("Expected %d Added items, got %d", len(expectedAdded), len(entry.Added)) + } + + for i, expected := range expectedAdded { + if i >= len(entry.Added) || entry.Added[i] != expected { + t.Errorf("Expected Added[%d] to be '%s', got '%s'", i, expected, entry.Added[i]) + } + } + + expectedFixed := []string{ + "Feature with extra spaces", + "Another with extra spaces", + } + + if len(entry.Fixed) != len(expectedFixed) { + t.Errorf("Expected %d Fixed items, got %d", len(expectedFixed), len(entry.Fixed)) + } + + for i, expected := range expectedFixed { + if i >= len(entry.Fixed) || entry.Fixed[i] != expected { + t.Errorf("Expected Fixed[%d] to be '%s', got '%s'", i, expected, entry.Fixed[i]) + } + } +} + +func TestParseContent_EmptyBulletPoints(t *testing.T) { + parser := NewParser() + content := `# Unreleased Changes + +## Added +- Valid feature +- +- +- Another valid feature + +## Fixed +- +- Valid fix` + + reader := strings.NewReader(content) + entry, err := parser.ParseContent(reader) + if err != nil { + t.Fatalf("ParseContent() returned error: %v", err) + } + + // Should skip empty bullet points + expectedAdded := []string{ + "Valid feature", + "Another valid feature", + } + + if len(entry.Added) != len(expectedAdded) { + t.Errorf("Expected %d Added items, got %d", len(expectedAdded), len(entry.Added)) + } + + for i, expected := range expectedAdded { + if i >= len(entry.Added) || entry.Added[i] != expected { + t.Errorf("Expected Added[%d] to be '%s', got '%s'", i, expected, entry.Added[i]) + } + } + + expectedFixed := []string{"Valid fix"} + if len(entry.Fixed) != len(expectedFixed) { + t.Errorf("Expected %d Fixed items, got %d", len(expectedFixed), len(entry.Fixed)) + } + if entry.Fixed[0] != "Valid fix" { + t.Errorf("Expected 'Valid fix', got '%s'", entry.Fixed[0]) + } +} + +func TestHasContent(t *testing.T) { + tests := []struct { + name string + entry ChangelogEntry + expected bool + }{ + { + name: "Empty entry", + entry: ChangelogEntry{}, + expected: false, + }, + { + name: "Entry with Added items", + entry: ChangelogEntry{ + Added: []string{"Feature A"}, + }, + expected: true, + }, + { + name: "Entry with Changed items", + entry: ChangelogEntry{ + Changed: []string{"Change A"}, + }, + expected: true, + }, + { + name: "Entry with Fixed items", + entry: ChangelogEntry{ + Fixed: []string{"Fix A"}, + }, + expected: true, + }, + { + name: "Entry with Deprecated items", + entry: ChangelogEntry{ + Deprecated: []string{"Deprecated A"}, + }, + expected: true, + }, + { + name: "Entry with Removed items", + entry: ChangelogEntry{ + Removed: []string{"Removed A"}, + }, + expected: true, + }, + { + name: "Entry with Security items", + entry: ChangelogEntry{ + Security: []string{"Security A"}, + }, + expected: true, + }, + { + name: "Entry with multiple sections", + entry: ChangelogEntry{ + Added: []string{"Feature A"}, + Fixed: []string{"Fix A"}, + Changed: []string{"Change A"}, + }, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.entry.HasContent(); got != tt.expected { + t.Errorf("HasContent() = %v, want %v", got, tt.expected) + } + }) + } +} + +func TestFormatForChangelog(t *testing.T) { + entry := ChangelogEntry{ + Added: []string{"New feature A", "New feature B"}, + Changed: []string{"Changed feature C"}, + Fixed: []string{"Fixed bug D"}, + Deprecated: []string{"Deprecated feature E"}, + Removed: []string{"Removed feature F"}, + Security: []string{"Security fix G"}, + } + + result := entry.FormatForChangelog() + + expected := `### Added +- New feature A +- New feature B + +### Changed +- Changed feature C + +### Fixed +- Fixed bug D + +### Deprecated +- Deprecated feature E + +### Removed +- Removed feature F + +### Security +- Security fix G` + + if result != expected { + t.Errorf("FormatForChangelog() mismatch.\nExpected:\n%s\n\nGot:\n%s", expected, result) + } +} + +func TestFormatForChangelog_PartialSections(t *testing.T) { + entry := ChangelogEntry{ + Added: []string{"New feature A"}, + Fixed: []string{"Fixed bug B"}, + // Other sections empty + } + + result := entry.FormatForChangelog() + + expected := `### Added +- New feature A + +### Fixed +- Fixed bug B` + + if result != expected { + t.Errorf("FormatForChangelog() mismatch.\nExpected:\n%s\n\nGot:\n%s", expected, result) + } +} + +func TestFormatForRelease(t *testing.T) { + entry := ChangelogEntry{ + Added: []string{"New feature A", "New feature B"}, + Changed: []string{"Changed feature C"}, + Fixed: []string{"Fixed bug D"}, + Deprecated: []string{"Deprecated feature E"}, + Removed: []string{"Removed feature F"}, + Security: []string{"Security fix G"}, + } + + result := entry.FormatForRelease() + + expected := `## ✨ Added +- New feature A +- New feature B + +## 🔄 Changed +- Changed feature C + +## 🐛 Fixed +- Fixed bug D + +## ⚠️ Deprecated +- Deprecated feature E + +## 🗑️ Removed +- Removed feature F + +## 🔒 Security +- Security fix G` + + if result != expected { + t.Errorf("FormatForRelease() mismatch.\nExpected:\n%s\n\nGot:\n%s", expected, result) + } +} + +func TestFormatForRelease_EmptyEntry(t *testing.T) { + entry := ChangelogEntry{} + + result := entry.FormatForRelease() + + if result != "" { + t.Errorf("FormatForRelease() for empty entry should return empty string, got: %s", result) + } +} diff --git a/v3/internal/changelog/validator.go b/v3/internal/changelog/validator.go new file mode 100644 index 000000000..20bff9ac3 --- /dev/null +++ b/v3/internal/changelog/validator.go @@ -0,0 +1,316 @@ +package changelog + +import ( + "fmt" + "regexp" + "strings" +) + +// ValidationError represents a validation error with context +type ValidationError struct { + Field string + Message string + Line int +} + +func (e ValidationError) Error() string { + if e.Line > 0 { + return fmt.Sprintf("validation error at line %d in %s: %s", e.Line, e.Field, e.Message) + } + return fmt.Sprintf("validation error in %s: %s", e.Field, e.Message) +} + +// ValidationResult contains the results of validation +type ValidationResult struct { + Valid bool + Errors []ValidationError +} + +// Validator handles validation of changelog content and entries +type Validator struct { + // Regex patterns for validation + sectionHeaderRegex *regexp.Regexp + bulletPointRegex *regexp.Regexp + urlRegex *regexp.Regexp + issueRefRegex *regexp.Regexp +} + +// NewValidator creates a new changelog validator +func NewValidator() *Validator { + return &Validator{ + sectionHeaderRegex: regexp.MustCompile(`^##\s+(Added|Changed|Fixed|Deprecated|Removed|Security)\s*$`), + bulletPointRegex: regexp.MustCompile(`^[\s]*[-*]\s+(.+)$`), + urlRegex: regexp.MustCompile(`https?://[^\s]+`), + issueRefRegex: regexp.MustCompile(`#\d+`), + } +} + +// ValidateContent validates raw changelog content for proper formatting +func (v *Validator) ValidateContent(content string) ValidationResult { + result := ValidationResult{ + Valid: true, + Errors: []ValidationError{}, + } + + lines := strings.Split(content, "\n") + var currentSection string + var hasValidSections bool + var inExampleSection bool + lineNum := 0 + + for _, line := range lines { + lineNum++ + trimmedLine := strings.TrimSpace(line) + + // Skip empty lines and comments + if trimmedLine == "" || strings.HasPrefix(trimmedLine, "") { + continue + } + + // Skip the main title + if strings.HasPrefix(trimmedLine, "# Unreleased Changes") { + continue + } + + // Check if we're entering the example section + if strings.HasPrefix(trimmedLine, "---") || strings.HasPrefix(trimmedLine, "### Example Entries") { + inExampleSection = true + continue + } + + // Skip example section content + if inExampleSection { + continue + } + + // Check for section headers + if strings.HasPrefix(trimmedLine, "##") { + if matches := v.sectionHeaderRegex.FindStringSubmatch(trimmedLine); len(matches) > 1 { + currentSection = strings.ToLower(matches[1]) + hasValidSections = true + } else { + result.Valid = false + result.Errors = append(result.Errors, ValidationError{ + Field: "section_header", + Message: fmt.Sprintf("invalid section header format: '%s'. Expected format: '## SectionName'", trimmedLine), + Line: lineNum, + }) + } + continue + } + + // Check bullet points + if strings.HasPrefix(trimmedLine, "-") || strings.HasPrefix(trimmedLine, "*") { + if currentSection == "" { + result.Valid = false + result.Errors = append(result.Errors, ValidationError{ + Field: "bullet_point", + Message: "bullet point found outside of any section", + Line: lineNum, + }) + continue + } + + // Check for empty bullet points first (just "-" or "*" with optional whitespace) + if trimmedLine == "-" || trimmedLine == "*" || strings.TrimSpace(trimmedLine[1:]) == "" { + result.Valid = false + result.Errors = append(result.Errors, ValidationError{ + Field: "bullet_point", + Message: "empty bullet point content", + Line: lineNum, + }) + continue + } + + if matches := v.bulletPointRegex.FindStringSubmatch(trimmedLine); len(matches) > 1 { + content := strings.TrimSpace(matches[1]) + if content == "" { + result.Valid = false + result.Errors = append(result.Errors, ValidationError{ + Field: "bullet_point", + Message: "empty bullet point content", + Line: lineNum, + }) + } else { + // Validate bullet point content + v.validateBulletPointContent(content, lineNum, &result) + } + } else { + result.Valid = false + result.Errors = append(result.Errors, ValidationError{ + Field: "bullet_point", + Message: fmt.Sprintf("malformed bullet point: '%s'", trimmedLine), + Line: lineNum, + }) + } + continue + } + + // Check for unexpected content + if trimmedLine != "" && !strings.HasPrefix(trimmedLine, " + + + + + + + + + + + + + diff --git a/v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/MainActivity.java b/v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/MainActivity.java new file mode 100644 index 000000000..3067fee09 --- /dev/null +++ b/v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/MainActivity.java @@ -0,0 +1,198 @@ +package com.wails.app; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.util.Log; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.webkit.WebViewAssetLoader; +import com.wails.app.BuildConfig; + +/** + * MainActivity hosts the WebView and manages the Wails application lifecycle. + * It uses WebViewAssetLoader to serve assets from the Go library without + * requiring a network server. + */ +public class MainActivity extends AppCompatActivity { + private static final String TAG = "WailsActivity"; + private static final String WAILS_SCHEME = "https"; + private static final String WAILS_HOST = "wails.localhost"; + + private WebView webView; + private WailsBridge bridge; + private WebViewAssetLoader assetLoader; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // Initialize the native Go library + bridge = new WailsBridge(this); + bridge.initialize(); + + // Set up WebView + setupWebView(); + + // Load the application + loadApplication(); + } + + @SuppressLint("SetJavaScriptEnabled") + private void setupWebView() { + webView = findViewById(R.id.webview); + + // Configure WebView settings + WebSettings settings = webView.getSettings(); + settings.setJavaScriptEnabled(true); + settings.setDomStorageEnabled(true); + settings.setDatabaseEnabled(true); + settings.setAllowFileAccess(false); + settings.setAllowContentAccess(false); + settings.setMediaPlaybackRequiresUserGesture(false); + settings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW); + + // Enable debugging in debug builds + if (BuildConfig.DEBUG) { + WebView.setWebContentsDebuggingEnabled(true); + } + + // Set up asset loader for serving local assets + assetLoader = new WebViewAssetLoader.Builder() + .setDomain(WAILS_HOST) + .addPathHandler("/", new WailsPathHandler(bridge)) + .build(); + + // Set up WebView client to intercept requests + webView.setWebViewClient(new WebViewClient() { + @Nullable + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + String url = request.getUrl().toString(); + Log.d(TAG, "Intercepting request: " + url); + + // Handle wails.localhost requests + if (request.getUrl().getHost() != null && + request.getUrl().getHost().equals(WAILS_HOST)) { + + // For wails API calls (runtime, capabilities, etc.), we need to pass the full URL + // including query string because WebViewAssetLoader.PathHandler strips query params + String path = request.getUrl().getPath(); + if (path != null && path.startsWith("/wails/")) { + // Get full path with query string for runtime calls + String fullPath = path; + String query = request.getUrl().getQuery(); + if (query != null && !query.isEmpty()) { + fullPath = path + "?" + query; + } + Log.d(TAG, "Wails API call detected, full path: " + fullPath); + + // Call bridge directly with full path + byte[] data = bridge.serveAsset(fullPath, request.getMethod(), "{}"); + if (data != null && data.length > 0) { + java.io.InputStream inputStream = new java.io.ByteArrayInputStream(data); + java.util.Map headers = new java.util.HashMap<>(); + headers.put("Access-Control-Allow-Origin", "*"); + headers.put("Cache-Control", "no-cache"); + headers.put("Content-Type", "application/json"); + + return new WebResourceResponse( + "application/json", + "UTF-8", + 200, + "OK", + headers, + inputStream + ); + } + // Return error response if data is null + return new WebResourceResponse( + "application/json", + "UTF-8", + 500, + "Internal Error", + new java.util.HashMap<>(), + new java.io.ByteArrayInputStream("{}".getBytes()) + ); + } + + // For regular assets, use the asset loader + return assetLoader.shouldInterceptRequest(request.getUrl()); + } + + return super.shouldInterceptRequest(view, request); + } + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + Log.d(TAG, "Page loaded: " + url); + // Inject Wails runtime + bridge.injectRuntime(webView, url); + } + }); + + // Add JavaScript interface for Go communication + webView.addJavascriptInterface(new WailsJSBridge(bridge, webView), "wails"); + } + + private void loadApplication() { + // Load the main page from the asset server + String url = WAILS_SCHEME + "://" + WAILS_HOST + "/"; + Log.d(TAG, "Loading URL: " + url); + webView.loadUrl(url); + } + + /** + * Execute JavaScript in the WebView from the Go side + */ + public void executeJavaScript(final String js) { + runOnUiThread(() -> { + if (webView != null) { + webView.evaluateJavascript(js, null); + } + }); + } + + @Override + protected void onResume() { + super.onResume(); + if (bridge != null) { + bridge.onResume(); + } + } + + @Override + protected void onPause() { + super.onPause(); + if (bridge != null) { + bridge.onPause(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (bridge != null) { + bridge.shutdown(); + } + if (webView != null) { + webView.destroy(); + } + } + + @Override + public void onBackPressed() { + if (webView != null && webView.canGoBack()) { + webView.goBack(); + } else { + super.onBackPressed(); + } + } +} diff --git a/v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsBridge.java b/v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsBridge.java new file mode 100644 index 000000000..3dab65247 --- /dev/null +++ b/v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsBridge.java @@ -0,0 +1,214 @@ +package com.wails.app; + +import android.content.Context; +import android.util.Log; +import android.webkit.WebView; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * WailsBridge manages the connection between the Java/Android side and the Go native library. + * It handles: + * - Loading and initializing the native Go library + * - Serving asset requests from Go + * - Passing messages between JavaScript and Go + * - Managing callbacks for async operations + */ +public class WailsBridge { + private static final String TAG = "WailsBridge"; + + static { + // Load the native Go library + System.loadLibrary("wails"); + } + + private final Context context; + private final AtomicInteger callbackIdGenerator = new AtomicInteger(0); + private final ConcurrentHashMap pendingAssetCallbacks = new ConcurrentHashMap<>(); + private final ConcurrentHashMap pendingMessageCallbacks = new ConcurrentHashMap<>(); + private WebView webView; + private volatile boolean initialized = false; + + // Native methods - implemented in Go + private static native void nativeInit(WailsBridge bridge); + private static native void nativeShutdown(); + private static native void nativeOnResume(); + private static native void nativeOnPause(); + private static native void nativeOnPageFinished(String url); + private static native byte[] nativeServeAsset(String path, String method, String headers); + private static native String nativeHandleMessage(String message); + private static native String nativeGetAssetMimeType(String path); + + public WailsBridge(Context context) { + this.context = context; + } + + /** + * Initialize the native Go library + */ + public void initialize() { + if (initialized) { + return; + } + + Log.i(TAG, "Initializing Wails bridge..."); + try { + nativeInit(this); + initialized = true; + Log.i(TAG, "Wails bridge initialized successfully"); + } catch (Exception e) { + Log.e(TAG, "Failed to initialize Wails bridge", e); + } + } + + /** + * Shutdown the native Go library + */ + public void shutdown() { + if (!initialized) { + return; + } + + Log.i(TAG, "Shutting down Wails bridge..."); + try { + nativeShutdown(); + initialized = false; + } catch (Exception e) { + Log.e(TAG, "Error during shutdown", e); + } + } + + /** + * Called when the activity resumes + */ + public void onResume() { + if (initialized) { + nativeOnResume(); + } + } + + /** + * Called when the activity pauses + */ + public void onPause() { + if (initialized) { + nativeOnPause(); + } + } + + /** + * Serve an asset from the Go asset server + * @param path The URL path requested + * @param method The HTTP method + * @param headers The request headers as JSON + * @return The asset data, or null if not found + */ + public byte[] serveAsset(String path, String method, String headers) { + if (!initialized) { + Log.w(TAG, "Bridge not initialized, cannot serve asset: " + path); + return null; + } + + Log.d(TAG, "Serving asset: " + path); + try { + return nativeServeAsset(path, method, headers); + } catch (Exception e) { + Log.e(TAG, "Error serving asset: " + path, e); + return null; + } + } + + /** + * Get the MIME type for an asset + * @param path The asset path + * @return The MIME type string + */ + public String getAssetMimeType(String path) { + if (!initialized) { + return "application/octet-stream"; + } + + try { + String mimeType = nativeGetAssetMimeType(path); + return mimeType != null ? mimeType : "application/octet-stream"; + } catch (Exception e) { + Log.e(TAG, "Error getting MIME type for: " + path, e); + return "application/octet-stream"; + } + } + + /** + * Handle a message from JavaScript + * @param message The message from JavaScript (JSON) + * @return The response to send back to JavaScript (JSON) + */ + public String handleMessage(String message) { + if (!initialized) { + Log.w(TAG, "Bridge not initialized, cannot handle message"); + return "{\"error\":\"Bridge not initialized\"}"; + } + + Log.d(TAG, "Handling message from JS: " + message); + try { + return nativeHandleMessage(message); + } catch (Exception e) { + Log.e(TAG, "Error handling message", e); + return "{\"error\":\"" + e.getMessage() + "\"}"; + } + } + + /** + * Inject the Wails runtime JavaScript into the WebView. + * Called when the page finishes loading. + * @param webView The WebView to inject into + * @param url The URL that finished loading + */ + public void injectRuntime(WebView webView, String url) { + this.webView = webView; + // Notify Go side that page has finished loading so it can inject the runtime + Log.d(TAG, "Page finished loading: " + url + ", notifying Go side"); + if (initialized) { + nativeOnPageFinished(url); + } + } + + /** + * Execute JavaScript in the WebView (called from Go side) + * @param js The JavaScript code to execute + */ + public void executeJavaScript(String js) { + if (webView != null) { + webView.post(() -> webView.evaluateJavascript(js, null)); + } + } + + /** + * Called from Go when an event needs to be emitted to JavaScript + * @param eventName The event name + * @param eventData The event data (JSON) + */ + public void emitEvent(String eventName, String eventData) { + String js = String.format("window.wails && window.wails._emit('%s', %s);", + escapeJsString(eventName), eventData); + executeJavaScript(js); + } + + private String escapeJsString(String str) { + return str.replace("\\", "\\\\") + .replace("'", "\\'") + .replace("\n", "\\n") + .replace("\r", "\\r"); + } + + // Callback interfaces + public interface AssetCallback { + void onAssetReady(byte[] data, String mimeType); + void onAssetError(String error); + } + + public interface MessageCallback { + void onResponse(String response); + void onError(String error); + } +} diff --git a/v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsJSBridge.java b/v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsJSBridge.java new file mode 100644 index 000000000..98ae5b247 --- /dev/null +++ b/v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsJSBridge.java @@ -0,0 +1,142 @@ +package com.wails.app; + +import android.util.Log; +import android.webkit.JavascriptInterface; +import android.webkit.WebView; +import com.wails.app.BuildConfig; + +/** + * WailsJSBridge provides the JavaScript interface that allows the web frontend + * to communicate with the Go backend. This is exposed to JavaScript as the + * `window.wails` object. + * + * Similar to iOS's WKScriptMessageHandler but using Android's addJavascriptInterface. + */ +public class WailsJSBridge { + private static final String TAG = "WailsJSBridge"; + + private final WailsBridge bridge; + private final WebView webView; + + public WailsJSBridge(WailsBridge bridge, WebView webView) { + this.bridge = bridge; + this.webView = webView; + } + + /** + * Send a message to Go and return the response synchronously. + * Called from JavaScript: wails.invoke(message) + * + * @param message The message to send (JSON string) + * @return The response from Go (JSON string) + */ + @JavascriptInterface + public String invoke(String message) { + Log.d(TAG, "Invoke called: " + message); + return bridge.handleMessage(message); + } + + /** + * Send a message to Go asynchronously. + * The response will be sent back via a callback. + * Called from JavaScript: wails.invokeAsync(callbackId, message) + * + * @param callbackId The callback ID to use for the response + * @param message The message to send (JSON string) + */ + @JavascriptInterface + public void invokeAsync(final String callbackId, final String message) { + Log.d(TAG, "InvokeAsync called: " + message); + + // Handle in background thread to not block JavaScript + new Thread(() -> { + try { + String response = bridge.handleMessage(message); + sendCallback(callbackId, response, null); + } catch (Exception e) { + Log.e(TAG, "Error in async invoke", e); + sendCallback(callbackId, null, e.getMessage()); + } + }).start(); + } + + /** + * Log a message from JavaScript to Android's logcat + * Called from JavaScript: wails.log(level, message) + * + * @param level The log level (debug, info, warn, error) + * @param message The message to log + */ + @JavascriptInterface + public void log(String level, String message) { + switch (level.toLowerCase()) { + case "debug": + Log.d(TAG + "/JS", message); + break; + case "info": + Log.i(TAG + "/JS", message); + break; + case "warn": + Log.w(TAG + "/JS", message); + break; + case "error": + Log.e(TAG + "/JS", message); + break; + default: + Log.v(TAG + "/JS", message); + break; + } + } + + /** + * Get the platform name + * Called from JavaScript: wails.platform() + * + * @return "android" + */ + @JavascriptInterface + public String platform() { + return "android"; + } + + /** + * Check if we're running in debug mode + * Called from JavaScript: wails.isDebug() + * + * @return true if debug build, false otherwise + */ + @JavascriptInterface + public boolean isDebug() { + return BuildConfig.DEBUG; + } + + /** + * Send a callback response to JavaScript + */ + private void sendCallback(String callbackId, String result, String error) { + final String js; + if (error != null) { + js = String.format( + "window.wails && window.wails._callback('%s', null, '%s');", + escapeJsString(callbackId), + escapeJsString(error) + ); + } else { + js = String.format( + "window.wails && window.wails._callback('%s', %s, null);", + escapeJsString(callbackId), + result != null ? result : "null" + ); + } + + webView.post(() -> webView.evaluateJavascript(js, null)); + } + + private String escapeJsString(String str) { + if (str == null) return ""; + return str.replace("\\", "\\\\") + .replace("'", "\\'") + .replace("\n", "\\n") + .replace("\r", "\\r"); + } +} diff --git a/v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsPathHandler.java b/v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsPathHandler.java new file mode 100644 index 000000000..326fa9b4d --- /dev/null +++ b/v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsPathHandler.java @@ -0,0 +1,118 @@ +package com.wails.app; + +import android.net.Uri; +import android.util.Log; +import android.webkit.WebResourceResponse; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.webkit.WebViewAssetLoader; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * WailsPathHandler implements WebViewAssetLoader.PathHandler to serve assets + * from the Go asset server. This allows the WebView to load assets without + * using a network server, similar to iOS's WKURLSchemeHandler. + */ +public class WailsPathHandler implements WebViewAssetLoader.PathHandler { + private static final String TAG = "WailsPathHandler"; + + private final WailsBridge bridge; + + public WailsPathHandler(WailsBridge bridge) { + this.bridge = bridge; + } + + @Nullable + @Override + public WebResourceResponse handle(@NonNull String path) { + Log.d(TAG, "Handling path: " + path); + + // Normalize path + if (path.isEmpty() || path.equals("/")) { + path = "/index.html"; + } + + // Get asset from Go + byte[] data = bridge.serveAsset(path, "GET", "{}"); + + if (data == null || data.length == 0) { + Log.w(TAG, "Asset not found: " + path); + return null; // Return null to let WebView handle 404 + } + + // Determine MIME type + String mimeType = bridge.getAssetMimeType(path); + Log.d(TAG, "Serving " + path + " with type " + mimeType + " (" + data.length + " bytes)"); + + // Create response + InputStream inputStream = new ByteArrayInputStream(data); + Map headers = new HashMap<>(); + headers.put("Access-Control-Allow-Origin", "*"); + headers.put("Cache-Control", "no-cache"); + + return new WebResourceResponse( + mimeType, + "UTF-8", + 200, + "OK", + headers, + inputStream + ); + } + + /** + * Determine MIME type from file extension + */ + private String getMimeType(String path) { + String lowerPath = path.toLowerCase(); + + if (lowerPath.endsWith(".html") || lowerPath.endsWith(".htm")) { + return "text/html"; + } else if (lowerPath.endsWith(".js") || lowerPath.endsWith(".mjs")) { + return "application/javascript"; + } else if (lowerPath.endsWith(".css")) { + return "text/css"; + } else if (lowerPath.endsWith(".json")) { + return "application/json"; + } else if (lowerPath.endsWith(".png")) { + return "image/png"; + } else if (lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg")) { + return "image/jpeg"; + } else if (lowerPath.endsWith(".gif")) { + return "image/gif"; + } else if (lowerPath.endsWith(".svg")) { + return "image/svg+xml"; + } else if (lowerPath.endsWith(".ico")) { + return "image/x-icon"; + } else if (lowerPath.endsWith(".woff")) { + return "font/woff"; + } else if (lowerPath.endsWith(".woff2")) { + return "font/woff2"; + } else if (lowerPath.endsWith(".ttf")) { + return "font/ttf"; + } else if (lowerPath.endsWith(".eot")) { + return "application/vnd.ms-fontobject"; + } else if (lowerPath.endsWith(".xml")) { + return "application/xml"; + } else if (lowerPath.endsWith(".txt")) { + return "text/plain"; + } else if (lowerPath.endsWith(".wasm")) { + return "application/wasm"; + } else if (lowerPath.endsWith(".mp3")) { + return "audio/mpeg"; + } else if (lowerPath.endsWith(".mp4")) { + return "video/mp4"; + } else if (lowerPath.endsWith(".webm")) { + return "video/webm"; + } else if (lowerPath.endsWith(".webp")) { + return "image/webp"; + } + + return "application/octet-stream"; + } +} diff --git a/v3/internal/commands/build_assets/android/app/src/main/res/layout/activity_main.xml b/v3/internal/commands/build_assets/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..f278384c7 --- /dev/null +++ b/v3/internal/commands/build_assets/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..9409abebe Binary files /dev/null and b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..9409abebe Binary files /dev/null and b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..5b6acc048 Binary files /dev/null and b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..5b6acc048 Binary files /dev/null and b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..1c2c66452 Binary files /dev/null and b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..1c2c66452 Binary files /dev/null and b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..be557d897 Binary files /dev/null and b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..be557d897 Binary files /dev/null and b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4507f32a5 Binary files /dev/null and b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..4507f32a5 Binary files /dev/null and b/v3/internal/commands/build_assets/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/v3/internal/commands/build_assets/android/app/src/main/res/values/colors.xml b/v3/internal/commands/build_assets/android/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..dd33f3b7d --- /dev/null +++ b/v3/internal/commands/build_assets/android/app/src/main/res/values/colors.xml @@ -0,0 +1,8 @@ + + + #3574D4 + #2C5FB8 + #1B2636 + #FFFFFFFF + #FF000000 + diff --git a/v3/internal/commands/build_assets/android/app/src/main/res/values/strings.xml b/v3/internal/commands/build_assets/android/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..3ed9e4717 --- /dev/null +++ b/v3/internal/commands/build_assets/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Wails App + diff --git a/v3/internal/commands/build_assets/android/app/src/main/res/values/themes.xml b/v3/internal/commands/build_assets/android/app/src/main/res/values/themes.xml new file mode 100644 index 000000000..be8a282b2 --- /dev/null +++ b/v3/internal/commands/build_assets/android/app/src/main/res/values/themes.xml @@ -0,0 +1,14 @@ + + + + diff --git a/v3/internal/commands/build_assets/android/build.gradle b/v3/internal/commands/build_assets/android/build.gradle new file mode 100644 index 000000000..d7fbab39a --- /dev/null +++ b/v3/internal/commands/build_assets/android/build.gradle @@ -0,0 +1,4 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id 'com.android.application' version '8.7.3' apply false +} diff --git a/v3/internal/commands/build_assets/android/gradle.properties b/v3/internal/commands/build_assets/android/gradle.properties new file mode 100644 index 000000000..b9d4426d5 --- /dev/null +++ b/v3/internal/commands/build_assets/android/gradle.properties @@ -0,0 +1,26 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/build/optimize-your-build#parallel +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true + +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true diff --git a/v3/internal/commands/build_assets/android/gradle/wrapper/gradle-wrapper.jar b/v3/internal/commands/build_assets/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..f8e1ee312 Binary files /dev/null and b/v3/internal/commands/build_assets/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/v3/internal/commands/build_assets/android/gradle/wrapper/gradle-wrapper.properties b/v3/internal/commands/build_assets/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..23449a2b5 --- /dev/null +++ b/v3/internal/commands/build_assets/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/v3/internal/commands/build_assets/android/gradlew b/v3/internal/commands/build_assets/android/gradlew new file mode 100755 index 000000000..adff685a0 --- /dev/null +++ b/v3/internal/commands/build_assets/android/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/v3/internal/commands/build_assets/android/gradlew.bat b/v3/internal/commands/build_assets/android/gradlew.bat new file mode 100644 index 000000000..e509b2dd8 --- /dev/null +++ b/v3/internal/commands/build_assets/android/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/v3/internal/commands/build_assets/android/main_android.go b/v3/internal/commands/build_assets/android/main_android.go new file mode 100644 index 000000000..70a716473 --- /dev/null +++ b/v3/internal/commands/build_assets/android/main_android.go @@ -0,0 +1,11 @@ +//go:build android + +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +func init() { + // Register main function to be called when the Android app initializes + // This is necessary because in c-shared build mode, main() is not automatically called + application.RegisterAndroidMain(main) +} diff --git a/v3/internal/commands/build_assets/android/scripts/deps/install_deps.go b/v3/internal/commands/build_assets/android/scripts/deps/install_deps.go new file mode 100644 index 000000000..d9dfedf80 --- /dev/null +++ b/v3/internal/commands/build_assets/android/scripts/deps/install_deps.go @@ -0,0 +1,151 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +func main() { + fmt.Println("Checking Android development dependencies...") + fmt.Println() + + errors := []string{} + + // Check Go + if !checkCommand("go", "version") { + errors = append(errors, "Go is not installed. Install from https://go.dev/dl/") + } else { + fmt.Println("✓ Go is installed") + } + + // Check ANDROID_HOME + androidHome := os.Getenv("ANDROID_HOME") + if androidHome == "" { + androidHome = os.Getenv("ANDROID_SDK_ROOT") + } + if androidHome == "" { + // Try common default locations + home, _ := os.UserHomeDir() + possiblePaths := []string{ + filepath.Join(home, "Android", "Sdk"), + filepath.Join(home, "Library", "Android", "sdk"), + "/usr/local/share/android-sdk", + } + for _, p := range possiblePaths { + if _, err := os.Stat(p); err == nil { + androidHome = p + break + } + } + } + + if androidHome == "" { + errors = append(errors, "ANDROID_HOME not set. Install Android Studio and set ANDROID_HOME environment variable") + } else { + fmt.Printf("✓ ANDROID_HOME: %s\n", androidHome) + } + + // Check adb + if !checkCommand("adb", "version") { + if androidHome != "" { + platformTools := filepath.Join(androidHome, "platform-tools") + errors = append(errors, fmt.Sprintf("adb not found. Add %s to PATH", platformTools)) + } else { + errors = append(errors, "adb not found. Install Android SDK Platform-Tools") + } + } else { + fmt.Println("✓ adb is installed") + } + + // Check emulator + if !checkCommand("emulator", "-list-avds") { + if androidHome != "" { + emulatorPath := filepath.Join(androidHome, "emulator") + errors = append(errors, fmt.Sprintf("emulator not found. Add %s to PATH", emulatorPath)) + } else { + errors = append(errors, "emulator not found. Install Android Emulator via SDK Manager") + } + } else { + fmt.Println("✓ Android Emulator is installed") + } + + // Check NDK + ndkHome := os.Getenv("ANDROID_NDK_HOME") + if ndkHome == "" && androidHome != "" { + // Look for NDK in default location + ndkDir := filepath.Join(androidHome, "ndk") + if entries, err := os.ReadDir(ndkDir); err == nil { + for _, entry := range entries { + if entry.IsDir() { + ndkHome = filepath.Join(ndkDir, entry.Name()) + break + } + } + } + } + + if ndkHome == "" { + errors = append(errors, "Android NDK not found. Install NDK via Android Studio > SDK Manager > SDK Tools > NDK (Side by side)") + } else { + fmt.Printf("✓ Android NDK: %s\n", ndkHome) + } + + // Check Java + if !checkCommand("java", "-version") { + errors = append(errors, "Java not found. Install JDK 11+ (OpenJDK recommended)") + } else { + fmt.Println("✓ Java is installed") + } + + // Check for AVD (Android Virtual Device) + if checkCommand("emulator", "-list-avds") { + cmd := exec.Command("emulator", "-list-avds") + output, err := cmd.Output() + if err == nil && len(strings.TrimSpace(string(output))) > 0 { + avds := strings.Split(strings.TrimSpace(string(output)), "\n") + fmt.Printf("✓ Found %d Android Virtual Device(s)\n", len(avds)) + } else { + fmt.Println("⚠ No Android Virtual Devices found. Create one via Android Studio > Tools > Device Manager") + } + } + + fmt.Println() + + if len(errors) > 0 { + fmt.Println("❌ Missing dependencies:") + for _, err := range errors { + fmt.Printf(" - %s\n", err) + } + fmt.Println() + fmt.Println("Setup instructions:") + fmt.Println("1. Install Android Studio: https://developer.android.com/studio") + fmt.Println("2. Open SDK Manager and install:") + fmt.Println(" - Android SDK Platform (API 34)") + fmt.Println(" - Android SDK Build-Tools") + fmt.Println(" - Android SDK Platform-Tools") + fmt.Println(" - Android Emulator") + fmt.Println(" - NDK (Side by side)") + fmt.Println("3. Set environment variables:") + if runtime.GOOS == "darwin" { + fmt.Println(" export ANDROID_HOME=$HOME/Library/Android/sdk") + } else { + fmt.Println(" export ANDROID_HOME=$HOME/Android/Sdk") + } + fmt.Println(" export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator") + fmt.Println("4. Create an AVD via Android Studio > Tools > Device Manager") + os.Exit(1) + } + + fmt.Println("✓ All Android development dependencies are installed!") +} + +func checkCommand(name string, args ...string) bool { + cmd := exec.Command(name, args...) + cmd.Stdout = nil + cmd.Stderr = nil + return cmd.Run() == nil +} diff --git a/v3/internal/commands/build_assets/android/settings.gradle b/v3/internal/commands/build_assets/android/settings.gradle new file mode 100644 index 000000000..a3f3ec3d4 --- /dev/null +++ b/v3/internal/commands/build_assets/android/settings.gradle @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "WailsApp" +include ':app' diff --git a/v3/internal/commands/build_assets/appicon.icon/Assets/wails_icon_vector.svg b/v3/internal/commands/build_assets/appicon.icon/Assets/wails_icon_vector.svg new file mode 100644 index 000000000..b099222f2 --- /dev/null +++ b/v3/internal/commands/build_assets/appicon.icon/Assets/wails_icon_vector.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/v3/internal/commands/build_assets/appicon.icon/icon.json b/v3/internal/commands/build_assets/appicon.icon/icon.json new file mode 100644 index 000000000..ecf18497c --- /dev/null +++ b/v3/internal/commands/build_assets/appicon.icon/icon.json @@ -0,0 +1,51 @@ +{ + "fill" : { + "automatic-gradient" : "extended-gray:1.00000,1.00000" + }, + "groups" : [ + { + "layers" : [ + { + "fill-specializations" : [ + { + "appearance" : "dark", + "value" : { + "solid" : "srgb:0.92143,0.92145,0.92144,1.00000" + } + }, + { + "appearance" : "tinted", + "value" : { + "solid" : "srgb:0.83742,0.83744,0.83743,1.00000" + } + } + ], + "image-name" : "wails_icon_vector.svg", + "name" : "wails_icon_vector", + "position" : { + "scale" : 1.25, + "translation-in-points" : [ + 36.890625, + 4.96875 + ] + } + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "specular" : true, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "circles" : [ + "watchOS" + ], + "squares" : "shared" + } +} \ No newline at end of file diff --git a/v3/internal/commands/build_assets/appicon.png b/v3/internal/commands/build_assets/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/commands/build_assets/appicon.png differ diff --git a/v3/internal/commands/build_assets/config.yml b/v3/internal/commands/build_assets/config.yml new file mode 100644 index 000000000..03cbfa9dd --- /dev/null +++ b/v3/internal/commands/build_assets/config.yml @@ -0,0 +1,78 @@ +# This file contains the configuration for this project. +# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets. +# Note that this will overwrite any changes you have made to the assets. +version: '3' + +# This information is used to generate the build assets. +info: + companyName: "My Company" # The name of the company + productName: "My Product" # The name of the application + productIdentifier: "com.mycompany.myproduct" # The unique product identifier + description: "A program that does X" # The application description + copyright: "(c) 2025, My Company" # Copyright text + comments: "Some Product Comments" # Comments + version: "0.0.1" # The application version + # cfBundleIconName: "appicon" # The macOS icon name in Assets.car icon bundles (optional) + # # Should match the name of your .icon file without the extension + # # If not set and Assets.car exists, defaults to "appicon" + +# iOS build configuration (uncomment to customise iOS project generation) +# Note: Keys under `ios` OVERRIDE values under `info` when set. +# ios: +# # The iOS bundle identifier used in the generated Xcode project (CFBundleIdentifier) +# bundleID: "com.mycompany.myproduct" +# # The display name shown under the app icon (CFBundleDisplayName/CFBundleName) +# displayName: "My Product" +# # The app version to embed in Info.plist (CFBundleShortVersionString/CFBundleVersion) +# version: "0.0.1" +# # The company/organisation name for templates and project settings +# company: "My Company" +# # Additional comments to embed in Info.plist metadata +# comments: "Some Product Comments" + +# Dev mode configuration +dev_mode: + root_path: . + log_level: warn + debounce: 1000 + ignore: + dir: + - .git + - node_modules + - frontend + - bin + file: + - .DS_Store + - .gitignore + - .gitkeep + watched_extension: + - "*.go" + - "*.js" # Watch for changes to JS/TS files included using the //wails:include directive. + - "*.ts" # The frontend directory will be excluded entirely by the setting above. + git_ignore: true + executes: + - cmd: wails3 build DEV=true + type: blocking + - cmd: wails3 task common:dev:frontend + type: background + - cmd: wails3 task run + type: primary + +# File Associations +# More information at: https://v3.wails.io/noit/done/yet +fileAssociations: +# - ext: wails +# name: Wails +# description: Wails Application File +# iconName: wailsFileIcon +# role: Editor +# - ext: jpg +# name: JPEG +# description: Image File +# iconName: jpegFileIcon +# role: Editor +# mimeType: image/jpeg # (optional) + +# Other data +other: + - name: My Other Data \ No newline at end of file diff --git a/v3/internal/commands/build_assets/darwin/Assets.car b/v3/internal/commands/build_assets/darwin/Assets.car new file mode 100644 index 000000000..4def9c322 Binary files /dev/null and b/v3/internal/commands/build_assets/darwin/Assets.car differ diff --git a/v3/internal/commands/build_assets/darwin/Taskfile.yml b/v3/internal/commands/build_assets/darwin/Taskfile.yml new file mode 100644 index 000000000..041bd2091 --- /dev/null +++ b/v3/internal/commands/build_assets/darwin/Taskfile.yml @@ -0,0 +1,207 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +vars: + # Signing configuration - edit these values for your project + # SIGN_IDENTITY: "Developer ID Application: Your Company (TEAMID)" + # KEYCHAIN_PROFILE: "my-notarize-profile" + # ENTITLEMENTS: "build/darwin/entitlements.plist" + + # Docker image for cross-compilation (used when building on non-macOS) + CROSS_IMAGE: wails-cross + +tasks: + build: + summary: Builds the application + cmds: + - task: '{{if eq OS "darwin"}}build:native{{else}}build:docker{{end}}' + vars: + ARCH: '{{.ARCH}}' + DEV: '{{.DEV}}' + OUTPUT: '{{.OUTPUT}}' + vars: + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + + build:native: + summary: Builds the application natively on macOS + internal: true + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + DEV: + ref: .DEV + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}} + vars: + BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + env: + GOOS: darwin + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + CGO_CFLAGS: "-mmacosx-version-min=10.15" + CGO_LDFLAGS: "-mmacosx-version-min=10.15" + MACOSX_DEPLOYMENT_TARGET: "10.15" + + build:docker: + summary: Cross-compiles for macOS using Docker (for Linux/Windows hosts) + internal: true + deps: + - task: common:build:frontend + - task: common:generate:icons + preconditions: + - sh: docker info > /dev/null 2>&1 + msg: "Docker is required for cross-compilation. Please install Docker." + - sh: docker image inspect {{.CROSS_IMAGE}} > /dev/null 2>&1 + msg: | + Docker image '{{.CROSS_IMAGE}}' not found. + Build it first: wails3 task setup:docker + cmds: + - docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{.CROSS_IMAGE}} darwin {{.DOCKER_ARCH}} + - docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin + - mkdir -p {{.BIN_DIR}} + - mv "bin/{{.APP_NAME}}-darwin-{{.DOCKER_ARCH}}" "{{.OUTPUT}}" + vars: + DOCKER_ARCH: '{{if eq .ARCH "arm64"}}arm64{{else if eq .ARCH "amd64"}}amd64{{else}}arm64{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + # Mount Go module cache for faster builds + GO_CACHE_MOUNT: + sh: 'echo "-v ${GOPATH:-$HOME/go}/pkg/mod:/go/pkg/mod"' + # Extract replace directives from go.mod and create -v mounts for each + # Handles both relative (=> ../) and absolute (=> /) paths + REPLACE_MOUNTS: + sh: | + grep -E '^replace .* => ' go.mod 2>/dev/null | while read -r line; do + path=$(echo "$line" | sed -E 's/^replace .* => //' | tr -d '\r') + # Convert relative paths to absolute + if [ "${path#/}" = "$path" ]; then + path="$(cd "$(dirname "$path")" 2>/dev/null && pwd)/$(basename "$path")" + fi + # Only mount if directory exists + if [ -d "$path" ]; then + echo "-v $path:$path:ro" + fi + done | tr '\n' ' ' + + build:universal: + summary: Builds darwin universal binary (arm64 + amd64) + deps: + - task: build + vars: + ARCH: amd64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" + - task: build + vars: + ARCH: arm64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + cmds: + - task: '{{if eq OS "darwin"}}build:universal:lipo:native{{else}}build:universal:lipo:go{{end}}' + + build:universal:lipo:native: + summary: Creates universal binary using native lipo (macOS) + internal: true + cmds: + - lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + - rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + + build:universal:lipo:go: + summary: Creates universal binary using wails3 tool lipo (Linux/Windows) + internal: true + cmds: + - wails3 tool lipo -output "{{.BIN_DIR}}/{{.APP_NAME}}" -input "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" -input "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + - rm -f "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + + package: + summary: Packages the application into a `.app` bundle + deps: + - task: build + cmds: + - task: create:app:bundle + + package:universal: + summary: Packages darwin universal binary (arm64 + amd64) + deps: + - task: build:universal + cmds: + - task: create:app:bundle + + + create:app:bundle: + summary: Creates an `.app` bundle + cmds: + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS" + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources" + - cp build/darwin/icons.icns "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources" + - | + if [ -f build/darwin/Assets.car ]; then + cp build/darwin/Assets.car "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources" + fi + - cp "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS" + - cp build/darwin/Info.plist "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents" + - task: '{{if eq OS "darwin"}}codesign:adhoc{{else}}codesign:skip{{end}}' + + codesign:adhoc: + summary: Ad-hoc signs the app bundle (macOS only) + internal: true + cmds: + - codesign --force --deep --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.app" + + codesign:skip: + summary: Skips codesigning when cross-compiling + internal: true + cmds: + - 'echo "Skipping codesign (not available on {{OS}}). Sign the .app on macOS before distribution."' + + run: + cmds: + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS" + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources" + - cp build/darwin/icons.icns "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources" + - | + if [ -f build/darwin/Assets.car ]; then + cp build/darwin/Assets.car "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources" + fi + - cp "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS" + - cp "build/darwin/Info.dev.plist" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist" + - codesign --force --deep --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" + - '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}' + + sign: + summary: Signs the application bundle with Developer ID + desc: | + Signs the .app bundle for distribution. + Configure SIGN_IDENTITY in the vars section at the top of this file. + deps: + - task: package + cmds: + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}.app" --identity "{{.SIGN_IDENTITY}}" {{if .ENTITLEMENTS}}--entitlements {{.ENTITLEMENTS}}{{end}} + preconditions: + - sh: '[ -n "{{.SIGN_IDENTITY}}" ]' + msg: "SIGN_IDENTITY is required. Set it in the vars section at the top of build/darwin/Taskfile.yml" + + sign:notarize: + summary: Signs and notarizes the application bundle + desc: | + Signs the .app bundle and submits it for notarization. + Configure SIGN_IDENTITY and KEYCHAIN_PROFILE in the vars section at the top of this file. + + Setup (one-time): + wails3 signing credentials --apple-id "you@email.com" --team-id "TEAMID" --password "app-specific-password" --profile "my-profile" + deps: + - task: package + cmds: + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}.app" --identity "{{.SIGN_IDENTITY}}" {{if .ENTITLEMENTS}}--entitlements {{.ENTITLEMENTS}}{{end}} --notarize --keychain-profile {{.KEYCHAIN_PROFILE}} + preconditions: + - sh: '[ -n "{{.SIGN_IDENTITY}}" ]' + msg: "SIGN_IDENTITY is required. Set it in the vars section at the top of build/darwin/Taskfile.yml" + - sh: '[ -n "{{.KEYCHAIN_PROFILE}}" ]' + msg: "KEYCHAIN_PROFILE is required. Set it in the vars section at the top of build/darwin/Taskfile.yml" diff --git a/v3/internal/commands/build_assets/darwin/icons.icns b/v3/internal/commands/build_assets/darwin/icons.icns new file mode 100644 index 000000000..458ce1992 Binary files /dev/null and b/v3/internal/commands/build_assets/darwin/icons.icns differ diff --git a/v3/internal/commands/build_assets/docker/Dockerfile.cross b/v3/internal/commands/build_assets/docker/Dockerfile.cross new file mode 100644 index 000000000..ddae7aa03 --- /dev/null +++ b/v3/internal/commands/build_assets/docker/Dockerfile.cross @@ -0,0 +1,198 @@ +# Cross-compile Wails v3 apps to any platform +# +# Darwin: Zig + macOS SDK +# Linux: Native GCC when host matches target, Zig for cross-arch +# Windows: Zig + bundled mingw +# +# Usage: +# docker build -t wails-cross -f Dockerfile.cross . +# docker run --rm -v $(pwd):/app wails-cross darwin arm64 +# docker run --rm -v $(pwd):/app wails-cross darwin amd64 +# docker run --rm -v $(pwd):/app wails-cross linux amd64 +# docker run --rm -v $(pwd):/app wails-cross linux arm64 +# docker run --rm -v $(pwd):/app wails-cross windows amd64 +# docker run --rm -v $(pwd):/app wails-cross windows arm64 + +FROM golang:1.25-bookworm + +ARG TARGETARCH + +# Install base tools, GCC, and GTK/WebKit dev packages +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl xz-utils nodejs npm pkg-config gcc libc6-dev \ + libgtk-3-dev libwebkit2gtk-4.1-dev \ + libgtk-4-dev libwebkitgtk-6.0-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install Zig - automatically selects correct binary for host architecture +ARG ZIG_VERSION=0.14.0 +RUN ZIG_ARCH=$(case "${TARGETARCH}" in arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \ + curl -L "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-${ZIG_ARCH}-${ZIG_VERSION}.tar.xz" \ + | tar -xJ -C /opt \ + && ln -s /opt/zig-linux-${ZIG_ARCH}-${ZIG_VERSION}/zig /usr/local/bin/zig + +# Download macOS SDK (required for darwin targets) +ARG MACOS_SDK_VERSION=14.5 +RUN curl -L "https://github.com/joseluisq/macosx-sdks/releases/download/${MACOS_SDK_VERSION}/MacOSX${MACOS_SDK_VERSION}.sdk.tar.xz" \ + | tar -xJ -C /opt \ + && mv /opt/MacOSX${MACOS_SDK_VERSION}.sdk /opt/macos-sdk + +ENV MACOS_SDK_PATH=/opt/macos-sdk + +# Create Zig CC wrappers for cross-compilation targets +# Darwin and Windows use Zig; Linux uses native GCC (run with --platform for cross-arch) + +# Darwin arm64 +COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-arm64 +#!/bin/sh +ARGS="" +SKIP_NEXT=0 +for arg in "$@"; do + if [ $SKIP_NEXT -eq 1 ]; then + SKIP_NEXT=0 + continue + fi + case "$arg" in + -target) SKIP_NEXT=1 ;; + -mmacosx-version-min=*) ;; + *) ARGS="$ARGS $arg" ;; + esac +done +exec zig cc -fno-sanitize=all -target aarch64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS +ZIGWRAP +RUN chmod +x /usr/local/bin/zcc-darwin-arm64 + +# Darwin amd64 +COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-amd64 +#!/bin/sh +ARGS="" +SKIP_NEXT=0 +for arg in "$@"; do + if [ $SKIP_NEXT -eq 1 ]; then + SKIP_NEXT=0 + continue + fi + case "$arg" in + -target) SKIP_NEXT=1 ;; + -mmacosx-version-min=*) ;; + *) ARGS="$ARGS $arg" ;; + esac +done +exec zig cc -fno-sanitize=all -target x86_64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS +ZIGWRAP +RUN chmod +x /usr/local/bin/zcc-darwin-amd64 + +# Windows amd64 - uses Zig's bundled mingw +COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-amd64 +#!/bin/sh +ARGS="" +SKIP_NEXT=0 +for arg in "$@"; do + if [ $SKIP_NEXT -eq 1 ]; then + SKIP_NEXT=0 + continue + fi + case "$arg" in + -target) SKIP_NEXT=1 ;; + -Wl,*) ;; + *) ARGS="$ARGS $arg" ;; + esac +done +exec zig cc -target x86_64-windows-gnu $ARGS +ZIGWRAP +RUN chmod +x /usr/local/bin/zcc-windows-amd64 + +# Windows arm64 - uses Zig's bundled mingw +COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-arm64 +#!/bin/sh +ARGS="" +SKIP_NEXT=0 +for arg in "$@"; do + if [ $SKIP_NEXT -eq 1 ]; then + SKIP_NEXT=0 + continue + fi + case "$arg" in + -target) SKIP_NEXT=1 ;; + -Wl,*) ;; + *) ARGS="$ARGS $arg" ;; + esac +done +exec zig cc -target aarch64-windows-gnu $ARGS +ZIGWRAP +RUN chmod +x /usr/local/bin/zcc-windows-arm64 + +# Build script +COPY <<'SCRIPT' /usr/local/bin/build.sh +#!/bin/sh +set -e + +OS=${1:-darwin} +ARCH=${2:-arm64} + +case "${OS}-${ARCH}" in + darwin-arm64|darwin-aarch64) + export CC=zcc-darwin-arm64 + export GOARCH=arm64 + export GOOS=darwin + ;; + darwin-amd64|darwin-x86_64) + export CC=zcc-darwin-amd64 + export GOARCH=amd64 + export GOOS=darwin + ;; + linux-arm64|linux-aarch64) + export CC=gcc + export GOARCH=arm64 + export GOOS=linux + ;; + linux-amd64|linux-x86_64) + export CC=gcc + export GOARCH=amd64 + export GOOS=linux + ;; + windows-arm64|windows-aarch64) + export CC=zcc-windows-arm64 + export GOARCH=arm64 + export GOOS=windows + ;; + windows-amd64|windows-x86_64) + export CC=zcc-windows-amd64 + export GOARCH=amd64 + export GOOS=windows + ;; + *) + echo "Usage: " + echo " os: darwin, linux, windows" + echo " arch: amd64, arm64" + exit 1 + ;; +esac + +export CGO_ENABLED=1 +export CGO_CFLAGS="-w" + +# Build frontend if exists and not already built (host may have built it) +if [ -d "frontend" ] && [ -f "frontend/package.json" ] && [ ! -d "frontend/dist" ]; then + (cd frontend && npm install --silent && npm run build --silent) +fi + +# Build +APP=${APP_NAME:-$(basename $(pwd))} +mkdir -p bin + +EXT="" +LDFLAGS="-s -w" +if [ "$GOOS" = "windows" ]; then + EXT=".exe" + LDFLAGS="-s -w -H windowsgui" +fi + +go build -ldflags="$LDFLAGS" -o bin/${APP}-${GOOS}-${GOARCH}${EXT} . +echo "Built: bin/${APP}-${GOOS}-${GOARCH}${EXT}" +SCRIPT +RUN chmod +x /usr/local/bin/build.sh + +WORKDIR /app +ENTRYPOINT ["/usr/local/bin/build.sh"] +CMD ["darwin", "arm64"] diff --git a/v3/internal/commands/build_assets/docker/Dockerfile.server b/v3/internal/commands/build_assets/docker/Dockerfile.server new file mode 100644 index 000000000..58fb64f76 --- /dev/null +++ b/v3/internal/commands/build_assets/docker/Dockerfile.server @@ -0,0 +1,41 @@ +# Wails Server Mode Dockerfile +# Multi-stage build for minimal image size + +# Build stage +FROM golang:alpine AS builder + +WORKDIR /app + +# Install build dependencies +RUN apk add --no-cache git + +# Copy source code +COPY . . + +# Remove local replace directive if present (for production builds) +RUN sed -i '/^replace/d' go.mod || true + +# Download dependencies +RUN go mod tidy + +# Build the server binary +RUN go build -tags server -ldflags="-s -w" -o server . + +# Runtime stage - minimal image +FROM gcr.io/distroless/static-debian12 + +# Copy the binary +COPY --from=builder /app/server /server + +# Copy frontend assets +COPY --from=builder /app/frontend/dist /frontend/dist + +# Expose the default port +EXPOSE 8080 + +# Bind to all interfaces (required for Docker) +# Can be overridden at runtime with -e WAILS_SERVER_HOST=... +ENV WAILS_SERVER_HOST=0.0.0.0 + +# Run the server +ENTRYPOINT ["/server"] diff --git a/v3/internal/commands/build_assets/ios/Taskfile.yml b/v3/internal/commands/build_assets/ios/Taskfile.yml new file mode 100644 index 000000000..8c27f0894 --- /dev/null +++ b/v3/internal/commands/build_assets/ios/Taskfile.yml @@ -0,0 +1,293 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +vars: + BUNDLE_ID: '{{.BUNDLE_ID | default "com.wails.app"}}' + # SDK_PATH is computed lazily at task-level to avoid errors on non-macOS systems + # Each task that needs it defines SDK_PATH in its own vars section + +tasks: + install:deps: + summary: Check and install iOS development dependencies + cmds: + - go run build/ios/scripts/deps/install_deps.go + env: + TASK_FORCE_YES: '{{if .YES}}true{{else}}false{{end}}' + prompt: This will check and install iOS development dependencies. Continue? + + # Note: Bindings generation may show CGO warnings for iOS C imports. + # These warnings are harmless and don't affect the generated bindings, + # as the generator only needs to parse Go types, not C implementations. + build: + summary: Creates a build of the application for iOS + deps: + - task: generate:ios:overlay + - task: generate:ios:xcode + - task: common:go:mod:tidy + - task: generate:ios:bindings + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - echo "Building iOS app {{.APP_NAME}}..." + - go build -buildmode=c-archive -overlay build/ios/xcode/overlay.json {{.BUILD_FLAGS}} -o {{.OUTPUT}}.a + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production,ios -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags ios,debug -buildvcs=false -gcflags=all="-l"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + SDK_PATH: + sh: xcrun --sdk iphonesimulator --show-sdk-path + env: + GOOS: ios + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default "arm64"}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + CGO_CFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator -mios-simulator-version-min=15.0' + CGO_LDFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator' + + compile:objc: + summary: Compile Objective-C iOS wrapper + vars: + SDK_PATH: + sh: xcrun --sdk iphonesimulator --show-sdk-path + cmds: + - xcrun -sdk iphonesimulator clang -target arm64-apple-ios15.0-simulator -isysroot {{.SDK_PATH}} -framework Foundation -framework UIKit -framework WebKit -o {{.BIN_DIR}}/{{.APP_NAME}} build/ios/main.m + - codesign --force --sign - "{{.BIN_DIR}}/{{.APP_NAME}}" + + package: + summary: Packages a production build of the application into a `.app` bundle + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:app:bundle + + create:app:bundle: + summary: Creates an iOS `.app` bundle + cmds: + - rm -rf "{{.BIN_DIR}}/{{.APP_NAME}}.app" + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app" + - cp "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}.app/" + - cp build/ios/Info.plist "{{.BIN_DIR}}/{{.APP_NAME}}.app/" + - | + # Compile asset catalog and embed icons in the app bundle + APP_BUNDLE="{{.BIN_DIR}}/{{.APP_NAME}}.app" + AC_IN="build/ios/xcode/main/Assets.xcassets" + if [ -d "$AC_IN" ]; then + TMP_AC=$(mktemp -d) + xcrun actool \ + --compile "$TMP_AC" \ + --app-icon AppIcon \ + --platform iphonesimulator \ + --minimum-deployment-target 15.0 \ + --product-type com.apple.product-type.application \ + --target-device iphone \ + --target-device ipad \ + --output-partial-info-plist "$APP_BUNDLE/assetcatalog_generated_info.plist" \ + "$AC_IN" + if [ -f "$TMP_AC/Assets.car" ]; then + cp -f "$TMP_AC/Assets.car" "$APP_BUNDLE/Assets.car" + fi + rm -rf "$TMP_AC" + if [ -f "$APP_BUNDLE/assetcatalog_generated_info.plist" ]; then + /usr/libexec/PlistBuddy -c "Merge $APP_BUNDLE/assetcatalog_generated_info.plist" "$APP_BUNDLE/Info.plist" || true + fi + fi + - codesign --force --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.app" + + deploy-simulator: + summary: Deploy to iOS Simulator + deps: [package] + cmds: + - xcrun simctl terminate booted {{.BUNDLE_ID}} 2>/dev/null || true + - xcrun simctl uninstall booted {{.BUNDLE_ID}} 2>/dev/null || true + - xcrun simctl install booted "{{.BIN_DIR}}/{{.APP_NAME}}.app" + - xcrun simctl launch booted {{.BUNDLE_ID}} + + compile:ios: + summary: Compile the iOS executable from Go archive and main.m + deps: + - task: build + vars: + SDK_PATH: + sh: xcrun --sdk iphonesimulator --show-sdk-path + cmds: + - | + MAIN_M=build/ios/xcode/main/main.m + if [ ! -f "$MAIN_M" ]; then + MAIN_M=build/ios/main.m + fi + xcrun -sdk iphonesimulator clang \ + -target arm64-apple-ios15.0-simulator \ + -isysroot {{.SDK_PATH}} \ + -framework Foundation -framework UIKit -framework WebKit \ + -framework Security -framework CoreFoundation \ + -lresolv \ + -o "{{.BIN_DIR}}/{{.APP_NAME | lower}}" \ + "$MAIN_M" "{{.BIN_DIR}}/{{.APP_NAME}}.a" + + generate:ios:bindings: + internal: true + summary: Generates bindings for iOS with proper CGO flags + sources: + - "**/*.go" + - go.mod + - go.sum + generates: + - frontend/bindings/**/* + vars: + SDK_PATH: + sh: xcrun --sdk iphonesimulator --show-sdk-path + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true + env: + GOOS: ios + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default "arm64"}}' + CGO_CFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator -mios-simulator-version-min=15.0' + CGO_LDFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator' + + ensure-simulator: + internal: true + summary: Ensure iOS Simulator is running and booted + silent: true + cmds: + - | + if ! xcrun simctl list devices booted | grep -q "Booted"; then + echo "Starting iOS Simulator..." + # Get first available iPhone device + DEVICE_ID=$(xcrun simctl list devices available | grep "iPhone" | head -1 | grep -o "[A-F0-9-]\{36\}" || true) + if [ -z "$DEVICE_ID" ]; then + echo "No iPhone simulator found. Creating one..." + RUNTIME=$(xcrun simctl list runtimes | grep iOS | tail -1 | awk '{print $NF}') + DEVICE_ID=$(xcrun simctl create "iPhone 15 Pro" "iPhone 15 Pro" "$RUNTIME") + fi + # Boot the device + echo "Booting device $DEVICE_ID..." + xcrun simctl boot "$DEVICE_ID" 2>/dev/null || true + # Open Simulator app + open -a Simulator + # Wait for boot (max 30 seconds) + for i in {1..30}; do + if xcrun simctl list devices booted | grep -q "Booted"; then + echo "Simulator booted successfully" + break + fi + sleep 1 + done + # Final check + if ! xcrun simctl list devices booted | grep -q "Booted"; then + echo "Failed to boot simulator after 30 seconds" + exit 1 + fi + fi + preconditions: + - sh: command -v xcrun + msg: "xcrun not found. Please run 'wails3 task ios:install:deps' to install iOS development dependencies" + + generate:ios:overlay: + internal: true + summary: Generate Go build overlay and iOS shim + sources: + - build/config.yml + generates: + - build/ios/xcode/overlay.json + - build/ios/xcode/gen/main_ios.gen.go + cmds: + - wails3 ios overlay:gen -out build/ios/xcode/overlay.json -config build/config.yml + + generate:ios:xcode: + internal: true + summary: Generate iOS Xcode project structure and assets + sources: + - build/config.yml + - build/appicon.png + generates: + - build/ios/xcode/main/main.m + - build/ios/xcode/main/Assets.xcassets/**/* + - build/ios/xcode/project.pbxproj + cmds: + - wails3 ios xcode:gen -outdir build/ios/xcode -config build/config.yml + + run: + summary: Run the application in iOS Simulator + deps: + - task: ensure-simulator + - task: compile:ios + cmds: + - rm -rf "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" + - cp "{{.BIN_DIR}}/{{.APP_NAME | lower}}" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/{{.APP_NAME | lower}}" + - cp build/ios/Info.dev.plist "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Info.plist" + - | + # Compile asset catalog and embed icons for dev bundle + APP_BUNDLE="{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" + AC_IN="build/ios/xcode/main/Assets.xcassets" + if [ -d "$AC_IN" ]; then + TMP_AC=$(mktemp -d) + xcrun actool \ + --compile "$TMP_AC" \ + --app-icon AppIcon \ + --platform iphonesimulator \ + --minimum-deployment-target 15.0 \ + --product-type com.apple.product-type.application \ + --target-device iphone \ + --target-device ipad \ + --output-partial-info-plist "$APP_BUNDLE/assetcatalog_generated_info.plist" \ + "$AC_IN" + if [ -f "$TMP_AC/Assets.car" ]; then + cp -f "$TMP_AC/Assets.car" "$APP_BUNDLE/Assets.car" + fi + rm -rf "$TMP_AC" + if [ -f "$APP_BUNDLE/assetcatalog_generated_info.plist" ]; then + /usr/libexec/PlistBuddy -c "Merge $APP_BUNDLE/assetcatalog_generated_info.plist" "$APP_BUNDLE/Info.plist" || true + fi + fi + - codesign --force --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" + - xcrun simctl terminate booted "com.wails.{{.APP_NAME | lower}}.dev" 2>/dev/null || true + - xcrun simctl uninstall booted "com.wails.{{.APP_NAME | lower}}.dev" 2>/dev/null || true + - xcrun simctl install booted "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" + - xcrun simctl launch booted "com.wails.{{.APP_NAME | lower}}.dev" + + xcode: + summary: Open the generated Xcode project for this app + cmds: + - task: generate:ios:xcode + - open build/ios/xcode/main.xcodeproj + + logs: + summary: Stream iOS Simulator logs filtered to this app + cmds: + - | + xcrun simctl spawn booted log stream \ + --level debug \ + --style compact \ + --predicate 'senderImagePath CONTAINS[c] "{{.APP_NAME | lower}}.app/" OR composedMessage CONTAINS[c] "{{.APP_NAME | lower}}" OR eventMessage CONTAINS[c] "{{.APP_NAME | lower}}" OR process == "{{.APP_NAME | lower}}" OR category CONTAINS[c] "{{.APP_NAME | lower}}"' + + logs:dev: + summary: Stream logs for the dev bundle (used by `task ios:run`) + cmds: + - | + xcrun simctl spawn booted log stream \ + --level debug \ + --style compact \ + --predicate 'senderImagePath CONTAINS[c] ".dev.app/" OR subsystem == "com.wails.{{.APP_NAME | lower}}.dev" OR process == "{{.APP_NAME | lower}}"' + + logs:wide: + summary: Wide log stream to help discover the exact process/bundle identifiers + cmds: + - | + xcrun simctl spawn booted log stream \ + --level debug \ + --style compact \ + --predicate 'senderImagePath CONTAINS[c] ".app/"' \ No newline at end of file diff --git a/v3/internal/commands/build_assets/ios/app_options_default.go b/v3/internal/commands/build_assets/ios/app_options_default.go new file mode 100644 index 000000000..04e4f1bc9 --- /dev/null +++ b/v3/internal/commands/build_assets/ios/app_options_default.go @@ -0,0 +1,10 @@ +//go:build !ios + +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +// modifyOptionsForIOS is a no-op on non-iOS platforms +func modifyOptionsForIOS(opts *application.Options) { + // No modifications needed for non-iOS platforms +} \ No newline at end of file diff --git a/v3/internal/commands/build_assets/ios/app_options_ios.go b/v3/internal/commands/build_assets/ios/app_options_ios.go new file mode 100644 index 000000000..8f6ac3170 --- /dev/null +++ b/v3/internal/commands/build_assets/ios/app_options_ios.go @@ -0,0 +1,11 @@ +//go:build ios + +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +// modifyOptionsForIOS adjusts the application options for iOS +func modifyOptionsForIOS(opts *application.Options) { + // Disable signal handlers on iOS to prevent crashes + opts.DisableDefaultSignalHandler = true +} \ No newline at end of file diff --git a/v3/internal/commands/build_assets/ios/icon.png b/v3/internal/commands/build_assets/ios/icon.png new file mode 100644 index 000000000..be7d59173 --- /dev/null +++ b/v3/internal/commands/build_assets/ios/icon.png @@ -0,0 +1,3 @@ +# iOS Icon Placeholder +# This file should be replaced with the actual app icon (1024x1024 PNG) +# The build process will generate all required icon sizes from this base icon \ No newline at end of file diff --git a/v3/internal/commands/build_assets/ios/main.m b/v3/internal/commands/build_assets/ios/main.m new file mode 100644 index 000000000..366767a62 --- /dev/null +++ b/v3/internal/commands/build_assets/ios/main.m @@ -0,0 +1,23 @@ +//go:build ios +// Minimal bootstrap: delegate comes from Go archive (WailsAppDelegate) +#import +#include + +// External Go initialization function from the c-archive (declare before use) +extern void WailsIOSMain(); + +int main(int argc, char * argv[]) { + @autoreleasepool { + // Disable buffering so stdout/stderr from Go log.Printf flush immediately + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + + // Start Go runtime on a background queue to avoid blocking main thread/UI + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ + WailsIOSMain(); + }); + + // Run UIApplicationMain using WailsAppDelegate provided by the Go archive + return UIApplicationMain(argc, argv, nil, @"WailsAppDelegate"); + } +} \ No newline at end of file diff --git a/v3/internal/commands/build_assets/ios/main_ios.go b/v3/internal/commands/build_assets/ios/main_ios.go new file mode 100644 index 000000000..b75a40321 --- /dev/null +++ b/v3/internal/commands/build_assets/ios/main_ios.go @@ -0,0 +1,24 @@ +//go:build ios + +package main + +import ( + "C" +) + +// For iOS builds, we need to export a function that can be called from Objective-C +// This wrapper allows us to keep the original main.go unmodified + +//export WailsIOSMain +func WailsIOSMain() { + // DO NOT lock the goroutine to the current OS thread on iOS! + // This causes signal handling issues: + // "signal 16 received on thread with no signal stack" + // "fatal error: non-Go code disabled sigaltstack" + // iOS apps run in a sandboxed environment where the Go runtime's + // signal handling doesn't work the same way as desktop platforms. + + // Call the actual main function from main.go + // This ensures all the user's code is executed + main() +} \ No newline at end of file diff --git a/v3/internal/commands/build_assets/ios/scripts/deps/install_deps.go b/v3/internal/commands/build_assets/ios/scripts/deps/install_deps.go new file mode 100644 index 000000000..88ed47a4a --- /dev/null +++ b/v3/internal/commands/build_assets/ios/scripts/deps/install_deps.go @@ -0,0 +1,319 @@ +// install_deps.go - iOS development dependency checker +// This script checks for required iOS development tools. +// It's designed to be portable across different shells by using Go instead of shell scripts. +// +// Usage: +// go run install_deps.go # Interactive mode +// TASK_FORCE_YES=true go run install_deps.go # Auto-accept prompts +// CI=true go run install_deps.go # CI mode (auto-accept) + +package main + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "strings" +) + +type Dependency struct { + Name string + CheckFunc func() (bool, string) // Returns (success, details) + Required bool + InstallCmd []string + InstallMsg string + SuccessMsg string + FailureMsg string +} + +func main() { + fmt.Println("Checking iOS development dependencies...") + fmt.Println("=" + strings.Repeat("=", 50)) + fmt.Println() + + hasErrors := false + dependencies := []Dependency{ + { + Name: "Xcode", + CheckFunc: func() (bool, string) { + // Check if xcodebuild exists + if !checkCommand([]string{"xcodebuild", "-version"}) { + return false, "" + } + // Get version info + out, err := exec.Command("xcodebuild", "-version").Output() + if err != nil { + return false, "" + } + lines := strings.Split(string(out), "\n") + if len(lines) > 0 { + return true, strings.TrimSpace(lines[0]) + } + return true, "" + }, + Required: true, + InstallMsg: "Please install Xcode from the Mac App Store:\n https://apps.apple.com/app/xcode/id497799835\n Xcode is REQUIRED for iOS development (includes iOS SDKs, simulators, and frameworks)", + SuccessMsg: "✅ Xcode found", + FailureMsg: "❌ Xcode not found (REQUIRED)", + }, + { + Name: "Xcode Developer Path", + CheckFunc: func() (bool, string) { + // Check if xcode-select points to a valid Xcode path + out, err := exec.Command("xcode-select", "-p").Output() + if err != nil { + return false, "xcode-select not configured" + } + path := strings.TrimSpace(string(out)) + + // Check if path exists and is in Xcode.app + if _, err := os.Stat(path); err != nil { + return false, "Invalid Xcode path" + } + + // Verify it's pointing to Xcode.app (not just Command Line Tools) + if !strings.Contains(path, "Xcode.app") { + return false, fmt.Sprintf("Points to %s (should be Xcode.app)", path) + } + + return true, path + }, + Required: true, + InstallCmd: []string{"sudo", "xcode-select", "-s", "/Applications/Xcode.app/Contents/Developer"}, + InstallMsg: "Xcode developer path needs to be configured", + SuccessMsg: "✅ Xcode developer path configured", + FailureMsg: "❌ Xcode developer path not configured correctly", + }, + { + Name: "iOS SDK", + CheckFunc: func() (bool, string) { + // Get the iOS Simulator SDK path + cmd := exec.Command("xcrun", "--sdk", "iphonesimulator", "--show-sdk-path") + output, err := cmd.Output() + if err != nil { + return false, "Cannot find iOS SDK" + } + sdkPath := strings.TrimSpace(string(output)) + + // Check if the SDK path exists + if _, err := os.Stat(sdkPath); err != nil { + return false, "iOS SDK path not found" + } + + // Check for UIKit framework (essential for iOS development) + uikitPath := fmt.Sprintf("%s/System/Library/Frameworks/UIKit.framework", sdkPath) + if _, err := os.Stat(uikitPath); err != nil { + return false, "UIKit.framework not found" + } + + // Get SDK version + versionCmd := exec.Command("xcrun", "--sdk", "iphonesimulator", "--show-sdk-version") + versionOut, _ := versionCmd.Output() + version := strings.TrimSpace(string(versionOut)) + + return true, fmt.Sprintf("iOS %s SDK", version) + }, + Required: true, + InstallMsg: "iOS SDK comes with Xcode. Please ensure Xcode is properly installed.", + SuccessMsg: "✅ iOS SDK found with UIKit framework", + FailureMsg: "❌ iOS SDK not found or incomplete", + }, + { + Name: "iOS Simulator Runtime", + CheckFunc: func() (bool, string) { + if !checkCommand([]string{"xcrun", "simctl", "help"}) { + return false, "" + } + // Check if we can list runtimes + out, err := exec.Command("xcrun", "simctl", "list", "runtimes").Output() + if err != nil { + return false, "Cannot access simulator" + } + // Count iOS runtimes + lines := strings.Split(string(out), "\n") + count := 0 + var versions []string + for _, line := range lines { + if strings.Contains(line, "iOS") && !strings.Contains(line, "unavailable") { + count++ + // Extract version number + if parts := strings.Fields(line); len(parts) > 2 { + for _, part := range parts { + if strings.HasPrefix(part, "(") && strings.HasSuffix(part, ")") { + versions = append(versions, strings.Trim(part, "()")) + break + } + } + } + } + } + if count > 0 { + return true, fmt.Sprintf("%d runtime(s): %s", count, strings.Join(versions, ", ")) + } + return false, "No iOS runtimes installed" + }, + Required: true, + InstallMsg: "iOS Simulator runtimes come with Xcode. You may need to download them:\n Xcode → Settings → Platforms → iOS", + SuccessMsg: "✅ iOS Simulator runtime available", + FailureMsg: "❌ iOS Simulator runtime not available", + }, + } + + // Check each dependency + for _, dep := range dependencies { + success, details := dep.CheckFunc() + if success { + msg := dep.SuccessMsg + if details != "" { + msg = fmt.Sprintf("%s (%s)", dep.SuccessMsg, details) + } + fmt.Println(msg) + } else { + fmt.Println(dep.FailureMsg) + if details != "" { + fmt.Printf(" Details: %s\n", details) + } + if dep.Required { + hasErrors = true + if len(dep.InstallCmd) > 0 { + fmt.Println() + fmt.Println(" " + dep.InstallMsg) + fmt.Printf(" Fix command: %s\n", strings.Join(dep.InstallCmd, " ")) + if promptUser("Do you want to run this command?") { + fmt.Println("Running command...") + cmd := exec.Command(dep.InstallCmd[0], dep.InstallCmd[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + if err := cmd.Run(); err != nil { + fmt.Printf("Command failed: %v\n", err) + os.Exit(1) + } + fmt.Println("✅ Command completed. Please run this check again.") + } else { + fmt.Printf(" Please run manually: %s\n", strings.Join(dep.InstallCmd, " ")) + } + } else { + fmt.Println(" " + dep.InstallMsg) + } + } + } + } + + // Check for iPhone simulators + fmt.Println() + fmt.Println("Checking for iPhone simulator devices...") + if !checkCommand([]string{"xcrun", "simctl", "list", "devices"}) { + fmt.Println("❌ Cannot check for iPhone simulators") + hasErrors = true + } else { + out, err := exec.Command("xcrun", "simctl", "list", "devices").Output() + if err != nil { + fmt.Println("❌ Failed to list simulator devices") + hasErrors = true + } else if !strings.Contains(string(out), "iPhone") { + fmt.Println("⚠️ No iPhone simulator devices found") + fmt.Println() + + // Get the latest iOS runtime + runtimeOut, err := exec.Command("xcrun", "simctl", "list", "runtimes").Output() + if err != nil { + fmt.Println(" Failed to get iOS runtimes:", err) + } else { + lines := strings.Split(string(runtimeOut), "\n") + var latestRuntime string + for _, line := range lines { + if strings.Contains(line, "iOS") && !strings.Contains(line, "unavailable") { + // Extract runtime identifier + parts := strings.Fields(line) + if len(parts) > 0 { + latestRuntime = parts[len(parts)-1] + } + } + } + + if latestRuntime == "" { + fmt.Println(" No iOS runtime found. Please install iOS simulators in Xcode:") + fmt.Println(" Xcode → Settings → Platforms → iOS") + } else { + fmt.Println(" Would you like to create an iPhone 15 Pro simulator?") + createCmd := []string{"xcrun", "simctl", "create", "iPhone 15 Pro", "iPhone 15 Pro", latestRuntime} + fmt.Printf(" Command: %s\n", strings.Join(createCmd, " ")) + if promptUser("Create simulator?") { + cmd := exec.Command(createCmd[0], createCmd[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + fmt.Printf(" Failed to create simulator: %v\n", err) + } else { + fmt.Println(" ✅ iPhone 15 Pro simulator created") + } + } else { + fmt.Println(" Skipping simulator creation") + fmt.Printf(" Create manually: %s\n", strings.Join(createCmd, " ")) + } + } + } + } else { + // Count iPhone devices + count := 0 + lines := strings.Split(string(out), "\n") + for _, line := range lines { + if strings.Contains(line, "iPhone") && !strings.Contains(line, "unavailable") { + count++ + } + } + fmt.Printf("✅ %d iPhone simulator device(s) available\n", count) + } + } + + // Final summary + fmt.Println() + fmt.Println("=" + strings.Repeat("=", 50)) + if hasErrors { + fmt.Println("❌ Some required dependencies are missing or misconfigured.") + fmt.Println() + fmt.Println("Quick setup guide:") + fmt.Println("1. Install Xcode from Mac App Store (if not installed)") + fmt.Println("2. Open Xcode once and agree to the license") + fmt.Println("3. Install additional components when prompted") + fmt.Println("4. Run: sudo xcode-select -s /Applications/Xcode.app/Contents/Developer") + fmt.Println("5. Download iOS simulators: Xcode → Settings → Platforms → iOS") + fmt.Println("6. Run this check again") + os.Exit(1) + } else { + fmt.Println("✅ All required dependencies are installed!") + fmt.Println(" You're ready for iOS development with Wails!") + } +} + +func checkCommand(args []string) bool { + if len(args) == 0 { + return false + } + cmd := exec.Command(args[0], args[1:]...) + cmd.Stdout = nil + cmd.Stderr = nil + err := cmd.Run() + return err == nil +} + +func promptUser(question string) bool { + // Check if we're in a non-interactive environment + if os.Getenv("CI") != "" || os.Getenv("TASK_FORCE_YES") == "true" { + fmt.Printf("%s [y/N]: y (auto-accepted)\n", question) + return true + } + + reader := bufio.NewReader(os.Stdin) + fmt.Printf("%s [y/N]: ", question) + + response, err := reader.ReadString('\n') + if err != nil { + return false + } + + response = strings.ToLower(strings.TrimSpace(response)) + return response == "y" || response == "yes" +} \ No newline at end of file diff --git a/v3/internal/commands/build_assets/linux/Taskfile.yml b/v3/internal/commands/build_assets/linux/Taskfile.yml new file mode 100644 index 000000000..12854847f --- /dev/null +++ b/v3/internal/commands/build_assets/linux/Taskfile.yml @@ -0,0 +1,225 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +vars: + # Signing configuration - edit these values for your project + # PGP_KEY: "path/to/signing-key.asc" + # SIGN_ROLE: "builder" # Options: origin, maint, archive, builder + # + # Password is stored securely in system keychain. Run: wails3 setup signing + + # Docker image for cross-compilation (used when building on non-Linux or no CC available) + CROSS_IMAGE: wails-cross + +tasks: + build: + summary: Builds the application for Linux + cmds: + # Linux requires CGO - use Docker when: + # 1. Cross-compiling from non-Linux, OR + # 2. No C compiler is available, OR + # 3. Target architecture differs from host architecture (cross-arch compilation) + - task: '{{if and (eq OS "linux") (eq .HAS_CC "true") (eq .TARGET_ARCH ARCH)}}build:native{{else}}build:docker{{end}}' + vars: + ARCH: '{{.ARCH}}' + DEV: '{{.DEV}}' + OUTPUT: '{{.OUTPUT}}' + vars: + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + # Determine target architecture (defaults to host ARCH if not specified) + TARGET_ARCH: '{{.ARCH | default ARCH}}' + # Check if a C compiler is available (gcc or clang) + HAS_CC: + sh: '(command -v gcc >/dev/null 2>&1 || command -v clang >/dev/null 2>&1) && echo "true" || echo "false"' + + build:native: + summary: Builds the application natively on Linux + internal: true + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + DEV: + ref: .DEV + - task: common:generate:icons + - task: generate:dotdesktop + cmds: + - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}} + vars: + BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + env: + GOOS: linux + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + + build:docker: + summary: Builds for Linux using Docker (for non-Linux hosts or when no C compiler available) + internal: true + deps: + - task: common:build:frontend + - task: common:generate:icons + - task: generate:dotdesktop + preconditions: + - sh: docker info > /dev/null 2>&1 + msg: "Docker is required for cross-compilation to Linux. Please install Docker." + - sh: docker image inspect {{.CROSS_IMAGE}} > /dev/null 2>&1 + msg: | + Docker image '{{.CROSS_IMAGE}}' not found. + Build it first: wails3 task setup:docker + cmds: + - docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" "{{.CROSS_IMAGE}}" linux {{.DOCKER_ARCH}} + - docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin + - mkdir -p {{.BIN_DIR}} + - mv "bin/{{.APP_NAME}}-linux-{{.DOCKER_ARCH}}" "{{.OUTPUT}}" + vars: + DOCKER_ARCH: '{{.ARCH | default "amd64"}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + # Mount Go module cache for faster builds + GO_CACHE_MOUNT: + sh: 'echo "-v ${GOPATH:-$HOME/go}/pkg/mod:/go/pkg/mod"' + # Extract replace directives from go.mod and create -v mounts for each + REPLACE_MOUNTS: + sh: | + grep -E '^replace .* => ' go.mod 2>/dev/null | while read -r line; do + path=$(echo "$line" | sed -E 's/^replace .* => //' | tr -d '\r') + # Convert relative paths to absolute + if [ "${path#/}" = "$path" ]; then + path="$(cd "$(dirname "$path")" 2>/dev/null && pwd)/$(basename "$path")" + fi + # Only mount if directory exists + if [ -d "$path" ]; then + echo "-v $path:$path:ro" + fi + done | tr '\n' ' ' + + package: + summary: Packages the application for Linux + deps: + - task: build + cmds: + - task: create:appimage + - task: create:deb + - task: create:rpm + - task: create:aur + + create:appimage: + summary: Creates an AppImage + dir: build/linux/appimage + deps: + - task: build + - task: generate:dotdesktop + cmds: + - cp "{{.APP_BINARY}}" "{{.APP_NAME}}" + - cp ../../appicon.png "{{.APP_NAME}}.png" + - wails3 generate appimage -binary "{{.APP_NAME}}" -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build + vars: + APP_NAME: '{{.APP_NAME}}' + APP_BINARY: '../../../bin/{{.APP_NAME}}' + ICON: '{{.APP_NAME}}.png' + DESKTOP_FILE: '../{{.APP_NAME}}.desktop' + OUTPUT_DIR: '../../../bin' + + create:deb: + summary: Creates a deb package + deps: + - task: build + cmds: + - task: generate:dotdesktop + - task: generate:deb + + create:rpm: + summary: Creates a rpm package + deps: + - task: build + cmds: + - task: generate:dotdesktop + - task: generate:rpm + + create:aur: + summary: Creates a arch linux packager package + deps: + - task: build + cmds: + - task: generate:dotdesktop + - task: generate:aur + + generate:deb: + summary: Creates a deb package + cmds: + - wails3 tool package -name "{{.APP_NAME}}" -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:rpm: + summary: Creates a rpm package + cmds: + - wails3 tool package -name "{{.APP_NAME}}" -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:aur: + summary: Creates a arch linux packager package + cmds: + - wails3 tool package -name "{{.APP_NAME}}" -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:dotdesktop: + summary: Generates a `.desktop` file + dir: build + cmds: + - mkdir -p {{.ROOT_DIR}}/build/linux/appimage + - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile "{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop" -categories "{{.CATEGORIES}}" + vars: + APP_NAME: '{{.APP_NAME}}' + EXEC: '{{.APP_NAME}}' + ICON: '{{.APP_NAME}}' + CATEGORIES: 'Development;' + OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}' + + sign:deb: + summary: Signs the DEB package + desc: | + Signs the .deb package with a PGP key. + Configure PGP_KEY in the vars section at the top of this file. + Password is retrieved from system keychain (run: wails3 setup signing) + deps: + - task: create:deb + cmds: + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}*.deb" --pgp-key {{.PGP_KEY}} {{if .SIGN_ROLE}}--role {{.SIGN_ROLE}}{{end}} + preconditions: + - sh: '[ -n "{{.PGP_KEY}}" ]' + msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml" + + sign:rpm: + summary: Signs the RPM package + desc: | + Signs the .rpm package with a PGP key. + Configure PGP_KEY in the vars section at the top of this file. + Password is retrieved from system keychain (run: wails3 setup signing) + deps: + - task: create:rpm + cmds: + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}*.rpm" --pgp-key {{.PGP_KEY}} + preconditions: + - sh: '[ -n "{{.PGP_KEY}}" ]' + msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml" + + sign:packages: + summary: Signs all Linux packages (DEB and RPM) + desc: | + Signs both .deb and .rpm packages with a PGP key. + Configure PGP_KEY in the vars section at the top of this file. + Password is retrieved from system keychain (run: wails3 setup signing) + cmds: + - task: sign:deb + - task: sign:rpm + preconditions: + - sh: '[ -n "{{.PGP_KEY}}" ]' + msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml" diff --git a/v3/internal/commands/build_assets/linux/appimage/build.sh b/v3/internal/commands/build_assets/linux/appimage/build.sh new file mode 100644 index 000000000..85901c34e --- /dev/null +++ b/v3/internal/commands/build_assets/linux/appimage/build.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Copyright (c) 2018-Present Lea Anthony +# SPDX-License-Identifier: MIT + +# Fail script on any error +set -euxo pipefail + +# Define variables +APP_DIR="${APP_NAME}.AppDir" + +# Create AppDir structure +mkdir -p "${APP_DIR}/usr/bin" +cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/" +cp "${ICON_PATH}" "${APP_DIR}/" +cp "${DESKTOP_FILE}" "${APP_DIR}/" + +if [[ $(uname -m) == *x86_64* ]]; then + # Download linuxdeploy and make it executable + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + + # Run linuxdeploy to bundle the application + ./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage +else + # Download linuxdeploy and make it executable (arm64) + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage + chmod +x linuxdeploy-aarch64.AppImage + + # Run linuxdeploy to bundle the application (arm64) + ./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage +fi + +# Rename the generated AppImage +mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage" + diff --git a/v3/internal/commands/build_assets/linux/nfpm/scripts/postinstall.sh b/v3/internal/commands/build_assets/linux/nfpm/scripts/postinstall.sh new file mode 100644 index 000000000..4bbb815a3 --- /dev/null +++ b/v3/internal/commands/build_assets/linux/nfpm/scripts/postinstall.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# Update desktop database for .desktop file changes +# This makes the application appear in application menus and registers its capabilities. +if command -v update-desktop-database >/dev/null 2>&1; then + echo "Updating desktop database..." + update-desktop-database -q /usr/share/applications +else + echo "Warning: update-desktop-database command not found. Desktop file may not be immediately recognized." >&2 +fi + +# Update MIME database for custom URL schemes (x-scheme-handler) +# This ensures the system knows how to handle your custom protocols. +if command -v update-mime-database >/dev/null 2>&1; then + echo "Updating MIME database..." + update-mime-database -n /usr/share/mime +else + echo "Warning: update-mime-database command not found. Custom URL schemes may not be immediately recognized." >&2 +fi + +exit 0 diff --git a/v3/internal/commands/build_assets/linux/nfpm/scripts/postremove.sh b/v3/internal/commands/build_assets/linux/nfpm/scripts/postremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/internal/commands/build_assets/linux/nfpm/scripts/postremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/internal/commands/build_assets/linux/nfpm/scripts/preinstall.sh b/v3/internal/commands/build_assets/linux/nfpm/scripts/preinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/internal/commands/build_assets/linux/nfpm/scripts/preinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/internal/commands/build_assets/linux/nfpm/scripts/preremove.sh b/v3/internal/commands/build_assets/linux/nfpm/scripts/preremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/internal/commands/build_assets/linux/nfpm/scripts/preremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/internal/commands/build_assets/windows/Taskfile.yml b/v3/internal/commands/build_assets/windows/Taskfile.yml new file mode 100644 index 000000000..77b620b7d --- /dev/null +++ b/v3/internal/commands/build_assets/windows/Taskfile.yml @@ -0,0 +1,183 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +vars: + # Signing configuration - edit these values for your project + # SIGN_CERTIFICATE: "path/to/certificate.pfx" + # SIGN_THUMBPRINT: "certificate-thumbprint" # Alternative to SIGN_CERTIFICATE + # TIMESTAMP_SERVER: "http://timestamp.digicert.com" + # + # Password is stored securely in system keychain. Run: wails3 setup signing + + # Docker image for cross-compilation with CGO (used when CGO_ENABLED=1 on non-Windows) + CROSS_IMAGE: wails-cross + +tasks: + build: + summary: Builds the application for Windows + cmds: + # Auto-detect CGO: if CGO_ENABLED=1, use Docker; otherwise use native Go cross-compile + - task: '{{if and (ne OS "windows") (eq .CGO_ENABLED "1")}}build:docker{{else}}build:native{{end}}' + vars: + ARCH: '{{.ARCH}}' + DEV: '{{.DEV}}' + vars: + # Default to CGO_ENABLED=0 if not explicitly set + CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}' + + build:native: + summary: Builds the application using native Go cross-compilation + internal: true + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + DEV: + ref: .DEV + - task: common:generate:icons + cmds: + - task: generate:syso + - go build {{.BUILD_FLAGS}} -o "{{.BIN_DIR}}/{{.APP_NAME}}.exe" + - cmd: powershell Remove-item *.syso + platforms: [windows] + - cmd: rm -f *.syso + platforms: [linux, darwin] + vars: + BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{end}}' + env: + GOOS: windows + CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}' + GOARCH: '{{.ARCH | default ARCH}}' + + build:docker: + summary: Cross-compiles for Windows using Docker with Zig (for CGO builds on non-Windows) + internal: true + deps: + - task: common:build:frontend + - task: common:generate:icons + preconditions: + - sh: docker info > /dev/null 2>&1 + msg: "Docker is required for CGO cross-compilation. Please install Docker." + - sh: docker image inspect {{.CROSS_IMAGE}} > /dev/null 2>&1 + msg: | + Docker image '{{.CROSS_IMAGE}}' not found. + Build it first: wails3 task setup:docker + cmds: + - task: generate:syso + - docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{.CROSS_IMAGE}} windows {{.DOCKER_ARCH}} + - docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin + - rm -f *.syso + vars: + DOCKER_ARCH: '{{.ARCH | default "amd64"}}' + # Mount Go module cache for faster builds + GO_CACHE_MOUNT: + sh: 'echo "-v ${GOPATH:-$HOME/go}/pkg/mod:/go/pkg/mod"' + # Extract replace directives from go.mod and create -v mounts for each + REPLACE_MOUNTS: + sh: | + grep -E '^replace .* => ' go.mod 2>/dev/null | while read -r line; do + path=$(echo "$line" | sed -E 's/^replace .* => //' | tr -d '\r') + # Convert relative paths to absolute + if [ "${path#/}" = "$path" ]; then + path="$(cd "$(dirname "$path")" 2>/dev/null && pwd)/$(basename "$path")" + fi + # Only mount if directory exists + if [ -d "$path" ]; then + echo "-v $path:$path:ro" + fi + done | tr '\n' ' ' + + package: + summary: Packages the application + cmds: + - task: '{{if eq (.FORMAT | default "nsis") "msix"}}create:msix:package{{else}}create:nsis:installer{{end}}' + vars: + FORMAT: '{{.FORMAT | default "nsis"}}' + + generate:syso: + summary: Generates Windows `.syso` file + dir: build + cmds: + - wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso + vars: + ARCH: '{{.ARCH | default ARCH}}' + + create:nsis:installer: + summary: Creates an NSIS installer + dir: build/windows/nsis + deps: + - task: build + cmds: + # Create the Microsoft WebView2 bootstrapper if it doesn't exist + - wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis" + - | + {{if eq OS "windows"}} + makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}\{{.BIN_DIR}}\{{.APP_NAME}}.exe" project.nsi + {{else}} + makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi + {{end}} + vars: + ARCH: '{{.ARCH | default ARCH}}' + ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}' + + create:msix:package: + summary: Creates an MSIX package + deps: + - task: build + cmds: + - |- + wails3 tool msix \ + --config "{{.ROOT_DIR}}/wails.json" \ + --name "{{.APP_NAME}}" \ + --executable "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" \ + --arch "{{.ARCH}}" \ + --out "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}-{{.ARCH}}.msix" \ + {{if .CERT_PATH}}--cert "{{.CERT_PATH}}"{{end}} \ + {{if .PUBLISHER}}--publisher "{{.PUBLISHER}}"{{end}} \ + {{if .USE_MSIX_TOOL}}--use-msix-tool{{else}}--use-makeappx{{end}} + vars: + ARCH: '{{.ARCH | default ARCH}}' + CERT_PATH: '{{.CERT_PATH | default ""}}' + PUBLISHER: '{{.PUBLISHER | default ""}}' + USE_MSIX_TOOL: '{{.USE_MSIX_TOOL | default "false"}}' + + install:msix:tools: + summary: Installs tools required for MSIX packaging + cmds: + - wails3 tool msix-install-tools + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}.exe' + + sign: + summary: Signs the Windows executable + desc: | + Signs the .exe with an Authenticode certificate. + Configure SIGN_CERTIFICATE or SIGN_THUMBPRINT in the vars section at the top of this file. + Password is retrieved from system keychain (run: wails3 setup signing) + deps: + - task: build + cmds: + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}.exe" {{if .SIGN_CERTIFICATE}}--certificate {{.SIGN_CERTIFICATE}}{{end}} {{if .SIGN_THUMBPRINT}}--thumbprint {{.SIGN_THUMBPRINT}}{{end}} {{if .TIMESTAMP_SERVER}}--timestamp {{.TIMESTAMP_SERVER}}{{end}} + preconditions: + - sh: '[ -n "{{.SIGN_CERTIFICATE}}" ] || [ -n "{{.SIGN_THUMBPRINT}}" ]' + msg: "Either SIGN_CERTIFICATE or SIGN_THUMBPRINT is required. Set it in the vars section at the top of build/windows/Taskfile.yml" + + sign:installer: + summary: Signs the NSIS installer + desc: | + Creates and signs the NSIS installer. + Configure SIGN_CERTIFICATE or SIGN_THUMBPRINT in the vars section at the top of this file. + Password is retrieved from system keychain (run: wails3 setup signing) + deps: + - task: create:nsis:installer + cmds: + - wails3 tool sign --input "build/windows/nsis/{{.APP_NAME}}-installer.exe" {{if .SIGN_CERTIFICATE}}--certificate {{.SIGN_CERTIFICATE}}{{end}} {{if .SIGN_THUMBPRINT}}--thumbprint {{.SIGN_THUMBPRINT}}{{end}} {{if .TIMESTAMP_SERVER}}--timestamp {{.TIMESTAMP_SERVER}}{{end}} + preconditions: + - sh: '[ -n "{{.SIGN_CERTIFICATE}}" ] || [ -n "{{.SIGN_THUMBPRINT}}" ]' + msg: "Either SIGN_CERTIFICATE or SIGN_THUMBPRINT is required. Set it in the vars section at the top of build/windows/Taskfile.yml" diff --git a/v3/internal/commands/build_assets/windows/icon.ico b/v3/internal/commands/build_assets/windows/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/internal/commands/build_assets/windows/icon.ico differ diff --git a/v3/internal/commands/build_assets/windows/msix/app_manifest.xml.tmpl b/v3/internal/commands/build_assets/windows/msix/app_manifest.xml.tmpl new file mode 100644 index 000000000..430f8c3a9 --- /dev/null +++ b/v3/internal/commands/build_assets/windows/msix/app_manifest.xml.tmpl @@ -0,0 +1,76 @@ + + + + + + + {{.ProductName}} + {{.ProductCompany}} + {{.ProductDescription}} + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + {{if .FileAssociations}} + + + {{.ProductName}} + Assets\FileIcon.png + {{.ProductName}} File + + {{range .FileAssociations}} + .{{.Ext}} + {{end}} + + + + {{end}} + {{range .Protocols}} + + + {{.Description}} + + + {{end}} + + + + + + + {{if .FileAssociations}} + + {{end}} + + diff --git a/v3/internal/commands/build_assets/windows/msix/template.xml.tmpl b/v3/internal/commands/build_assets/windows/msix/template.xml.tmpl new file mode 100644 index 000000000..92f217499 --- /dev/null +++ b/v3/internal/commands/build_assets/windows/msix/template.xml.tmpl @@ -0,0 +1,68 @@ + + + + + + + + + + {{if .FileAssociations}} + + {{end}} + + + + {{if .FileAssociations}} + + + + {{range .FileAssociations}} + + .{{.Ext}} + + {{end}} + + + + {{end}} + + + + + + + + + + false + {{.ProductName}} + {{.ProductCompany}} + {{.ProductDescription}} + Assets\AppIcon.png + + + + + {{.CertificatePath}} + + diff --git a/v3/internal/commands/build_assets/windows/nsis/project.nsi.tmpl b/v3/internal/commands/build_assets/windows/nsis/project.nsi.tmpl new file mode 100644 index 000000000..df4bd5438 --- /dev/null +++ b/v3/internal/commands/build_assets/windows/nsis/project.nsi.tmpl @@ -0,0 +1,114 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the wails_tools.nsh file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "my-project" # Default "{{.Name}}" +## !define INFO_COMPANYNAME "My Company" # Default "{{.ProductCompany}}" +## !define INFO_PRODUCTNAME "My Product Name" # Default "{{.ProductName}}" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.ProductVersion}}" +## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "{{.ProductCopyright}}" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + !insertmacro wails.associateCustomProtocols + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + !insertmacro wails.unassociateCustomProtocols + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/v3/internal/commands/constants.go b/v3/internal/commands/constants.go new file mode 100644 index 000000000..729d7e027 --- /dev/null +++ b/v3/internal/commands/constants.go @@ -0,0 +1,27 @@ +package commands + +import ( + "os" + + "github.com/wailsapp/wails/v3/internal/generator" +) + +type GenerateConstantsOptions struct { + ConstantsFilename string `name:"f" description:"The filename for the models file" default:"constants.go"` + OutputFilename string `name:"o" description:"The output file" default:"constants.js"` +} + +func GenerateConstants(options *GenerateConstantsOptions) error { + DisableFooter = true + goData, err := os.ReadFile(options.ConstantsFilename) + if err != nil { + return err + } + + result, err := generator.GenerateConstants(goData) + if err != nil { + return err + } + + return os.WriteFile(options.OutputFilename, []byte(result), 0644) +} diff --git a/v3/internal/commands/dev.go b/v3/internal/commands/dev.go new file mode 100644 index 000000000..f7053e377 --- /dev/null +++ b/v3/internal/commands/dev.go @@ -0,0 +1,58 @@ +package commands + +import ( + "fmt" + "net" + "os" + "strconv" + + "github.com/wailsapp/wails/v3/internal/flags" +) + +const defaultVitePort = 9245 +const wailsVitePort = "WAILS_VITE_PORT" + +type DevOptions struct { + flags.Common + + Config string `description:"The config file including path" default:"./build/config.yml"` + VitePort int `name:"port" description:"Specify the vite dev server port"` + Secure bool `name:"s" description:"Enable HTTPS"` +} + +func Dev(options *DevOptions) error { + host := "localhost" + + // flag takes precedence over environment variable + var port int + if options.VitePort != 0 { + port = options.VitePort + } else if p, err := strconv.Atoi(os.Getenv(wailsVitePort)); err == nil { + port = p + } else { + port = defaultVitePort + } + + // check if port is already in use + l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port)) + if err != nil { + return err + } + if err = l.Close(); err != nil { + return err + } + + // Set environment variable for the dev:frontend task + os.Setenv(wailsVitePort, strconv.Itoa(port)) + + // Set url of frontend dev server + if options.Secure { + os.Setenv("FRONTEND_DEVSERVER_URL", fmt.Sprintf("https://%s:%d", host, port)) + } else { + os.Setenv("FRONTEND_DEVSERVER_URL", fmt.Sprintf("http://%s:%d", host, port)) + } + + return Watcher(&WatcherOptions{ + Config: options.Config, + }) +} diff --git a/v3/internal/commands/dmg/dmg.go b/v3/internal/commands/dmg/dmg.go new file mode 100644 index 000000000..7ba2de572 --- /dev/null +++ b/v3/internal/commands/dmg/dmg.go @@ -0,0 +1,157 @@ +package dmg + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +// Creator handles DMG creation +type Creator struct { + sourcePath string + outputPath string + appName string + backgroundImage string + iconPositions map[string]Position +} + +// Position represents icon coordinates in the DMG +type Position struct { + X, Y int +} + +// New creates a new DMG creator +func New(sourcePath, outputPath, appName string) (*Creator, error) { + if runtime.GOOS != "darwin" { + return nil, fmt.Errorf("DMG creation is only supported on macOS") + } + + // Check if source exists + if _, err := os.Stat(sourcePath); os.IsNotExist(err) { + return nil, fmt.Errorf("source path does not exist: %s", sourcePath) + } + + return &Creator{ + sourcePath: sourcePath, + outputPath: outputPath, + appName: appName, + iconPositions: make(map[string]Position), + }, nil +} + +// SetBackgroundImage sets the background image for the DMG +func (c *Creator) SetBackgroundImage(imagePath string) error { + if _, err := os.Stat(imagePath); os.IsNotExist(err) { + return fmt.Errorf("background image does not exist: %s", imagePath) + } + c.backgroundImage = imagePath + return nil +} + +// AddIconPosition adds an icon position for the DMG layout +func (c *Creator) AddIconPosition(filename string, x, y int) { + c.iconPositions[filename] = Position{X: x, Y: y} +} + +// Create creates the DMG file +func (c *Creator) Create() error { + // Remove existing DMG if it exists + if _, err := os.Stat(c.outputPath); err == nil { + if err := os.Remove(c.outputPath); err != nil { + return fmt.Errorf("failed to remove existing DMG: %w", err) + } + } + + // Create a temporary directory for DMG content + tempDir, err := os.MkdirTemp("", "dmg-*") + if err != nil { + return fmt.Errorf("failed to create temp directory: %w", err) + } + defer os.RemoveAll(tempDir) + + // Copy the app bundle to temp directory + appName := filepath.Base(c.sourcePath) + tempAppPath := filepath.Join(tempDir, appName) + if err := c.copyDir(c.sourcePath, tempAppPath); err != nil { + return fmt.Errorf("failed to copy app bundle: %w", err) + } + + // Create Applications symlink + applicationsLink := filepath.Join(tempDir, "Applications") + if err := os.Symlink("/Applications", applicationsLink); err != nil { + return fmt.Errorf("failed to create Applications symlink: %w", err) + } + + // Copy background image if provided + if c.backgroundImage != "" { + bgName := filepath.Base(c.backgroundImage) + bgPath := filepath.Join(tempDir, bgName) + if err := c.copyFile(c.backgroundImage, bgPath); err != nil { + return fmt.Errorf("failed to copy background image: %w", err) + } + } + + // Create DMG using hdiutil + if err := c.createDMGWithHdiutil(tempDir); err != nil { + return fmt.Errorf("failed to create DMG with hdiutil: %w", err) + } + + return nil +} + +// copyDir recursively copies a directory +func (c *Creator) copyDir(src, dst string) error { + cmd := exec.Command("cp", "-R", src, dst) + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to copy directory: %w", err) + } + return nil +} + +// copyFile copies a file +func (c *Creator) copyFile(src, dst string) error { + cmd := exec.Command("cp", src, dst) + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to copy file: %w", err) + } + return nil +} + +// createDMGWithHdiutil creates the DMG using macOS hdiutil +func (c *Creator) createDMGWithHdiutil(sourceDir string) error { + // Calculate size needed for DMG (roughly 2x the source size for safety) + sizeCmd := exec.Command("du", "-sk", sourceDir) + output, err := sizeCmd.Output() + if err != nil { + return fmt.Errorf("failed to calculate directory size: %w", err) + } + + // Parse size and add padding + sizeStr := strings.Fields(string(output))[0] + + // Create DMG with hdiutil + args := []string{ + "create", + "-srcfolder", sourceDir, + "-format", "UDZO", + "-volname", c.appName, + c.outputPath, + } + + // Add size if we could determine it + if sizeStr != "" { + // Add 50% padding to the calculated size + args = append([]string{"create", "-size", sizeStr + "k"}, args[1:]...) + args[0] = "create" + } + + cmd := exec.Command("hdiutil", args...) + if output, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("hdiutil failed: %w\nOutput: %s", err, string(output)) + } + + return nil +} diff --git a/v3/internal/commands/doctor.go b/v3/internal/commands/doctor.go new file mode 100644 index 000000000..08a3da2a2 --- /dev/null +++ b/v3/internal/commands/doctor.go @@ -0,0 +1,11 @@ +package commands + +import ( + "github.com/wailsapp/wails/v3/internal/doctor" +) + +type DoctorOptions struct{} + +func Doctor(_ *DoctorOptions) error { + return doctor.Run() +} diff --git a/v3/internal/commands/dot_desktop.go b/v3/internal/commands/dot_desktop.go new file mode 100644 index 000000000..de3ed6c01 --- /dev/null +++ b/v3/internal/commands/dot_desktop.go @@ -0,0 +1,80 @@ +package commands + +import ( + "bytes" + "fmt" + "os" +) + +type DotDesktopOptions struct { + OutputFile string `description:"The output file to write to"` + Type string `description:"The type of the desktop entry" default:"Application"` + Name string `description:"The name of the application"` + Exec string `description:"The binary name + args to execute"` + Icon string `description:"The icon name or path for the application"` + Categories string `description:"Categories in which the application should be shown e.g. 'Development;IDE;'"` + Comment string `description:"A brief description of the application"` + Terminal bool `description:"Whether the application runs in a terminal" default:"false"` + Keywords string `description:"Keywords associated with the application e.g. 'Editor;Image;'" default:"wails"` + Version string `description:"The version of the Desktop Entry Specification" default:"1.0"` + GenericName string `description:"A generic name for the application"` + StartupNotify bool `description:"If true, the app will send a notification when starting" default:"false"` + MimeType string `description:"The MIME types the application can handle e.g. 'image/gif;image/jpeg;'"` + //Actions []string `description:"Additional actions offered by the application"` +} + +func (d *DotDesktopOptions) asBytes() []byte { + var buf bytes.Buffer + // Mandatory fields + buf.WriteString("[Desktop Entry]\n") + buf.WriteString(fmt.Sprintf("Type=%s\n", d.Type)) + buf.WriteString(fmt.Sprintf("Name=%s\n", d.Name)) + buf.WriteString(fmt.Sprintf("Exec=%s\n", d.Exec)) + + // Optional fields with checks + if d.Icon != "" { + buf.WriteString(fmt.Sprintf("Icon=%s\n", d.Icon)) + } + buf.WriteString(fmt.Sprintf("Categories=%s\n", d.Categories)) + if d.Comment != "" { + buf.WriteString(fmt.Sprintf("Comment=%s\n", d.Comment)) + } + buf.WriteString(fmt.Sprintf("Terminal=%t\n", d.Terminal)) + if d.Keywords != "" { + buf.WriteString(fmt.Sprintf("Keywords=%s\n", d.Keywords)) + } + if d.Version != "" { + buf.WriteString(fmt.Sprintf("Version=%s\n", d.Version)) + } + if d.GenericName != "" { + buf.WriteString(fmt.Sprintf("GenericName=%s\n", d.GenericName)) + } + buf.WriteString(fmt.Sprintf("StartupNotify=%t\n", d.StartupNotify)) + if d.MimeType != "" { + buf.WriteString(fmt.Sprintf("MimeType=%s\n", d.MimeType)) + } + return buf.Bytes() +} + +func GenerateDotDesktop(options *DotDesktopOptions) error { + DisableFooter = true + + if options.Name == "" { + return fmt.Errorf("name is required") + } + + options.Name = normaliseName(options.Name) + + if options.Exec == "" { + return fmt.Errorf("exec is required") + } + + if options.OutputFile == "" { + options.OutputFile = options.Name + ".desktop" + } + + // Write to file + err := os.WriteFile(options.OutputFile, options.asBytes(), 0755) + + return err +} diff --git a/v3/internal/commands/entitlements_setup.go b/v3/internal/commands/entitlements_setup.go new file mode 100644 index 000000000..44633189d --- /dev/null +++ b/v3/internal/commands/entitlements_setup.go @@ -0,0 +1,241 @@ +package commands + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/charmbracelet/huh" + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v3/internal/flags" +) + +// Entitlement represents a macOS entitlement +type Entitlement struct { + Key string + Name string + Description string + Category string +} + +// Common macOS entitlements organized by category +var availableEntitlements = []Entitlement{ + // Hardened Runtime - Code Execution + {Key: "com.apple.security.cs.allow-jit", Name: "Allow JIT", Description: "Allow creating writable/executable memory using MAP_JIT (most secure option for JIT)", Category: "Code Execution"}, + {Key: "com.apple.security.cs.allow-unsigned-executable-memory", Name: "Allow Unsigned Executable Memory", Description: "Allow writable/executable memory without MAP_JIT restrictions", Category: "Code Execution"}, + {Key: "com.apple.security.cs.disable-executable-page-protection", Name: "Disable Executable Page Protection", Description: "Disable all executable memory protections (least secure)", Category: "Code Execution"}, + {Key: "com.apple.security.cs.disable-library-validation", Name: "Disable Library Validation", Description: "Allow loading unsigned or differently-signed libraries/frameworks", Category: "Code Execution"}, + {Key: "com.apple.security.cs.allow-dyld-environment-variables", Name: "Allow DYLD Environment Variables", Description: "Allow DYLD_* environment variables to modify library loading", Category: "Code Execution"}, + + // Hardened Runtime - Resource Access + {Key: "com.apple.security.device.audio-input", Name: "Audio Input (Microphone)", Description: "Access to audio input devices", Category: "Resource Access"}, + {Key: "com.apple.security.device.camera", Name: "Camera", Description: "Access to the camera", Category: "Resource Access"}, + {Key: "com.apple.security.personal-information.location", Name: "Location", Description: "Access to location services", Category: "Resource Access"}, + {Key: "com.apple.security.personal-information.addressbook", Name: "Address Book", Description: "Access to contacts", Category: "Resource Access"}, + {Key: "com.apple.security.personal-information.calendars", Name: "Calendars", Description: "Access to calendar data", Category: "Resource Access"}, + {Key: "com.apple.security.personal-information.photos-library", Name: "Photos Library", Description: "Access to the Photos library", Category: "Resource Access"}, + {Key: "com.apple.security.automation.apple-events", Name: "Apple Events", Description: "Send Apple Events to other apps (AppleScript)", Category: "Resource Access"}, + + // App Sandbox - Basic + {Key: "com.apple.security.app-sandbox", Name: "Enable App Sandbox", Description: "Enable the App Sandbox (required for Mac App Store)", Category: "App Sandbox"}, + + // App Sandbox - Network + {Key: "com.apple.security.network.client", Name: "Outgoing Network Connections", Description: "Allow outgoing network connections (client)", Category: "Network"}, + {Key: "com.apple.security.network.server", Name: "Incoming Network Connections", Description: "Allow incoming network connections (server)", Category: "Network"}, + + // App Sandbox - Files + {Key: "com.apple.security.files.user-selected.read-only", Name: "User-Selected Files (Read)", Description: "Read access to files the user selects", Category: "File Access"}, + {Key: "com.apple.security.files.user-selected.read-write", Name: "User-Selected Files (Read/Write)", Description: "Read/write access to files the user selects", Category: "File Access"}, + {Key: "com.apple.security.files.downloads.read-only", Name: "Downloads Folder (Read)", Description: "Read access to the Downloads folder", Category: "File Access"}, + {Key: "com.apple.security.files.downloads.read-write", Name: "Downloads Folder (Read/Write)", Description: "Read/write access to the Downloads folder", Category: "File Access"}, + + // Development + {Key: "com.apple.security.get-task-allow", Name: "Debugging", Description: "Allow debugging (disable for production)", Category: "Development"}, +} + +// EntitlementsSetup runs the interactive entitlements configuration wizard +func EntitlementsSetup(options *flags.EntitlementsSetup) error { + pterm.DefaultHeader.Println("macOS Entitlements Setup") + fmt.Println() + + // Build all options for custom selection + var allOptions []huh.Option[string] + for _, e := range availableEntitlements { + label := fmt.Sprintf("[%s] %s", e.Category, e.Name) + allOptions = append(allOptions, huh.NewOption(label, e.Key)) + } + + // Show quick presets first + var preset string + presetForm := huh.NewForm( + huh.NewGroup( + huh.NewSelect[string](). + Title("Which entitlements profile?"). + Description("Development and production use separate files"). + Options( + huh.NewOption("Development (entitlements.dev.plist)", "dev"), + huh.NewOption("Production (entitlements.plist)", "prod"), + huh.NewOption("Both (recommended)", "both"), + huh.NewOption("App Store (entitlements.plist with sandbox)", "appstore"), + huh.NewOption("Custom", "custom"), + ). + Value(&preset), + ), + ) + + if err := presetForm.Run(); err != nil { + return err + } + + devEntitlements := []string{ + "com.apple.security.cs.allow-jit", + "com.apple.security.cs.allow-unsigned-executable-memory", + "com.apple.security.cs.disable-library-validation", + "com.apple.security.get-task-allow", + "com.apple.security.network.client", + } + + prodEntitlements := []string{ + "com.apple.security.network.client", + } + + appStoreEntitlements := []string{ + "com.apple.security.app-sandbox", + "com.apple.security.network.client", + "com.apple.security.files.user-selected.read-write", + } + + baseDir := "build/darwin" + if options.Output != "" { + baseDir = filepath.Dir(options.Output) + } + + switch preset { + case "dev": + return writeEntitlementsFile(filepath.Join(baseDir, "entitlements.dev.plist"), devEntitlements) + + case "prod": + return writeEntitlementsFile(filepath.Join(baseDir, "entitlements.plist"), prodEntitlements) + + case "both": + if err := writeEntitlementsFile(filepath.Join(baseDir, "entitlements.dev.plist"), devEntitlements); err != nil { + return err + } + return writeEntitlementsFile(filepath.Join(baseDir, "entitlements.plist"), prodEntitlements) + + case "appstore": + return writeEntitlementsFile(filepath.Join(baseDir, "entitlements.plist"), appStoreEntitlements) + + case "custom": + // Let user choose which file and entitlements + var targetFile string + var selected []string + + customForm := huh.NewForm( + huh.NewGroup( + huh.NewSelect[string](). + Title("Target file"). + Options( + huh.NewOption("entitlements.plist (production)", "entitlements.plist"), + huh.NewOption("entitlements.dev.plist (development)", "entitlements.dev.plist"), + ). + Value(&targetFile), + ), + huh.NewGroup( + huh.NewMultiSelect[string](). + Title("Select entitlements"). + Description("Use space to select, enter to confirm"). + Options(allOptions...). + Value(&selected), + ), + ) + + if err := customForm.Run(); err != nil { + return err + } + + return writeEntitlementsFile(filepath.Join(baseDir, targetFile), selected) + } + + return nil +} + +func writeEntitlementsFile(path string, entitlements []string) error { + // Ensure directory exists + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("failed to create directory: %w", err) + } + + // Generate and write the plist + plist := generateEntitlementsPlist(entitlements) + if err := os.WriteFile(path, []byte(plist), 0644); err != nil { + return fmt.Errorf("failed to write entitlements file: %w", err) + } + + pterm.Success.Printfln("Wrote %s", path) + + // Show summary + pterm.Info.Println("Entitlements:") + for _, key := range entitlements { + for _, e := range availableEntitlements { + if e.Key == key { + fmt.Printf(" - %s\n", e.Name) + break + } + } + } + fmt.Println() + + return nil +} + +func parseExistingEntitlements(path string) (map[string]bool, error) { + content, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + result := make(map[string]bool) + lines := strings.Split(string(content), "\n") + + for i, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "") && strings.HasSuffix(line, "") { + key := strings.TrimPrefix(line, "") + key = strings.TrimSuffix(key, "") + + // Check if next line is + if i+1 < len(lines) { + nextLine := strings.TrimSpace(lines[i+1]) + if nextLine == "" { + result[key] = true + } + } + } + } + + return result, nil +} + +func generateEntitlementsPlist(entitlements []string) string { + var sb strings.Builder + + sb.WriteString(` + + + +`) + + for _, key := range entitlements { + sb.WriteString(fmt.Sprintf("\t%s\n", key)) + sb.WriteString("\t\n") + } + + sb.WriteString(` + +`) + + return sb.String() +} diff --git a/v3/internal/commands/generate_template.go b/v3/internal/commands/generate_template.go new file mode 100644 index 000000000..8903b5069 --- /dev/null +++ b/v3/internal/commands/generate_template.go @@ -0,0 +1,9 @@ +package commands + +import ( + "github.com/wailsapp/wails/v3/internal/templates" +) + +func GenerateTemplate(options *templates.BaseTemplate) error { + return templates.GenerateTemplate(options) +} diff --git a/v3/internal/commands/generate_webview2.go b/v3/internal/commands/generate_webview2.go new file mode 100644 index 000000000..60ec33a42 --- /dev/null +++ b/v3/internal/commands/generate_webview2.go @@ -0,0 +1,43 @@ +package commands + +import ( + _ "embed" + "fmt" + "github.com/wailsapp/wails/v3/internal/term" + "os" + "path/filepath" +) + +//go:embed webview2/MicrosoftEdgeWebview2Setup.exe +var webview2Bootstrapper []byte + +type GenerateWebView2Options struct { + Directory string `name:"dir" json:"directory"` +} + +func GenerateWebView2Bootstrapper(options *GenerateWebView2Options) error { + println("options.Directory", options.Directory) + options.Directory = filepath.Clean(options.Directory) + println("cleaned options.Directory", options.Directory) + + // If the file already exists, exit early + if _, err := os.Stat(filepath.Join(options.Directory, "MicrosoftEdgeWebview2Setup.exe")); err == nil { + return nil + } + + // Create target directory if it doesn't exist + err := os.MkdirAll(options.Directory, 0755) + if err != nil { + return fmt.Errorf("failed to create target directory: %w", err) + } + + // Write to target directory + targetPath := filepath.Join(options.Directory, "MicrosoftEdgeWebview2Setup.exe") + err = os.WriteFile(targetPath, webview2Bootstrapper, 0644) + if err != nil { + return fmt.Errorf("failed to write WebView2 bootstrapper: %w", err) + } + + term.Success("Generated WebView2 bootstrapper at: " + targetPath + "\n") + return nil +} diff --git a/v3/internal/commands/icons.go b/v3/internal/commands/icons.go new file mode 100644 index 000000000..a25482d74 --- /dev/null +++ b/v3/internal/commands/icons.go @@ -0,0 +1,359 @@ +package commands + +import ( + "bytes" + "errors" + "fmt" + "image" + "image/color" + "image/png" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + + "github.com/jackmordaunt/icns/v2" + "github.com/leaanthony/winicon" + "github.com/wailsapp/wails/v3/internal/operatingsystem" + "howett.net/plist" +) + +// ErrMacAssetNotSupported is returned by generateMacAsset when mac asset generation +// is not supported on the current platform (e.g., non-macOS systems). +var ErrMacAssetNotSupported = errors.New("mac asset generation is only supported on macOS") + +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)" default:"256,128,64,48,32,16"` + WindowsFilename string `description:"The output filename for the Windows icon"` + MacFilename string `description:"The output filename for the Mac icon bundle"` + IconComposerInput string `description:"The input Icon Composer file (.icon)"` + MacAssetDir string `description:"The output directory for the Mac assets (Assets.car and icons.icns)"` +} + +func GenerateIcons(options *IconsOptions) error { + DisableFooter = true + + if options.Example { + return generateExampleIcon() + } + + if options.Input == "" && options.IconComposerInput == "" { + return fmt.Errorf("either input or icon composer input is required") + } + + if options.Input != "" && options.WindowsFilename == "" && options.MacFilename == "" { + return fmt.Errorf("either windows filename or mac filename is required") + } + + if options.IconComposerInput != "" && options.MacAssetDir == "" { + return fmt.Errorf("mac asset directory 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 + } + } + + // Generate Icons from Icon Composer input + macIconsGenerated := false + if options.IconComposerInput != "" { + if options.MacAssetDir != "" { + err := generateMacAsset(options) + if err != nil { + if errors.Is(err, ErrMacAssetNotSupported) { + // No fallback: Icon Composer path requires macOS; return so callers see unsupported-platform failure + if options.Input == "" { + return fmt.Errorf("icon composer input requires macOS for mac asset generation: %w", err) + } + // Fallback to input-based generation will run below + } else { + return err + } + } else { + macIconsGenerated = true + } + } + } + + // Generate Icons from input image + if options.Input != "" { + iconData, err := os.ReadFile(options.Input) + if err != nil { + return err + } + + if options.WindowsFilename != "" { + err := generateWindowsIcon(iconData, sizes, options) + if err != nil { + return err + } + } + + // Generate Icons from input image if no Mac icons were generated from Icon Composer input + if options.MacFilename != "" && !macIconsGenerated { + err := generateMacIcon(iconData, options) + if err != nil { + return err + } + } + } + + return nil +} + +func generateExampleIcon() error { + appIcon, err := buildAssets.ReadFile("build_assets/appicon.png") + if err != nil { + return err + } + return os.WriteFile("appicon.png", 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 generateMacAsset(options *IconsOptions) error { + //Check if running on darwin (macOS), because this will only run on a mac + if runtime.GOOS != "darwin" { + return ErrMacAssetNotSupported + } + // Get system info, because this will only run on macOS 26 or later + info, err := operatingsystem.Info() + if err != nil { + return ErrMacAssetNotSupported + } + majorStr, _, found := strings.Cut(info.Version, ".") + if !found { + return ErrMacAssetNotSupported + } + major, err := strconv.Atoi(majorStr) + if err != nil { + return ErrMacAssetNotSupported + } + if major < 26 { + return ErrMacAssetNotSupported + } + + cmd := exec.Command("/usr/bin/actool", "--version") + versionPlist, err := cmd.Output() + if err != nil { + return ErrMacAssetNotSupported + } + + // Parse the plist to extract short-bundle-version + var plistData map[string]any + if _, err := plist.Unmarshal(versionPlist, &plistData); err != nil { + return ErrMacAssetNotSupported + } + + // Navigate to com.apple.actool.version -> short-bundle-version + actoolVersion, ok := plistData["com.apple.actool.version"].(map[string]any) + if !ok { + return ErrMacAssetNotSupported + } + + shortVersion, ok := actoolVersion["short-bundle-version"].(string) + if !ok { + return ErrMacAssetNotSupported + } + + // Parse the major version number (e.g., "26.2" -> 26) + actoolMajorStr, _, _ := strings.Cut(shortVersion, ".") + actoolMajor, err := strconv.Atoi(actoolMajorStr) + if err != nil { + return ErrMacAssetNotSupported + } + + if actoolMajor < 26 { + return ErrMacAssetNotSupported + } + + // Convert paths to absolute paths (required for actool) + iconComposerPath, err := filepath.Abs(options.IconComposerInput) + if err != nil { + return fmt.Errorf("failed to get absolute path for icon composer input: %w", err) + } + macAssetDirPath, err := filepath.Abs(options.MacAssetDir) + if err != nil { + return fmt.Errorf("failed to get absolute path for mac asset directory: %w", err) + } + + // Get Filename from Icon Composer input without extension + iconComposerFilename := filepath.Base(iconComposerPath) + iconComposerFilename = strings.TrimSuffix(iconComposerFilename, filepath.Ext(iconComposerFilename)) + + cmd = exec.Command("/usr/bin/actool", iconComposerPath, + "--compile", macAssetDirPath, + "--notices", "--warnings", "--errors", + "--output-partial-info-plist", filepath.Join(macAssetDirPath, "temp.plist"), + "--app-icon", iconComposerFilename, + "--enable-on-demand-resources", "NO", + "--development-region", "en", + "--target-device", "mac", + "--minimum-deployment-target", "26.0", + "--platform", "macosx") + out, err := cmd.Output() + if err != nil { + return fmt.Errorf("failed to run actool: %w", err) + } + + // Parse the plist output to verify compilation results + var compilationResults map[string]any + if _, err := plist.Unmarshal(out, &compilationResults); err != nil { + return fmt.Errorf("failed to parse actool compilation results: %w", err) + } + + // Navigate to com.apple.actool.compilation-results -> output-files + compilationData, ok := compilationResults["com.apple.actool.compilation-results"].(map[string]any) + if !ok { + return fmt.Errorf("failed to find com.apple.actool.compilation-results in plist") + } + + outputFiles, ok := compilationData["output-files"].([]any) + if !ok { + return fmt.Errorf("failed to find output-files array in compilation results") + } + + // Check that we have one .car file and one .plist file + var carFile, plistFile, icnsFile string + for _, file := range outputFiles { + filePath, ok := file.(string) + if !ok { + return fmt.Errorf("output file is not a string: %v", file) + } + ext := filepath.Ext(filePath) + switch ext { + case ".car": + carFile = filePath + case ".plist": + plistFile = filePath + case ".icns": + icnsFile = filePath + // Ignore other output files that may be added in future actool versions + } + } + + if carFile == "" { + return fmt.Errorf("no .car file found in output files") + } + if plistFile == "" { + return fmt.Errorf("no .plist file found in output files") + } + if icnsFile == "" { + return fmt.Errorf("no .icns file found in output files") + } + + // Remove the temporary plist file since compilation was successful + if err := os.Remove(plistFile); err != nil { + return fmt.Errorf("failed to remove temporary plist file: %w", err) + } + + // Rename the .icns file to icons.icns + if err := os.Rename(icnsFile, filepath.Join(macAssetDirPath, "icons.icns")); err != nil { + return fmt.Errorf("failed to rename .icns file to icons.icns: %w", err) + } + + return nil +} + +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 +} + +func GenerateTemplateIcon(data []byte, outputFilename string) (err error) { + // Decode the input file as a PNG + buffer := bytes.NewBuffer(data) + var img image.Image + img, err = png.Decode(buffer) + if err != nil { + return fmt.Errorf("failed to decode input file as PNG: %w", err) + } + + // Create a new image with the same dimensions and RGBA color model + bounds := img.Bounds() + iconImg := image.NewRGBA(bounds) + + // Iterate over each pixel of the input image + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + // Get the alpha of the pixel + _, _, _, a := img.At(x, y).RGBA() + iconImg.SetRGBA(x, y, color.RGBA{R: 0, G: 0, B: 0, A: uint8(a)}) + } + } + + // Create the output file + var outFile *os.File + outFile, err = os.Create(outputFilename) + if err != nil { + return fmt.Errorf("failed to create output file: %w", err) + } + defer func() { + err = outFile.Close() + }() + + // Encode the template icon image as a PNG and write it to the output file + if err = png.Encode(outFile, iconImg); err != nil { + return fmt.Errorf("failed to encode output image as PNG: %w", err) + } + + return nil +} diff --git a/v3/internal/commands/icons_test.go b/v3/internal/commands/icons_test.go new file mode 100644 index 000000000..b13823e92 --- /dev/null +++ b/v3/internal/commands/icons_test.go @@ -0,0 +1,335 @@ +package commands + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "testing" +) + +func TestGenerateIcon(t *testing.T) { + tests := []struct { + name string + setup func() *IconsOptions + wantErr bool + requireDarwin 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 `windowsfilename` 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, "build_assets", "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 + _ = os.Remove("appicon.ico") + }() + 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, "build_assets", "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 Assets.car and icons.icns file when using the `IconComposerInput` flag and `MacAssetDir` flag", + requireDarwin: true, + 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, "build_assets", "appicon.icon") + return &IconsOptions{ + IconComposerInput: exampleIcon, + MacAssetDir: localDir, + } + }, + wantErr: false, + test: func() error { + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + carPath := filepath.Join(localDir, "Assets.car") + icnsPath := filepath.Join(localDir, "icons.icns") + defer func() { + _ = os.Remove(carPath) + _ = os.Remove(icnsPath) + }() + f, err := os.Stat(carPath) + if err != nil { + return err + } + if f.IsDir() { + return fmt.Errorf("Assets.car is a directory") + } + if f.Size() == 0 { + return fmt.Errorf("Assets.car is empty") + } + f, err = os.Stat(icnsPath) + if err != nil { + return err + } + if f.IsDir() { + return fmt.Errorf("icons.icns is a directory") + } + if f.Size() == 0 { + return fmt.Errorf("icons.icns is empty") + } + 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, "build_assets", "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, "build_assets", "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, "build_assets", "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, "build_assets", "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) { + if tt.requireDarwin && (runtime.GOOS != "darwin" || os.Getenv("CI") != "") { + t.Skip("Assets.car generation is only supported on macOS and not in CI") + } + + 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) + } + } + }) + } +} diff --git a/v3/internal/commands/init.go b/v3/internal/commands/init.go new file mode 100644 index 000000000..3f827d1b7 --- /dev/null +++ b/v3/internal/commands/init.go @@ -0,0 +1,237 @@ +package commands + +import ( + "errors" + "fmt" + "net/url" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/go-git/go-git/v5/config" + "github.com/wailsapp/wails/v3/internal/defaults" + "github.com/wailsapp/wails/v3/internal/term" + + "github.com/go-git/go-git/v5" + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/templates" +) + +var DisableFooter bool + +// See https://github.com/git/git/blob/master/Documentation/urls.adoc +var ( + gitProtocolFormat = regexp.MustCompile(`^(?:ssh|git|https?|ftps?|file)://`) + gitScpLikeGuard = regexp.MustCompile(`^[^/:]+:`) + gitScpLikeFormat = regexp.MustCompile(`^(?:([^@/:]+)@)?([^@/:]+):([^\\].*)$`) +) + +// gitURLToModulePath converts a git URL to a Go module name by removing common prefixes +// and suffixes. It handles HTTPS, SSH, Git protocol, and filesystem URLs. +func gitURLToModulePath(gitURL string) string { + var path string + + if gitProtocolFormat.MatchString(gitURL) { + // Standard URL + parsed, err := url.Parse(gitURL) + if err != nil { + term.Warningf("invalid Git repository URL: %s; module path will default to 'changeme'", err) + return "changeme" + } + + path = parsed.Host + parsed.Path + } else if gitScpLikeGuard.MatchString(gitURL) { + // SCP-like URL + match := gitScpLikeFormat.FindStringSubmatch(gitURL) + if match != nil { + sep := "" + if !strings.HasPrefix(match[3], "/") { + // Add slash between host and path if missing + sep = "/" + } + + path = match[2] + sep + match[3] + } + } + + if path == "" { + // Filesystem path + path = gitURL + } + + if strings.HasSuffix(path, ".git") { + path = path[:len(path)-4] + } + + // Remove leading forward slash for file system paths + return strings.TrimPrefix(path, "/") +} + +func initGitRepository(projectDir string, gitURL string) error { + // Initialize repository + repo, err := git.PlainInit(projectDir, false) + if err != nil { + return fmt.Errorf("failed to initialize git repository: %w", err) + } + + // Create remote + _, err = repo.CreateRemote(&config.RemoteConfig{ + Name: "origin", + URLs: []string{gitURL}, + }) + if err != nil { + return fmt.Errorf("failed to create git remote: %w", err) + } + + // Stage all files + worktree, err := repo.Worktree() + if err != nil { + return fmt.Errorf("failed to get git worktree: %w", err) + } + + _, err = worktree.Add(".") + if err != nil { + return fmt.Errorf("failed to stage files: %w", err) + } + + return nil +} + +// applyGlobalDefaults applies global defaults to init options if they are using default values +func applyGlobalDefaults(options *flags.Init, globalDefaults defaults.GlobalDefaults) { + // Apply template default if using the built-in default + if options.TemplateName == "vanilla" && globalDefaults.Project.DefaultTemplate != "" { + options.TemplateName = globalDefaults.Project.DefaultTemplate + } + + // Apply company default if using the built-in default + if options.ProductCompany == "My Company" && globalDefaults.Author.Company != "" { + options.ProductCompany = globalDefaults.Author.Company + } + + // Apply copyright from global defaults if using the built-in default + if options.ProductCopyright == "\u00a9 now, My Company" { + options.ProductCopyright = globalDefaults.GenerateCopyright() + } + + // Apply product identifier from global defaults if not explicitly set + if options.ProductIdentifier == "" && globalDefaults.Project.ProductIdentifierPrefix != "" { + options.ProductIdentifier = globalDefaults.GenerateProductIdentifier(options.ProjectName) + } + + // Apply description from global defaults if using the built-in default + if options.ProductDescription == "My Product Description" && globalDefaults.Project.DescriptionTemplate != "" { + options.ProductDescription = globalDefaults.GenerateDescription(options.ProjectName) + } + + // Apply version from global defaults if using the built-in default + if options.ProductVersion == "0.1.0" && globalDefaults.Project.DefaultVersion != "" { + options.ProductVersion = globalDefaults.GetDefaultVersion() + } +} + +func Init(options *flags.Init) error { + if options.List { + term.Header("Available templates") + return printTemplates() + } + + if options.Quiet { + term.DisableOutput() + } + term.Header("Init project") + + // Check if the template is a typescript template + isTypescript := false + if strings.HasSuffix(options.TemplateName, "-ts") { + isTypescript = true + } + + if options.ProjectName == "" { + return errors.New("please use the -n flag to specify a project name") + } + + options.ProjectName = sanitizeFileName(options.ProjectName) + + // Load and apply global defaults + globalDefaults, err := defaults.Load() + if err != nil { + // Log warning but continue - global defaults are optional + term.Warningf("Could not load global defaults: %v\n", err) + } else { + applyGlobalDefaults(options, globalDefaults) + } + + if options.ModulePath == "" { + if options.Git == "" { + options.ModulePath = "changeme" + } else { + options.ModulePath = gitURLToModulePath(options.Git) + } + } + + err = templates.Install(options) + if err != nil { + return err + } + + // Rename gitignore to .gitignore + err = os.Rename(filepath.Join(options.ProjectDir, "gitignore"), filepath.Join(options.ProjectDir, ".gitignore")) + if err != nil { + return err + } + + // Generate build assets + buildAssetsOptions := &BuildAssetsOptions{ + Name: options.ProjectName, + Dir: filepath.Join(options.ProjectDir, "build"), + Silent: true, + ProductCompany: options.ProductCompany, + ProductName: options.ProductName, + ProductDescription: options.ProductDescription, + ProductVersion: options.ProductVersion, + ProductIdentifier: options.ProductIdentifier, + ProductCopyright: options.ProductCopyright, + ProductComments: options.ProductComments, + Typescript: isTypescript, + } + err = GenerateBuildAssets(buildAssetsOptions) + if err != nil { + return err + } + // Initialize git repository if URL is provided + if options.Git != "" { + err = initGitRepository(options.ProjectDir, options.Git) + if err != nil { + return err + } + if !options.Quiet { + term.Infof("Initialized git repository with remote: %s\n", options.Git) + } + } + return nil +} + +func printTemplates() error { + defaultTemplates := templates.GetDefaultTemplates() + + pterm.Println() + table := pterm.TableData{{"Name", "Description"}} + for _, template := range defaultTemplates { + table = append(table, []string{template.Name, template.Description}) + } + err := pterm.DefaultTable.WithHasHeader(true).WithBoxed(true).WithData(table).Render() + pterm.Println() + return err +} + +func sanitizeFileName(fileName string) string { + // Regular expression to match non-allowed characters in file names + // You can adjust this based on the specific requirements of your file system + reg := regexp.MustCompile(`[^a-zA-Z0-9_.-]`) + + // Replace matched characters with an underscore or any other safe character + return reg.ReplaceAllString(fileName, "_") +} diff --git a/v3/internal/commands/init_test.go b/v3/internal/commands/init_test.go new file mode 100644 index 000000000..cb1b30d48 --- /dev/null +++ b/v3/internal/commands/init_test.go @@ -0,0 +1,116 @@ +package commands + +import ( + "testing" +) + +func TestGitURLToModulePath(t *testing.T) { + tests := []struct { + name string + gitURL string + want string + }{ + { + name: "Simple GitHub URL", + gitURL: "github.com/username/project", + want: "github.com/username/project", + }, + { + name: "GitHub URL with .git suffix", + gitURL: "github.com/username/project.git", + want: "github.com/username/project", + }, + { + name: "HTTPS GitHub URL", + gitURL: "https://github.com/username/project", + want: "github.com/username/project", + }, + { + name: "HTTPS GitHub URL with .git suffix", + gitURL: "https://github.com/username/project.git", + want: "github.com/username/project", + }, + { + name: "HTTP GitHub URL", + gitURL: "http://github.com/username/project", + want: "github.com/username/project", + }, + { + name: "HTTP GitHub URL with .git suffix", + gitURL: "http://github.com/username/project.git", + want: "github.com/username/project", + }, + { + name: "SSH GitHub URL", + gitURL: "git@github.com:username/project", + want: "github.com/username/project", + }, + { + name: "SSH GitHub URL with .git suffix", + gitURL: "git@github.com:username/project.git", + want: "github.com/username/project", + }, + { + name: "Alternative SSH URL format", + gitURL: "ssh://git@github.com/username/project.git", + want: "github.com/username/project", + }, + { + name: "Git protocol URL", + gitURL: "git://github.com/username/project.git", + want: "github.com/username/project", + }, + { + name: "File system URL", + gitURL: "file:///path/to/project.git", + want: "path/to/project", + }, + { + name: "SSH GitLab URL", + gitURL: "git@gitlab.com:username/project.git", + want: "gitlab.com/username/project", + }, + { + name: "SSH Custom Domain", + gitURL: "git@git.company.com:username/project.git", + want: "git.company.com/username/project", + }, + { + name: "GitLab URL", + gitURL: "gitlab.com/username/project", + want: "gitlab.com/username/project", + }, + { + name: "BitBucket URL", + gitURL: "bitbucket.org/username/project", + want: "bitbucket.org/username/project", + }, + { + name: "Custom domain", + gitURL: "git.company.com/username/project", + want: "git.company.com/username/project", + }, + { + name: "Custom domain with HTTPS and .git", + gitURL: "https://git.company.com/username/project.git", + want: "git.company.com/username/project", + }, + { + name: "Empty string", + gitURL: "", + want: "", + }, + { + name: "Just .git suffix", + gitURL: ".git", + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := gitURLToModulePath(tt.gitURL); got != tt.want { + t.Errorf("gitURLToModulePath() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/v3/internal/commands/ios_overlay_gen.go b/v3/internal/commands/ios_overlay_gen.go new file mode 100644 index 000000000..918fc0dab --- /dev/null +++ b/v3/internal/commands/ios_overlay_gen.go @@ -0,0 +1,115 @@ +package commands + +import ( + "encoding/json" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" +) + +// IOSOverlayGenOptions holds parameters for overlay generation. +type IOSOverlayGenOptions struct { + Out string `description:"Path to write overlay.json" default:"build/ios/xcode/overlay.json"` + Config string `description:"Path to build/config.yml (optional)" default:"build/config.yml"` +} + +// IOSOverlayGen generates a Go build overlay JSON that injects a generated +// main_ios.gen.go exporting WailsIOSMain() which calls main(). +// +// It writes: +// - : overlay JSON file +// - /gen/main_ios.gen.go : the generated Go file referenced by the overlay +// +// The overlay maps /main_ios.gen.go -> /gen/main_ios.gen.go +func IOSOverlayGen(options *IOSOverlayGenOptions) error { // options currently unused beyond defaults + out := options.Out + + if out == "" { + return errors.New("--out is required (path to write overlay.json)") + } + + absOut, err := filepath.Abs(out) + if err != nil { + return err + } + targetDir := filepath.Dir(absOut) + if err := os.MkdirAll(targetDir, 0o755); err != nil { + return err + } + + // Locate the internal template file to source content + root, err := repoRoot() + if err != nil { + return err + } + tmplPath := filepath.Join(root, "v3", "internal", "commands", "build_assets", "ios", "main_ios.go") + content, err := os.ReadFile(tmplPath) + if err != nil { + return fmt.Errorf("read template %s: %w", tmplPath, err) + } + + genDir := filepath.Join(targetDir, "gen") + if err := os.MkdirAll(genDir, 0o755); err != nil { + return err + } + genGo := filepath.Join(genDir, "main_ios.gen.go") + if err := os.WriteFile(genGo, content, 0o644); err != nil { + return err + } + + // Determine app dir (current working directory) + appDir, err := os.Getwd() + if err != nil { + return err + } + virtual := filepath.Join(appDir, "main_ios.gen.go") + + type overlay struct { + Replace map[string]string `json:"Replace"` + } + ov := overlay{Replace: map[string]string{virtual: genGo}} + data, err := json.MarshalIndent(ov, "", " ") + if err != nil { + return err + } + if err := os.WriteFile(absOut, data, 0o644); err != nil { + return err + } + return nil +} + +// IOSOverlayGenCmd is a CLI entry compatible with NewSubCommandFunction. +// Defaults: +// config: ./build/config.yml (optional) +// out: ./build/ios/xcode/overlay.json +func IOSOverlayGenCmd() error { + // Default paths relative to CWD + out := filepath.Join("build", "ios", "xcode", "overlay.json") + cfg := filepath.Join("build", "config.yml") + return IOSOverlayGen(&IOSOverlayGenOptions{Out: out, Config: cfg}) +} + +// repoRoot attempts to find the repository root relative to this file location. +func repoRoot() (string, error) { + // Resolve based on the location of this source at build time if possible. + self, err := os.Getwd() + if err != nil { + return "", err + } + // Walk up until we find a directory containing v3/internal/commands + probe := self + for i := 0; i < 10; i++ { + p := filepath.Join(probe, "v3", "internal", "commands") + if st, err := os.Stat(p); err == nil && st.IsDir() { + return probe, nil + } + next := filepath.Dir(probe) + if next == probe { + break + } + probe = next + } + return "", fs.ErrNotExist +} diff --git a/v3/internal/commands/ios_xcode_gen.go b/v3/internal/commands/ios_xcode_gen.go new file mode 100644 index 000000000..1f883a12f --- /dev/null +++ b/v3/internal/commands/ios_xcode_gen.go @@ -0,0 +1,298 @@ +package commands + +import ( + "bytes" + "fmt" + "image" + "image/png" + "io" + "io/fs" + "os" + "path/filepath" + "text/template" + + "golang.org/x/image/draw" + "gopkg.in/yaml.v3" +) + +// IOSXcodeGenOptions holds parameters for Xcode project generation. +type IOSXcodeGenOptions struct { + OutDir string `description:"Output directory for generated Xcode project" default:"build/ios/xcode"` + Config string `description:"Path to build/config.yml (optional)" default:"build/config.yml"` +} + +// generateIOSAppIcons creates required iOS AppIcon PNGs into appIconsetDir using inputIcon PNG. +func generateIOSAppIcons(inputIcon string, appIconsetDir string) error { + in, err := os.Open(inputIcon) + if err != nil { + return err + } + defer in.Close() + return generateIOSAppIconsFromReader(in, appIconsetDir) +} + +// generateIOSAppIconsFromReader decodes an image source and writes all required sizes. +func generateIOSAppIconsFromReader(r io.Reader, appIconsetDir string) error { + src, _, err := image.Decode(r) + if err != nil { + return fmt.Errorf("decode appicon: %w", err) + } + + // Mapping: filename -> size(px) (unique keys only) + sizes := map[string]int{ + "icon-20.png": 20, + "icon-20@2x.png": 40, + "icon-20@3x.png": 60, + "icon-29.png": 29, + "icon-29@2x.png": 58, + "icon-29@3x.png": 87, + "icon-40.png": 40, + "icon-40@2x.png": 80, + "icon-40@3x.png": 120, + "icon-60@2x.png": 120, + "icon-60@3x.png": 180, + "icon-76.png": 76, + "icon-76@2x.png": 152, + "icon-83.5@2x.png": 167, + "icon-1024.png": 1024, + } + + // To avoid duplicate work, use a small cache of resized images by dimension + cache := map[int]image.Image{} + resize := func(dim int) image.Image { + if img, ok := cache[dim]; ok { + return img + } + dst := image.NewRGBA(image.Rect(0, 0, dim, dim)) + draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil) + cache[dim] = dst + return dst + } + + for filename, dim := range sizes { + // Create output file + outPath := filepath.Join(appIconsetDir, filename) + f, err := os.Create(outPath) + if err != nil { + return err + } + if err := png.Encode(f, resize(dim)); err != nil { + _ = f.Close() + return fmt.Errorf("encode %s: %w", filename, err) + } + if err := f.Close(); err != nil { + return err + } + } + return nil +} + +// iosBuildYAML is a permissive schema used to populate iOS project config from build/config.yml. +type iosBuildYAML struct { + IOS struct { + BundleID string `yaml:"bundleID"` + DisplayName string `yaml:"displayName"` + Version string `yaml:"version"` + Company string `yaml:"company"` + Comments string `yaml:"comments"` + } `yaml:"ios"` + Info struct { + ProductName string `yaml:"productName"` + ProductIdentifier string `yaml:"productIdentifier"` + Version string `yaml:"version"` + CompanyName string `yaml:"companyName"` + Comments string `yaml:"comments"` + Copyright string `yaml:"copyright"` + Description string `yaml:"description"` + } `yaml:"info"` +} + +// loadIOSProjectConfig merges defaults with values from build/config.yml if present. +func loadIOSProjectConfig(configPath string, cfg *iOSProjectConfig) error { + if configPath == "" { + return nil + } + if _, err := os.Stat(configPath); os.IsNotExist(err) { + return nil + } + data, err := os.ReadFile(configPath) + if err != nil { + return err + } + var in iosBuildYAML + if err := yaml.Unmarshal(data, &in); err != nil { + return err + } + // Prefer ios.* if set, otherwise fall back to info.* where applicable + if in.IOS.DisplayName != "" { + cfg.ProductName = in.IOS.DisplayName + } else if in.Info.ProductName != "" { + cfg.ProductName = in.Info.ProductName + } + if in.IOS.BundleID != "" { + cfg.ProductIdentifier = in.IOS.BundleID + } else if in.Info.ProductIdentifier != "" { + cfg.ProductIdentifier = in.Info.ProductIdentifier + } + if in.IOS.Version != "" { + cfg.ProductVersion = in.IOS.Version + } else if in.Info.Version != "" { + cfg.ProductVersion = in.Info.Version + } + if in.IOS.Company != "" { + cfg.ProductCompany = in.IOS.Company + } else if in.Info.CompanyName != "" { + cfg.ProductCompany = in.Info.CompanyName + } + if in.IOS.Comments != "" { + cfg.ProductComments = in.IOS.Comments + } else if in.Info.Comments != "" { + cfg.ProductComments = in.Info.Comments + } + // Copyright comes from info.* for now (no iOS override defined yet) + if in.Info.Copyright != "" { + cfg.ProductCopyright = in.Info.Copyright + } + // Description comes from info.* for now (no iOS override defined yet) + if in.Info.Description != "" { + cfg.ProductDescription = in.Info.Description + } + // BinaryName remains default unless we later add config support + return nil +} + +// iOSProjectConfig is a minimal config used to fill templates. Extend later to read build/config.yml. +type iOSProjectConfig struct { + ProductName string + BinaryName string + ProductIdentifier string + ProductVersion string + ProductCompany string + ProductComments string + ProductCopyright string + ProductDescription string +} + +// IOSXcodeGen generates an Xcode project skeleton for the current app. +func IOSXcodeGen(options *IOSXcodeGenOptions) error { + outDir := options.OutDir + if outDir == "" { + outDir = filepath.Join("build", "ios", "xcode") + } + if err := os.MkdirAll(outDir, 0o755); err != nil { + return err + } + + // Create standard layout + mainDir := filepath.Join(outDir, "main") + if err := os.MkdirAll(mainDir, 0o755); err != nil { + return err + } + // Create placeholder .xcodeproj dir + xcodeprojDir := filepath.Join(outDir, "main.xcodeproj") + if err := os.MkdirAll(xcodeprojDir, 0o755); err != nil { + return err + } + + // Prepare config with defaults, then merge from build/config.yml if present + cfg := iOSProjectConfig{ + ProductName: "Wails App", + BinaryName: "wailsapp", + ProductIdentifier: "com.wails.app", + ProductVersion: "0.1.0", + ProductCompany: "", + ProductComments: "", + ProductCopyright: "", + ProductDescription: "", + } + if err := loadIOSProjectConfig(options.Config, &cfg); err != nil { + return fmt.Errorf("parse config: %w", err) + } + + // Render Info.plist + if err := renderTemplateTo(updatableBuildAssets, "updatable_build_assets/ios/Info.plist.tmpl", filepath.Join(mainDir, "Info.plist"), cfg); err != nil { + return fmt.Errorf("render Info.plist: %w", err) + } + // Render LaunchScreen.storyboard + if err := renderTemplateTo(updatableBuildAssets, "updatable_build_assets/ios/LaunchScreen.storyboard.tmpl", filepath.Join(mainDir, "LaunchScreen.storyboard"), cfg); err != nil { + return fmt.Errorf("render LaunchScreen.storyboard: %w", err) + } + + // Copy main.m from assets (lives under build_assets) + if err := copyEmbeddedFile(buildAssets, "build_assets/ios/main.m", filepath.Join(mainDir, "main.m")); err != nil { + return fmt.Errorf("copy main.m: %w", err) + } + + // Create Assets.xcassets/AppIcon.appiconset and Contents.json + assetsDir := filepath.Join(mainDir, "Assets.xcassets", "AppIcon.appiconset") + if err := os.MkdirAll(assetsDir, 0o755); err != nil { + return err + } + if err := renderTemplateTo(updatableBuildAssets, "updatable_build_assets/ios/Assets.xcassets.tmpl", filepath.Join(assetsDir, "Contents.json"), cfg); err != nil { + return fmt.Errorf("render AppIcon Contents.json: %w", err) + } + + // Generate iOS AppIcon PNGs from build/appicon.png if present; otherwise use embedded default + inputIcon := filepath.Join("build", "appicon.png") + if _, err := os.Stat(inputIcon); err == nil { + if err := generateIOSAppIcons(inputIcon, assetsDir); err != nil { + return fmt.Errorf("generate iOS icons: %w", err) + } + } else { + if data, rerr := buildAssets.ReadFile("build_assets/appicon.png"); rerr == nil { + if err := generateIOSAppIconsFromReader(bytes.NewReader(data), assetsDir); err != nil { + return fmt.Errorf("generate default iOS icons: %w", err) + } + } + } + + // Render project.pbxproj from template + projectPbxproj := filepath.Join(xcodeprojDir, "project.pbxproj") + if err := renderTemplateTo(updatableBuildAssets, "updatable_build_assets/ios/project.pbxproj.tmpl", projectPbxproj, cfg); err != nil { + return fmt.Errorf("render project.pbxproj: %w", err) + } + return nil +} + +// renderTemplateTo reads a template file from an embed FS and writes it to dest using data. +func renderTemplateTo(efs fs.FS, templatePath, dest string, data any) error { + raw, err := fs.ReadFile(efs, templatePath) + if err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil { + return err + } + t, err := template.New(filepath.Base(templatePath)).Parse(string(raw)) + if err != nil { + return err + } + f, err := os.Create(dest) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + return t.Execute(f, data) +} + +// copyEmbeddedFile writes a file from an embed FS path to dest. +func copyEmbeddedFile(efs fs.FS, src, dest string) error { + data, err := fs.ReadFile(efs, src) + if err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil { + return err + } + return os.WriteFile(dest, data, 0o644) +} + +// IOSXcodeGenCmd is a CLI entry compatible with NewSubCommandFunction. +// Defaults: +// config: ./build/config.yml (optional) +// out: ./build/ios/xcode +func IOSXcodeGenCmd() error { + out := filepath.Join("build", "ios", "xcode") + cfg := filepath.Join("build", "config.yml") + return IOSXcodeGen(&IOSXcodeGenOptions{OutDir: out, Config: cfg}) +} diff --git a/v3/internal/commands/linuxdeploy-plugin-gtk.sh b/v3/internal/commands/linuxdeploy-plugin-gtk.sh new file mode 100644 index 000000000..51c1231dd --- /dev/null +++ b/v3/internal/commands/linuxdeploy-plugin-gtk.sh @@ -0,0 +1,376 @@ +#! /usr/bin/env bash + +# Source: https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh +# License: MIT (https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/blob/master/LICENSE.txt) + +# GTK3 environment variables: https://developer.gnome.org/gtk3/stable/gtk-running.html +# GTK4 environment variables: https://developer.gnome.org/gtk4/stable/gtk-running.html + +# abort on all errors +set -e + +if [ "$DEBUG" != "" ]; then + set -x + verbose="--verbose" +fi + +SCRIPT="$(basename "$(readlink -f "$0")")" + +show_usage() { + echo "Usage: $SCRIPT --appdir " + echo + echo "Bundles resources for applications that use GTK into an AppDir" + echo + echo "Required variables:" + echo " LINUXDEPLOY=\".../linuxdeploy\" path to linuxdeploy (e.g., AppImage); set automatically when plugin is run directly by linuxdeploy" + echo + echo "Optional variables:" + echo " DEPLOY_GTK_VERSION (major version of GTK to deploy, e.g. '2', '3' or '4'; auto-detect by default)" +} + +variable_is_true() { + local var="$1" + + if [ -n "$var" ] && { [ "$var" == "true" ] || [ "$var" -gt 0 ]; } 2> /dev/null; then + return 0 # true + else + return 1 # false + fi +} + +get_pkgconf_variable() { + local variable="$1" + local library="$2" + local default_value="$3" + + pkgconfig_ret="$("$PKG_CONFIG" --variable="$variable" "$library")" + if [ -n "$pkgconfig_ret" ]; then + echo "$pkgconfig_ret" + elif [ -n "$default_value" ]; then + echo "$default_value" + else + echo "$0: there is no '$variable' variable for '$library' library." > /dev/stderr + echo "Please check the '$library.pc' file is present in \$PKG_CONFIG_PATH (you may need to install the appropriate -dev/-devel package)." > /dev/stderr + exit 1 + fi +} + +copy_tree() { + local src=("${@:1:$#-1}") + local dst="${*:$#}" + + for elem in "${src[@]}"; do + mkdir -p "${dst::-1}$elem" + cp "$elem" --archive --parents --target-directory="$dst" $verbose + done +} + +copy_lib_tree() { + # The source lib directory could be /usr/lib, /usr/lib64, or /usr/lib/x86_64-linux-gnu + # Therefore, when copying lib directories, we need to transform that target path + # to a consistent /usr/lib + local src=("${@:1:$#-1}") + local dst="${*:$#}" + + for elem in "${src[@]}"; do + mkdir -p "${dst::-1}${elem/$LD_GTK_LIBRARY_PATH//usr/lib}" + pushd "$LD_GTK_LIBRARY_PATH" + cp "$(realpath --relative-to="$LD_GTK_LIBRARY_PATH" "$elem")" --archive --parents --target-directory="$dst/usr/lib" $verbose + popd + done +} + +get_triplet_path() { + if command -v dpkg-architecture > /dev/null; then + echo "/usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)" + fi +} + + + +search_library_path() { + PATH_ARRAY=( + "$(get_triplet_path)" + "/usr/lib64" + "/usr/lib" + ) + + for path in "${PATH_ARRAY[@]}"; do + if [ -d "$path" ]; then + echo "$path" + return 0 + fi + done +} + +search_tool() { + local tool="$1" + local directory="$2" + + if command -v "$tool"; then + return 0 + fi + + PATH_ARRAY=( + "$(get_triplet_path)/$directory/$tool" + "/usr/lib64/$directory/$tool" + "/usr/lib/$directory/$tool" + "/usr/bin/$tool" + "/usr/bin/$tool-64" + "/usr/bin/$tool-32" + ) + + for path in "${PATH_ARRAY[@]}"; do + if [ -x "$path" ]; then + echo "$path" + return 0 + fi + done +} + +DEPLOY_GTK_VERSION="${DEPLOY_GTK_VERSION:-0}" # When not set by user, this variable use the integer '0' as a sentinel value +APPDIR= + +while [ "$1" != "" ]; do + case "$1" in + --plugin-api-version) + echo "0" + exit 0 + ;; + --appdir) + APPDIR="$2" + shift + shift + ;; + --help) + show_usage + exit 0 + ;; + *) + echo "Invalid argument: $1" + echo + show_usage + exit 1 + ;; + esac +done + +if [ "$APPDIR" == "" ]; then + show_usage + exit 1 +fi + +APPDIR="$(realpath "$APPDIR")" +mkdir -p "$APPDIR" + +. /etc/os-release +if [ "$ID" = "debian" ] || [ "$ID" = "ubuntu" ]; then + if ! command -v dpkg-architecture &>/dev/null; then + echo -e "$0: dpkg-architecture not found.\nInstall dpkg-dev then re-run the plugin." + exit 1 + fi +fi + +if command -v pkgconf > /dev/null; then + PKG_CONFIG="pkgconf" +elif command -v pkg-config > /dev/null; then + PKG_CONFIG="pkg-config" +else + echo "$0: pkg-config/pkgconf not found in PATH, aborting" + exit 1 +fi + +# GTK's library path *must not* have a trailing slash for later parameter substitution to work properly +LD_GTK_LIBRARY_PATH="$(realpath "${LD_GTK_LIBRARY_PATH:-$(search_library_path)}")" + +if ! command -v find &>/dev/null && ! type find &>/dev/null; then + echo -e "$0: find not found.\nInstall findutils then re-run the plugin." + exit 1 +fi + +if [ -z "$LINUXDEPLOY" ]; then + echo -e "$0: LINUXDEPLOY environment variable is not set.\nDownload a suitable linuxdeploy AppImage, set the environment variable and re-run the plugin." + exit 1 +fi + +gtk_versions=0 # Count major versions of GTK when auto-detect GTK version +if [ "$DEPLOY_GTK_VERSION" -eq 0 ]; then + echo "Determining which GTK version to deploy" + while IFS= read -r -d '' file; do + if [ "$DEPLOY_GTK_VERSION" -ne 2 ] && ldd "$file" | grep -q "libgtk-x11-2.0.so"; then + DEPLOY_GTK_VERSION=2 + gtk_versions="$((gtk_versions+1))" + fi + if [ "$DEPLOY_GTK_VERSION" -ne 3 ] && ldd "$file" | grep -q "libgtk-3.so"; then + DEPLOY_GTK_VERSION=3 + gtk_versions="$((gtk_versions+1))" + fi + if [ "$DEPLOY_GTK_VERSION" -ne 4 ] && ldd "$file" | grep -q "libgtk-4.so"; then + DEPLOY_GTK_VERSION=4 + gtk_versions="$((gtk_versions+1))" + fi + done < <(find "$APPDIR/usr/bin" -executable -type f -print0) +fi + +if [ "$gtk_versions" -gt 1 ]; then + echo "$0: can not deploy multiple GTK versions at the same time." + echo "Please set DEPLOY_GTK_VERSION to {2, 3, 4}." + exit 1 +elif [ "$DEPLOY_GTK_VERSION" -eq 0 ]; then + echo "$0: failed to auto-detect GTK version." + echo "Please set DEPLOY_GTK_VERSION to {2, 3, 4}." + exit 1 +fi + +echo "Installing AppRun hook" +HOOKSDIR="$APPDIR/apprun-hooks" +HOOKFILE="$HOOKSDIR/linuxdeploy-plugin-gtk.sh" +mkdir -p "$HOOKSDIR" +cat > "$HOOKFILE" <<\EOF +#! /usr/bin/env bash + +COLOR_SCHEME="$(dbus-send --session --dest=org.freedesktop.portal.Desktop --type=method_call --print-reply --reply-timeout=1000 /org/freedesktop/portal/desktop org.freedesktop.portal.Settings.Read 'string:org.freedesktop.appearance' 'string:color-scheme' 2> /dev/null | tail -n1 | cut -b35- | cut -d' ' -f2 || printf '')" +if [ -z "$COLOR_SCHEME" ]; then + COLOR_SCHEME="$(gsettings get org.gnome.desktop.interface color-scheme 2> /dev/null || printf '')" +fi +case "$COLOR_SCHEME" in + "1"|"'prefer-dark'") GTK_THEME_VARIANT="dark";; + "2"|"'prefer-light'") GTK_THEME_VARIANT="light";; + *) GTK_THEME_VARIANT="light";; +esac +APPIMAGE_GTK_THEME="${APPIMAGE_GTK_THEME:-"Adwaita:$GTK_THEME_VARIANT"}" # Allow user to override theme (discouraged) + +export APPDIR="${APPDIR:-"$(dirname "$(realpath "$0")")"}" # Workaround to run extracted AppImage +export GTK_DATA_PREFIX="$APPDIR" +export GTK_THEME="$APPIMAGE_GTK_THEME" # Custom themes are broken +export GDK_BACKEND=x11 # Crash with Wayland backend on Wayland +export XDG_DATA_DIRS="$APPDIR/usr/share:/usr/share:$XDG_DATA_DIRS" # g_get_system_data_dirs() from GLib +EOF + +echo "Installing GLib schemas" +# Note: schemasdir is undefined on Ubuntu 16.04 +glib_schemasdir="$(get_pkgconf_variable "schemasdir" "gio-2.0" "/usr/share/glib-2.0/schemas")" +copy_tree "$glib_schemasdir" "$APPDIR/" +glib-compile-schemas "$APPDIR/$glib_schemasdir" +cat >> "$HOOKFILE" <> "$HOOKFILE" <> "$HOOKFILE" < "$APPDIR/${gtk3_immodules_cache_file/$LD_GTK_LIBRARY_PATH//usr/lib}" + else + echo "WARNING: gtk-query-immodules-3.0 not found" + fi + if [ ! -f "$APPDIR/${gtk3_immodules_cache_file/$LD_GTK_LIBRARY_PATH//usr/lib}" ]; then + echo "WARNING: immodules.cache file is missing" + fi + sed -i "s|$gtk3_libdir/3.0.0/immodules/||g" "$APPDIR/${gtk3_immodules_cache_file/$LD_GTK_LIBRARY_PATH//usr/lib}" + ;; + 4) + echo "Installing GTK 4.0 modules" + gtk4_exec_prefix="$(get_pkgconf_variable "exec_prefix" "gtk4" "/usr")" + gtk4_libdir="$(get_pkgconf_variable "libdir" "gtk4")/gtk-4.0" + gtk4_path="$gtk4_libdir" + copy_lib_tree "$gtk4_libdir" "$APPDIR/" + cat >> "$HOOKFILE" <> "$HOOKFILE" < "$APPDIR/${gdk_pixbuf_cache_file/$LD_GTK_LIBRARY_PATH//usr/lib}" +else + echo "WARNING: gdk-pixbuf-query-loaders not found" +fi +if [ ! -f "$APPDIR/${gdk_pixbuf_cache_file/$LD_GTK_LIBRARY_PATH//usr/lib}" ]; then + echo "WARNING: loaders.cache file is missing" +fi +sed -i "s|$gdk_pixbuf_moduledir/||g" "$APPDIR/${gdk_pixbuf_cache_file/$LD_GTK_LIBRARY_PATH//usr/lib}" + +echo "Copying more libraries" +gobject_libdir="$(get_pkgconf_variable "libdir" "gobject-2.0" "$LD_GTK_LIBRARY_PATH")" +gio_libdir="$(get_pkgconf_variable "libdir" "gio-2.0" "$LD_GTK_LIBRARY_PATH")" +librsvg_libdir="$(get_pkgconf_variable "libdir" "librsvg-2.0" "$LD_GTK_LIBRARY_PATH")" +pango_libdir="$(get_pkgconf_variable "libdir" "pango" "$LD_GTK_LIBRARY_PATH")" +pangocairo_libdir="$(get_pkgconf_variable "libdir" "pangocairo" "$LD_GTK_LIBRARY_PATH")" +pangoft2_libdir="$(get_pkgconf_variable "libdir" "pangoft2" "$LD_GTK_LIBRARY_PATH")" +FIND_ARRAY=( + "$gdk_libdir" "libgdk_pixbuf-*.so*" + "$gobject_libdir" "libgobject-*.so*" + "$gio_libdir" "libgio-*.so*" + "$librsvg_libdir" "librsvg-*.so*" + "$pango_libdir" "libpango-*.so*" + "$pangocairo_libdir" "libpangocairo-*.so*" + "$pangoft2_libdir" "libpangoft2-*.so*" +) +LIBRARIES=() +for (( i=0; i<${#FIND_ARRAY[@]}; i+=2 )); do + directory=${FIND_ARRAY[i]} + library=${FIND_ARRAY[i+1]} + while IFS= read -r -d '' file; do + LIBRARIES+=( "--library=$file" ) + done < <(find "$directory" \( -type l -o -type f \) -name "$library" -print0) +done + +env LINUXDEPLOY_PLUGIN_MODE=1 "$LINUXDEPLOY" --appdir="$APPDIR" "${LIBRARIES[@]}" + +# Create symbolic links as a workaround +# Details: https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/issues/24#issuecomment-1030026529 +echo "Manually setting rpath for GTK modules" +PATCH_ARRAY=( + "$gtk3_immodulesdir" + "$gtk3_printbackendsdir" + "$gdk_pixbuf_moduledir" +) +for directory in "${PATCH_ARRAY[@]}"; do + while IFS= read -r -d '' file; do + ln $verbose -sf "${file/$LD_GTK_LIBRARY_PATH\//}" "$APPDIR/usr/lib" + done < <(find "$directory" -name '*.so' -print0) +done \ No newline at end of file diff --git a/v3/internal/commands/msix.go b/v3/internal/commands/msix.go new file mode 100644 index 000000000..6c803e6d8 --- /dev/null +++ b/v3/internal/commands/msix.go @@ -0,0 +1,485 @@ +package commands + +import ( + "embed" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "text/template" + + "github.com/wailsapp/wails/v3/internal/flags" +) + +//go:embed build_assets/windows/msix/* +var msixAssets embed.FS + +// MSIXOptions represents the configuration for MSIX packaging +type MSIXOptions struct { + // Info from project config + Info struct { + CompanyName string `json:"companyName"` + ProductName string `json:"productName"` + ProductVersion string `json:"version"` + ProductIdentifier string `json:"productIdentifier"` + Description string `json:"description"` + Copyright string `json:"copyright"` + Comments string `json:"comments"` + } + // File associations + FileAssociations []struct { + Ext string `json:"ext"` + Name string `json:"name"` + Description string `json:"description"` + IconName string `json:"iconName"` + Role string `json:"role"` + MimeType string `json:"mimeType,omitempty"` + } `json:"fileAssociations"` + // MSIX specific options + Publisher string `json:"publisher"` + CertificatePath string `json:"certificatePath"` + CertificatePassword string `json:"certificatePassword,omitempty"` + ProcessorArchitecture string `json:"processorArchitecture"` + ExecutableName string `json:"executableName"` + ExecutablePath string `json:"executablePath"` + OutputPath string `json:"outputPath"` + UseMsixPackagingTool bool `json:"useMsixPackagingTool"` + UseMakeAppx bool `json:"useMakeAppx"` +} + +// ToolMSIX creates an MSIX package for Windows applications +func ToolMSIX(options *flags.ToolMSIX) error { + DisableFooter = true + + if runtime.GOOS != "windows" { + return fmt.Errorf("MSIX packaging is only supported on Windows") + } + + // Check if required tools are installed + if err := checkMSIXTools(options); err != nil { + return err + } + + // Load project configuration + configPath := options.ConfigPath + if configPath == "" { + configPath = "wails.json" + } + + // Read the config file + configData, err := os.ReadFile(configPath) + if err != nil { + return fmt.Errorf("error reading config file: %w", err) + } + + // Parse the config + var config struct { + Info map[string]interface{} `json:"info"` + FileAssociations []map[string]interface{} `json:"fileAssociations"` + } + if err := json.Unmarshal(configData, &config); err != nil { + return fmt.Errorf("error parsing config file: %w", err) + } + + // Create MSIX options + msixOptions := MSIXOptions{ + Publisher: options.Publisher, + CertificatePath: options.CertificatePath, + CertificatePassword: options.CertificatePassword, + ProcessorArchitecture: options.Arch, + ExecutableName: options.ExecutableName, + ExecutablePath: options.ExecutablePath, + OutputPath: options.OutputPath, + UseMsixPackagingTool: options.UseMsixPackagingTool, + UseMakeAppx: options.UseMakeAppx, + } + + // Copy info from config + infoBytes, err := json.Marshal(config.Info) + if err != nil { + return fmt.Errorf("error marshaling info: %w", err) + } + if err := json.Unmarshal(infoBytes, &msixOptions.Info); err != nil { + return fmt.Errorf("error unmarshaling info: %w", err) + } + + // Copy file associations from config + if len(config.FileAssociations) > 0 { + faBytes, err := json.Marshal(config.FileAssociations) + if err != nil { + return fmt.Errorf("error marshaling file associations: %w", err) + } + if err := json.Unmarshal(faBytes, &msixOptions.FileAssociations); err != nil { + return fmt.Errorf("error unmarshaling file associations: %w", err) + } + } + + // Validate options + if err := validateMSIXOptions(&msixOptions); err != nil { + return err + } + + // Create MSIX package + if msixOptions.UseMsixPackagingTool { + return createMSIXWithPackagingTool(&msixOptions) + } else if msixOptions.UseMakeAppx { + return createMSIXWithMakeAppx(&msixOptions) + } + + // Default to MakeAppx if neither is specified + return createMSIXWithMakeAppx(&msixOptions) +} + +// checkMSIXTools checks if the required tools for MSIX packaging are installed +func checkMSIXTools(options *flags.ToolMSIX) error { + // Check if MsixPackagingTool is installed if requested + if options.UseMsixPackagingTool { + cmd := exec.Command("powershell", "-Command", "Get-AppxPackage -Name Microsoft.MsixPackagingTool") + if err := cmd.Run(); err != nil { + return fmt.Errorf("Microsoft MSIX Packaging Tool is not installed. Please install it from the Microsoft Store") + } + } + + // Check if MakeAppx is available if requested + if options.UseMakeAppx { + cmd := exec.Command("where", "MakeAppx.exe") + if err := cmd.Run(); err != nil { + return fmt.Errorf("MakeAppx.exe is not found in PATH. Please install the Windows SDK") + } + } + + // If neither is specified, check for MakeAppx as the default + if !options.UseMsixPackagingTool && !options.UseMakeAppx { + cmd := exec.Command("where", "MakeAppx.exe") + if err := cmd.Run(); err != nil { + return fmt.Errorf("MakeAppx.exe is not found in PATH. Please install the Windows SDK") + } + } + + // Check if signtool is available for signing + if options.CertificatePath != "" { + cmd := exec.Command("where", "signtool.exe") + if err := cmd.Run(); err != nil { + return fmt.Errorf("signtool.exe is not found in PATH. Please install the Windows SDK") + } + } + + return nil +} + +// validateMSIXOptions validates the MSIX options +func validateMSIXOptions(options *MSIXOptions) error { + // Check required fields + if options.Info.ProductName == "" { + return fmt.Errorf("product name is required") + } + if options.Info.ProductIdentifier == "" { + return fmt.Errorf("product identifier is required") + } + if options.Info.CompanyName == "" { + return fmt.Errorf("company name is required") + } + if options.ExecutableName == "" { + return fmt.Errorf("executable name is required") + } + if options.ExecutablePath == "" { + return fmt.Errorf("executable path is required") + } + + // Validate executable path + if _, err := os.Stat(options.ExecutablePath); os.IsNotExist(err) { + return fmt.Errorf("executable file not found: %s", options.ExecutablePath) + } + + // Validate certificate path if provided + if options.CertificatePath != "" { + if _, err := os.Stat(options.CertificatePath); os.IsNotExist(err) { + return fmt.Errorf("certificate file not found: %s", options.CertificatePath) + } + } + + // Set default processor architecture if not provided + if options.ProcessorArchitecture == "" { + options.ProcessorArchitecture = "x64" + } + + // Set default publisher if not provided + if options.Publisher == "" { + options.Publisher = fmt.Sprintf("CN=%s", options.Info.CompanyName) + } + + // Set default output path if not provided + if options.OutputPath == "" { + options.OutputPath = filepath.Join(".", fmt.Sprintf("%s.msix", options.Info.ProductName)) + } + + return nil +} + +// createMSIXWithPackagingTool creates an MSIX package using the Microsoft MSIX Packaging Tool +func createMSIXWithPackagingTool(options *MSIXOptions) error { + // Create a temporary directory for the template + tempDir, err := os.MkdirTemp("", "wails-msix-") + if err != nil { + return fmt.Errorf("error creating temporary directory: %w", err) + } + defer os.RemoveAll(tempDir) + + // Generate the template file + templatePath := filepath.Join(tempDir, "template.xml") + if err := generateMSIXTemplate(options, templatePath); err != nil { + return fmt.Errorf("error generating MSIX template: %w", err) + } + + // Create the MSIX package + fmt.Println("Creating MSIX package using Microsoft MSIX Packaging Tool...") + args := []string{"create-package", "--template", templatePath} + + // Add certificate password if provided + if options.CertificatePassword != "" { + args = append(args, "--certPassword", options.CertificatePassword) + } + + cmd := exec.Command("MsixPackagingTool.exe", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("error creating MSIX package: %w", err) + } + + fmt.Printf("MSIX package created successfully: %s\n", options.OutputPath) + return nil +} + +// createMSIXWithMakeAppx creates an MSIX package using MakeAppx.exe +func createMSIXWithMakeAppx(options *MSIXOptions) error { + // Create a temporary directory for the package structure + tempDir, err := os.MkdirTemp("", "wails-msix-") + if err != nil { + return fmt.Errorf("error creating temporary directory: %w", err) + } + defer os.RemoveAll(tempDir) + + // Create the package structure + if err := createMSIXPackageStructure(options, tempDir); err != nil { + return fmt.Errorf("error creating MSIX package structure: %w", err) + } + + // Create the MSIX package + fmt.Println("Creating MSIX package using MakeAppx.exe...") + cmd := exec.Command("MakeAppx.exe", "pack", "/d", tempDir, "/p", options.OutputPath) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("error creating MSIX package: %w", err) + } + + // Sign the package if certificate is provided + if options.CertificatePath != "" { + fmt.Println("Signing MSIX package...") + signArgs := []string{"sign", "/fd", "SHA256", "/a", "/f", options.CertificatePath} + + // Add certificate password if provided + if options.CertificatePassword != "" { + signArgs = append(signArgs, "/p", options.CertificatePassword) + } + + signArgs = append(signArgs, options.OutputPath) + + cmd = exec.Command("signtool.exe", signArgs...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("error signing MSIX package: %w", err) + } + } + + fmt.Printf("MSIX package created successfully: %s\n", options.OutputPath) + return nil +} + +// generateMSIXTemplate generates the MSIX template file for the Microsoft MSIX Packaging Tool +func generateMSIXTemplate(options *MSIXOptions, outputPath string) error { + // Read the template file + templateData, err := msixAssets.ReadFile("build_assets/windows/msix/template.xml.tmpl") + if err != nil { + return fmt.Errorf("error reading template file: %w", err) + } + + // Parse the template + tmpl, err := template.New("msix-template").Parse(string(templateData)) + if err != nil { + return fmt.Errorf("error parsing template: %w", err) + } + + // Create the output file + file, err := os.Create(outputPath) + if err != nil { + return fmt.Errorf("error creating output file: %w", err) + } + defer file.Close() + + // Execute the template + if err := tmpl.Execute(file, options); err != nil { + return fmt.Errorf("error executing template: %w", err) + } + + return nil +} + +// createMSIXPackageStructure creates the MSIX package structure for MakeAppx.exe +func createMSIXPackageStructure(options *MSIXOptions, outputDir string) error { + // Create the Assets directory + assetsDir := filepath.Join(outputDir, "Assets") + if err := os.MkdirAll(assetsDir, 0755); err != nil { + return fmt.Errorf("error creating Assets directory: %w", err) + } + + // Generate the AppxManifest.xml file + manifestPath := filepath.Join(outputDir, "AppxManifest.xml") + if err := generateAppxManifest(options, manifestPath); err != nil { + return fmt.Errorf("error generating AppxManifest.xml: %w", err) + } + + // Copy the executable + executableDest := filepath.Join(outputDir, filepath.Base(options.ExecutablePath)) + if err := copyFile(options.ExecutablePath, executableDest); err != nil { + return fmt.Errorf("error copying executable: %w", err) + } + + // Copy any additional files needed for the application + // This would include DLLs, resources, etc. + // For now, we'll just copy the executable + + // Generate placeholder assets + assets := []string{ + "Square150x150Logo.png", + "Square44x44Logo.png", + "Wide310x150Logo.png", + "SplashScreen.png", + "StoreLogo.png", + } + + // Add FileIcon.png if there are file associations + if len(options.FileAssociations) > 0 { + assets = append(assets, "FileIcon.png") + } + + // Generate placeholder assets + for _, asset := range assets { + assetPath := filepath.Join(assetsDir, asset) + if err := generatePlaceholderImage(assetPath); err != nil { + return fmt.Errorf("error generating placeholder image %s: %w", asset, err) + } + } + + return nil +} + +// generateAppxManifest generates the AppxManifest.xml file +func generateAppxManifest(options *MSIXOptions, outputPath string) error { + // Read the template file + templateData, err := msixAssets.ReadFile("build_assets/windows/msix/app_manifest.xml.tmpl") + if err != nil { + return fmt.Errorf("error reading template file: %w", err) + } + + // Parse the template + tmpl, err := template.New("appx-manifest").Parse(string(templateData)) + if err != nil { + return fmt.Errorf("error parsing template: %w", err) + } + + // Create the output file + file, err := os.Create(outputPath) + if err != nil { + return fmt.Errorf("error creating output file: %w", err) + } + defer file.Close() + + // Execute the template + if err := tmpl.Execute(file, options); err != nil { + return fmt.Errorf("error executing template: %w", err) + } + + return nil +} + +// generatePlaceholderImage generates a placeholder image file +func generatePlaceholderImage(outputPath string) error { + // For now, we'll create a simple 1x1 transparent PNG + // In a real implementation, we would generate proper icons based on the application icon + + // Create a minimal valid PNG file (1x1 transparent pixel) + pngData := []byte{ + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, + 0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, + 0x05, 0x00, 0x01, 0x0D, 0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49, + 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, + } + + return os.WriteFile(outputPath, pngData, 0644) +} + +// copyFile copies a file from src to dst +func copyFile(src, dst string) error { + // Read the source file + data, err := os.ReadFile(src) + if err != nil { + return err + } + + // Write the destination file + return os.WriteFile(dst, data, 0644) +} + +// InstallMSIXTools installs the required tools for MSIX packaging +func InstallMSIXTools() error { + // Check if running on Windows + if runtime.GOOS != "windows" { + return fmt.Errorf("MSIX packaging is only supported on Windows") + } + + fmt.Println("Installing MSIX packaging tools...") + + // Install MSIX Packaging Tool from Microsoft Store + fmt.Println("Installing Microsoft MSIX Packaging Tool from Microsoft Store...") + cmd := exec.Command("powershell", "-Command", "Start-Process ms-windows-store://pdp/?ProductId=9N5R1TQPJVBP") + if err := cmd.Run(); err != nil { + return fmt.Errorf("error launching Microsoft Store: %w", err) + } + + // Check if Windows SDK is installed + fmt.Println("Checking for Windows SDK...") + sdkInstalled := false + cmd = exec.Command("where", "MakeAppx.exe") + if err := cmd.Run(); err == nil { + sdkInstalled = true + fmt.Println("Windows SDK is already installed.") + } + + // Install Windows SDK if not installed + if !sdkInstalled { + fmt.Println("Windows SDK is not installed. Please download and install from:") + fmt.Println("https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/") + + // Open the download page + cmd = exec.Command("powershell", "-Command", "Start-Process https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/") + if err := cmd.Run(); err != nil { + return fmt.Errorf("error opening Windows SDK download page: %w", err) + } + } + + fmt.Println("MSIX packaging tools installation initiated. Please complete the installation process in the opened windows.") + return nil +} + +// init registers the MSIX command +func init() { + // Register the MSIX command in the CLI + // This will be called by the CLI framework +} diff --git a/v3/internal/commands/myapp.ARCHLINUX b/v3/internal/commands/myapp.ARCHLINUX new file mode 100644 index 000000000..8dfb9fa69 Binary files /dev/null and b/v3/internal/commands/myapp.ARCHLINUX differ diff --git a/v3/internal/commands/myapp.DEB b/v3/internal/commands/myapp.DEB new file mode 100644 index 000000000..7361bdc19 Binary files /dev/null and b/v3/internal/commands/myapp.DEB differ diff --git a/v3/internal/commands/myapp.RPM b/v3/internal/commands/myapp.RPM new file mode 100644 index 000000000..16a2d484e Binary files /dev/null and b/v3/internal/commands/myapp.RPM differ diff --git a/v3/internal/commands/releasenotes.go b/v3/internal/commands/releasenotes.go new file mode 100644 index 000000000..a90c7cb1b --- /dev/null +++ b/v3/internal/commands/releasenotes.go @@ -0,0 +1,34 @@ +package commands + +import ( + "github.com/wailsapp/wails/v3/internal/github" + "github.com/wailsapp/wails/v3/internal/term" + "github.com/wailsapp/wails/v3/internal/version" +) + +type ReleaseNotesOptions struct { + Version string `name:"v" description:"The version to show release notes for"` + NoColour bool `name:"n" description:"Disable colour output"` +} + +func ReleaseNotes(options *ReleaseNotesOptions) error { + if options.NoColour { + term.DisableColor() + } + + term.Header("Release Notes") + + if version.IsDev() { + term.Println("Release notes are not available for development builds") + return nil + } + + currentVersion := version.String() + if options.Version != "" { + currentVersion = options.Version + } + + releaseNotes := github.GetReleaseNotes(currentVersion, options.NoColour) + term.Println(releaseNotes) + return nil +} diff --git a/v3/internal/commands/runtime.go b/v3/internal/commands/runtime.go new file mode 100644 index 000000000..7414c8b32 --- /dev/null +++ b/v3/internal/commands/runtime.go @@ -0,0 +1,47 @@ +package commands + +import ( + "io" + "os" + "path/filepath" + "runtime" +) + +type RuntimeOptions struct { + Directory string `name:"d" description:"Directory to generate runtime file in" default:"."` +} + +func GenerateRuntime(options *RuntimeOptions) error { + DisableFooter = true + _, thisFile, _, _ := runtime.Caller(0) + localDir := filepath.Dir(thisFile) + bundledAssetsDir := filepath.Join(localDir, "..", "assetserver", "bundledassets") + runtimeJS := filepath.Join(bundledAssetsDir, "runtime.js") + err := CopyFile(runtimeJS, filepath.Join(options.Directory, "runtime.js")) + if err != nil { + return err + } + runtimeDebugJS := filepath.Join(bundledAssetsDir, "runtime.debug.js") + err = CopyFile(runtimeDebugJS, filepath.Join(options.Directory, "runtime-debug.js")) + if err != nil { + return err + } + return nil +} + +func CopyFile(source string, target string) error { + s, err := os.Open(source) + if err != nil { + return err + } + defer s.Close() + d, err := os.Create(target) + if err != nil { + return err + } + if _, err := io.Copy(d, s); err != nil { + d.Close() + return err + } + return d.Close() +} diff --git a/v3/internal/commands/service.go b/v3/internal/commands/service.go new file mode 100644 index 000000000..8f5a6bd0a --- /dev/null +++ b/v3/internal/commands/service.go @@ -0,0 +1,44 @@ +package commands + +import ( + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/service" + "github.com/wailsapp/wails/v3/internal/term" + "strings" +) + +func toCamelCasePlugin(s string) string { + var camelCase string + var capitalize = true + + for _, c := range s { + if c >= 'a' && c <= 'z' || c >= '0' && c <= '9' { + if capitalize { + camelCase += strings.ToUpper(string(c)) + capitalize = false + } else { + camelCase += string(c) + } + } else if c >= 'A' && c <= 'Z' { + camelCase += string(c) + capitalize = false + } else { + capitalize = true + } + } + + return camelCase + "Plugin" +} + +func ServiceInit(options *flags.ServiceInit) error { + + if options.Quiet { + term.DisableOutput() + } + + if options.PackageName == "" { + options.PackageName = toCamelCasePlugin(options.Name) + } + + return service.Install(options) +} diff --git a/v3/internal/commands/setup.go b/v3/internal/commands/setup.go new file mode 100644 index 000000000..f1a04274b --- /dev/null +++ b/v3/internal/commands/setup.go @@ -0,0 +1,13 @@ +package commands + +import ( + "github.com/wailsapp/wails/v3/internal/setupwizard" +) + +type SetupOptions struct{} + +func Setup(_ *SetupOptions) error { + DisableFooter = true + wizard := setupwizard.New() + return wizard.Run() +} diff --git a/v3/internal/commands/sign.go b/v3/internal/commands/sign.go new file mode 100644 index 000000000..b862f83fa --- /dev/null +++ b/v3/internal/commands/sign.go @@ -0,0 +1,396 @@ +package commands + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/keychain" +) + +// Sign signs a binary or package +func Sign(options *flags.Sign) error { + if options.Input == "" { + return fmt.Errorf("--input is required") + } + + // Check input file exists + info, err := os.Stat(options.Input) + if err != nil { + return fmt.Errorf("input file not found: %w", err) + } + + // Determine what type of signing to do based on file extension and flags + ext := strings.ToLower(filepath.Ext(options.Input)) + + // macOS app bundle (directory) + if info.IsDir() && strings.HasSuffix(options.Input, ".app") { + return signMacOSApp(options) + } + + // macOS binary or Windows executable + if ext == ".exe" || ext == ".msi" || ext == ".msix" || ext == ".appx" { + return signWindows(options) + } + + // Linux packages + if ext == ".deb" { + return signDEB(options) + } + if ext == ".rpm" { + return signRPM(options) + } + + // macOS binary (no extension typically) + if runtime.GOOS == "darwin" && options.Identity != "" { + return signMacOSBinary(options) + } + + return fmt.Errorf("unsupported file type: %s", ext) +} + +func signMacOSApp(options *flags.Sign) error { + if options.Identity == "" { + return fmt.Errorf("--identity is required for macOS signing") + } + + if options.Verbose { + pterm.Info.Printfln("Signing macOS app bundle: %s", options.Input) + } + + // Build codesign command + args := []string{ + "--force", + "--deep", + "--sign", options.Identity, + } + + if options.Entitlements != "" { + args = append(args, "--entitlements", options.Entitlements) + } + + if options.HardenedRuntime || options.Notarize { + args = append(args, "--options", "runtime") + } + + args = append(args, options.Input) + + cmd := exec.Command("codesign", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("codesign failed: %w", err) + } + + pterm.Success.Printfln("Signed: %s", options.Input) + + // Notarize if requested + if options.Notarize { + return notarizeMacOSApp(options) + } + + return nil +} + +func signMacOSBinary(options *flags.Sign) error { + if options.Identity == "" { + return fmt.Errorf("--identity is required for macOS signing") + } + + if options.Verbose { + pterm.Info.Printfln("Signing macOS binary: %s", options.Input) + } + + args := []string{ + "--force", + "--sign", options.Identity, + } + + if options.Entitlements != "" { + args = append(args, "--entitlements", options.Entitlements) + } + + if options.HardenedRuntime { + args = append(args, "--options", "runtime") + } + + args = append(args, options.Input) + + cmd := exec.Command("codesign", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("codesign failed: %w", err) + } + + pterm.Success.Printfln("Signed: %s", options.Input) + return nil +} + +func notarizeMacOSApp(options *flags.Sign) error { + if options.KeychainProfile == "" { + return fmt.Errorf("--keychain-profile is required for notarization") + } + + if options.Verbose { + pterm.Info.Println("Submitting for notarization...") + } + + // Create a zip for notarization + zipPath := options.Input + ".zip" + zipCmd := exec.Command("ditto", "-c", "-k", "--keepParent", options.Input, zipPath) + if err := zipCmd.Run(); err != nil { + return fmt.Errorf("failed to create zip for notarization: %w", err) + } + defer os.Remove(zipPath) + + // Submit for notarization + args := []string{ + "notarytool", "submit", + zipPath, + "--keychain-profile", options.KeychainProfile, + "--wait", + } + + cmd := exec.Command("xcrun", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("notarization failed: %w", err) + } + + // Staple the ticket + stapleCmd := exec.Command("xcrun", "stapler", "staple", options.Input) + stapleCmd.Stdout = os.Stdout + stapleCmd.Stderr = os.Stderr + + if err := stapleCmd.Run(); err != nil { + return fmt.Errorf("stapling failed: %w", err) + } + + pterm.Success.Println("Notarization complete and ticket stapled") + return nil +} + +func signWindows(options *flags.Sign) error { + // Get password from keychain if not provided + password := options.Password + if password == "" && options.Certificate != "" { + var err error + password, err = keychain.Get(keychain.KeyWindowsCertPassword) + if err != nil { + pterm.Warning.Printfln("Could not get password from keychain: %v", err) + // Continue without password - might work for some certificates + } + } + + if options.Verbose { + pterm.Info.Printfln("Signing Windows executable: %s", options.Input) + } + + // Try native signtool first on Windows + if runtime.GOOS == "windows" { + err := signWindowsNative(options, password) + if err == nil { + return nil + } + if options.Verbose { + pterm.Warning.Printfln("Native signing failed, trying built-in: %v", err) + } + } + + // Use built-in signing (works cross-platform) + return signWindowsBuiltin(options, password) +} + +func signWindowsNative(options *flags.Sign, password string) error { + // Find signtool.exe + signtool, err := findSigntool() + if err != nil { + return err + } + + args := []string{"sign"} + + if options.Certificate != "" { + args = append(args, "/f", options.Certificate) + if password != "" { + args = append(args, "/p", password) + } + } else if options.Thumbprint != "" { + args = append(args, "/sha1", options.Thumbprint) + } else { + return fmt.Errorf("either --certificate or --thumbprint is required") + } + + // Add timestamp server + timestamp := options.Timestamp + if timestamp == "" { + timestamp = "http://timestamp.digicert.com" + } + args = append(args, "/tr", timestamp, "/td", "SHA256", "/fd", "SHA256") + + args = append(args, options.Input) + + cmd := exec.Command(signtool, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("signtool failed: %w", err) + } + + pterm.Success.Printfln("Signed: %s", options.Input) + return nil +} + +func findSigntool() (string, error) { + // Check if signtool is in PATH + path, err := exec.LookPath("signtool.exe") + if err == nil { + return path, nil + } + + // Common Windows SDK locations + sdkPaths := []string{ + `C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe`, + `C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe`, + `C:\Program Files (x86)\Windows Kits\10\bin\x64\signtool.exe`, + } + + for _, p := range sdkPaths { + if _, err := os.Stat(p); err == nil { + return p, nil + } + } + + return "", fmt.Errorf("signtool.exe not found") +} + +func signWindowsBuiltin(options *flags.Sign, password string) error { + // This would use a Go library for Authenticode signing + // For now, we'll return an error indicating it needs implementation + // In a full implementation, you'd use something like: + // - github.com/AkarinLiu/osslsigncode-go + // - or implement PE signing directly + + if options.Certificate == "" { + return fmt.Errorf("--certificate is required for cross-platform signing") + } + + return fmt.Errorf("built-in Windows signing not yet implemented - please use signtool.exe on Windows, or install osslsigncode") +} + +func signDEB(options *flags.Sign) error { + if options.PGPKey == "" { + return fmt.Errorf("--pgp-key is required for DEB signing") + } + + // Get password from keychain if not provided + password := options.PGPPassword + if password == "" { + var err error + password, err = keychain.Get(keychain.KeyPGPPassword) + if err != nil { + // Password might not be required if key is unencrypted + if options.Verbose { + pterm.Warning.Printfln("Could not get PGP password from keychain: %v", err) + } + } + } + + if options.Verbose { + pterm.Info.Printfln("Signing DEB package: %s", options.Input) + } + + role := options.Role + if role == "" { + role = "builder" + } + + // Use dpkg-sig for signing + args := []string{ + "-k", options.PGPKey, + "--sign", role, + } + + if password != "" { + // dpkg-sig reads from GPG_TTY or gpg-agent + // For scripted use, we need to use gpg with passphrase + args = append(args, "--gpg-options", fmt.Sprintf("--batch --passphrase %s", password)) + } + + args = append(args, options.Input) + + cmd := exec.Command("dpkg-sig", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + // Fallback: try using gpg directly to sign + return signDEBWithGPG(options, password, role) + } + + pterm.Success.Printfln("Signed: %s", options.Input) + return nil +} + +func signDEBWithGPG(options *flags.Sign, password, role string) error { + // Alternative approach using ar and gpg directly + // This is more portable but more complex + return fmt.Errorf("dpkg-sig not found - please install dpkg-sig or use a Linux system") +} + +func signRPM(options *flags.Sign) error { + if options.PGPKey == "" { + return fmt.Errorf("--pgp-key is required for RPM signing") + } + + // Get password from keychain if not provided + password := options.PGPPassword + if password == "" { + var err error + password, err = keychain.Get(keychain.KeyPGPPassword) + if err != nil { + if options.Verbose { + pterm.Warning.Printfln("Could not get PGP password from keychain: %v", err) + } + } + } + + if options.Verbose { + pterm.Info.Printfln("Signing RPM package: %s", options.Input) + } + + // RPM signing requires the key to be imported to GPG keyring + // and uses rpmsign command + args := []string{ + "--addsign", + options.Input, + } + + cmd := exec.Command("rpmsign", args...) + + // Set up passphrase via environment if needed + if password != "" { + cmd.Env = append(os.Environ(), fmt.Sprintf("GPG_PASSPHRASE=%s", password)) + } + + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("rpmsign failed: %w", err) + } + + pterm.Success.Printfln("Signed: %s", options.Input) + return nil +} diff --git a/v3/internal/commands/signing_setup.go b/v3/internal/commands/signing_setup.go new file mode 100644 index 000000000..7bbe64817 --- /dev/null +++ b/v3/internal/commands/signing_setup.go @@ -0,0 +1,588 @@ +package commands + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/packet" + "github.com/charmbracelet/huh" + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/keychain" +) + +// SigningSetup configures signing variables in platform Taskfiles +func SigningSetup(options *flags.SigningSetup) error { + // Determine which platforms to configure + platforms := options.Platforms + if len(platforms) == 0 { + // Auto-detect based on existing Taskfiles + platforms = detectPlatforms() + if len(platforms) == 0 { + return fmt.Errorf("no platform Taskfiles found in build/ directory") + } + } + + for _, platform := range platforms { + var err error + switch platform { + case "darwin": + err = setupDarwinSigning() + case "windows": + err = setupWindowsSigning() + case "linux": + err = setupLinuxSigning() + default: + pterm.Warning.Printfln("Unknown platform: %s", platform) + continue + } + if err != nil { + return err + } + } + + return nil +} + +func detectPlatforms() []string { + var platforms []string + for _, p := range []string{"darwin", "windows", "linux"} { + taskfile := filepath.Join("build", p, "Taskfile.yml") + if _, err := os.Stat(taskfile); err == nil { + platforms = append(platforms, p) + } + } + return platforms +} + +func setupDarwinSigning() error { + pterm.DefaultHeader.Println("macOS Code Signing Setup") + fmt.Println() + + // Get available signing identities + identities, err := getMacOSSigningIdentities() + if err != nil { + pterm.Warning.Printfln("Could not list signing identities: %v", err) + identities = []string{} + } + + var signIdentity string + var keychainProfile string + var entitlements string + var configureNotarization bool + + // Build identity options + var identityOptions []huh.Option[string] + for _, id := range identities { + identityOptions = append(identityOptions, huh.NewOption(id, id)) + } + identityOptions = append(identityOptions, huh.NewOption("Enter manually...", "manual")) + + form := huh.NewForm( + huh.NewGroup( + huh.NewSelect[string](). + Title("Select signing identity"). + Description("Choose your Developer ID certificate"). + Options(identityOptions...). + Value(&signIdentity), + ).WithHideFunc(func() bool { + return len(identities) == 0 + }), + + huh.NewGroup( + huh.NewInput(). + Title("Signing identity"). + Description("e.g., Developer ID Application: Your Company (TEAMID)"). + Placeholder("Developer ID Application: ..."). + Value(&signIdentity), + ).WithHideFunc(func() bool { + return len(identities) > 0 && signIdentity != "manual" + }), + + huh.NewGroup( + huh.NewConfirm(). + Title("Configure notarization?"). + Description("Required for distributing apps outside the App Store"). + Value(&configureNotarization), + ), + + huh.NewGroup( + huh.NewInput(). + Title("Keychain profile name"). + Description("The profile name used with 'wails3 signing credentials'"). + Placeholder("my-notarize-profile"). + Value(&keychainProfile), + ).WithHideFunc(func() bool { + return !configureNotarization + }), + + huh.NewGroup( + huh.NewInput(). + Title("Entitlements file (optional)"). + Description("Path to entitlements plist, leave empty to skip"). + Placeholder("build/darwin/entitlements.plist"). + Value(&entitlements), + ), + ) + + err = form.Run() + if err != nil { + return err + } + + // Handle manual entry + if signIdentity == "manual" { + signIdentity = "" + } + + // Update Taskfile + taskfilePath := filepath.Join("build", "darwin", "Taskfile.yml") + err = updateTaskfileVars(taskfilePath, map[string]string{ + "SIGN_IDENTITY": signIdentity, + "KEYCHAIN_PROFILE": keychainProfile, + "ENTITLEMENTS": entitlements, + }) + if err != nil { + return err + } + + pterm.Success.Printfln("Updated %s", taskfilePath) + + if configureNotarization && keychainProfile != "" { + fmt.Println() + pterm.Info.Println("Next step: Store your notarization credentials:") + fmt.Println() + pterm.Println(pterm.LightBlue(fmt.Sprintf(` wails3 signing credentials \ + --apple-id "your@email.com" \ + --team-id "TEAMID" \ + --password "app-specific-password" \ + --profile "%s"`, keychainProfile))) + fmt.Println() + } + + return nil +} + +func setupWindowsSigning() error { + pterm.DefaultHeader.Println("Windows Code Signing Setup") + fmt.Println() + + var certSource string + var certPath string + var certPassword string + var thumbprint string + var timestampServer string + + form := huh.NewForm( + huh.NewGroup( + huh.NewSelect[string](). + Title("Certificate source"). + Options( + huh.NewOption("Certificate file (.pfx/.p12)", "file"), + huh.NewOption("Windows certificate store (thumbprint)", "store"), + ). + Value(&certSource), + ), + + huh.NewGroup( + huh.NewInput(). + Title("Certificate path"). + Description("Path to your .pfx or .p12 file"). + Placeholder("certs/signing.pfx"). + Value(&certPath), + + huh.NewInput(). + Title("Certificate password"). + Description("Stored securely in system keychain"). + EchoMode(huh.EchoModePassword). + Value(&certPassword), + ).WithHideFunc(func() bool { + return certSource != "file" + }), + + huh.NewGroup( + huh.NewInput(). + Title("Certificate thumbprint"). + Description("SHA-1 thumbprint of the certificate in Windows store"). + Placeholder("ABC123DEF456..."). + Value(&thumbprint), + ).WithHideFunc(func() bool { + return certSource != "store" + }), + + huh.NewGroup( + huh.NewInput(). + Title("Timestamp server (optional)"). + Description("Leave empty for default: http://timestamp.digicert.com"). + Placeholder("http://timestamp.digicert.com"). + Value(×tampServer), + ), + ) + + err := form.Run() + if err != nil { + return err + } + + // Store password in keychain if provided + if certPassword != "" { + err = keychain.Set(keychain.KeyWindowsCertPassword, certPassword) + if err != nil { + return fmt.Errorf("failed to store password in keychain: %w", err) + } + pterm.Success.Println("Certificate password stored in system keychain") + } + + // Update Taskfile (no passwords stored here) + taskfilePath := filepath.Join("build", "windows", "Taskfile.yml") + vars := map[string]string{ + "TIMESTAMP_SERVER": timestampServer, + } + + if certSource == "file" { + vars["SIGN_CERTIFICATE"] = certPath + } else { + vars["SIGN_THUMBPRINT"] = thumbprint + } + + err = updateTaskfileVars(taskfilePath, vars) + if err != nil { + return err + } + + pterm.Success.Printfln("Updated %s", taskfilePath) + return nil +} + +func setupLinuxSigning() error { + pterm.DefaultHeader.Println("Linux Package Signing Setup") + fmt.Println() + + var keySource string + var keyPath string + var keyPassword string + var signRole string + + // For key generation + var genName string + var genEmail string + var genPassword string + + form := huh.NewForm( + huh.NewGroup( + huh.NewSelect[string](). + Title("PGP key source"). + Options( + huh.NewOption("Use existing key", "existing"), + huh.NewOption("Generate new key", "generate"), + ). + Value(&keySource), + ), + + // Existing key options + huh.NewGroup( + huh.NewInput(). + Title("PGP private key path"). + Description("Path to your ASCII-armored private key file"). + Placeholder("signing-key.asc"). + Value(&keyPath), + + huh.NewInput(). + Title("Key password (if encrypted)"). + Description("Stored securely in system keychain"). + EchoMode(huh.EchoModePassword). + Value(&keyPassword), + ).WithHideFunc(func() bool { + return keySource != "existing" + }), + + // Key generation options + huh.NewGroup( + huh.NewInput(). + Title("Name"). + Description("Name for the PGP key"). + Placeholder("Your Name"). + Value(&genName). + Validate(func(s string) error { + if keySource == "generate" && s == "" { + return fmt.Errorf("name is required") + } + return nil + }), + + huh.NewInput(). + Title("Email"). + Description("Email for the PGP key"). + Placeholder("you@example.com"). + Value(&genEmail). + Validate(func(s string) error { + if keySource == "generate" && s == "" { + return fmt.Errorf("email is required") + } + return nil + }), + + huh.NewInput(). + Title("Key password (optional but recommended)"). + Description("Stored securely in system keychain"). + EchoMode(huh.EchoModePassword). + Value(&genPassword), + ).WithHideFunc(func() bool { + return keySource != "generate" + }), + + huh.NewGroup( + huh.NewSelect[string](). + Title("DEB signing role"). + Description("Role for signing Debian packages"). + Options( + huh.NewOption("builder (default)", "builder"), + huh.NewOption("origin", "origin"), + huh.NewOption("maint", "maint"), + huh.NewOption("archive", "archive"), + ). + Value(&signRole), + ), + ) + + err := form.Run() + if err != nil { + return err + } + + // Generate key if requested + if keySource == "generate" { + keyPath = "signing-key.asc" + pubKeyPath := "signing-key.pub.asc" + + pterm.Info.Println("Generating PGP key pair...") + + // Call the key generation + err = generatePGPKeyForSetup(genName, genEmail, genPassword, keyPath, pubKeyPath) + if err != nil { + return fmt.Errorf("failed to generate key: %w", err) + } + + keyPassword = genPassword + + pterm.Success.Printfln("Generated %s and %s", keyPath, pubKeyPath) + fmt.Println() + pterm.Info.Println("Distribute the public key to users so they can verify your packages:") + pterm.Println(pterm.LightBlue(fmt.Sprintf(" # For apt: sudo cp %s /etc/apt/trusted.gpg.d/", pubKeyPath))) + pterm.Println(pterm.LightBlue(fmt.Sprintf(" # For rpm: sudo rpm --import %s", pubKeyPath))) + fmt.Println() + } + + // Store password in keychain if provided + if keyPassword != "" { + err = keychain.Set(keychain.KeyPGPPassword, keyPassword) + if err != nil { + return fmt.Errorf("failed to store password in keychain: %w", err) + } + pterm.Success.Println("PGP key password stored in system keychain") + } + + // Update Taskfile (no passwords stored here) + taskfilePath := filepath.Join("build", "linux", "Taskfile.yml") + vars := map[string]string{ + "PGP_KEY": keyPath, + } + if signRole != "" && signRole != "builder" { + vars["SIGN_ROLE"] = signRole + } + + err = updateTaskfileVars(taskfilePath, vars) + if err != nil { + return err + } + + pterm.Success.Printfln("Updated %s", taskfilePath) + return nil +} + +// getMacOSSigningIdentities returns available signing identities on macOS +func getMacOSSigningIdentities() ([]string, error) { + if runtime.GOOS != "darwin" { + return nil, fmt.Errorf("not running on macOS") + } + + // Run security find-identity to get available codesigning identities + cmd := exec.Command("security", "find-identity", "-v", "-p", "codesigning") + output, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("failed to run security find-identity: %w", err) + } + + var identities []string + lines := strings.Split(string(output), "\n") + for _, line := range lines { + // Lines look like: 1) ABC123... "Developer ID Application: Company Name (TEAMID)" + // We want to extract the quoted part + if strings.Contains(line, "\"") { + start := strings.Index(line, "\"") + end := strings.LastIndex(line, "\"") + if start != -1 && end > start { + identity := line[start+1 : end] + // Filter for Developer ID certificates (most useful for distribution) + if strings.Contains(identity, "Developer ID") { + identities = append(identities, identity) + } + } + } + } + + return identities, nil +} + +// updateTaskfileVars updates the vars section of a Taskfile +func updateTaskfileVars(path string, vars map[string]string) error { + content, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read %s: %w", path, err) + } + + lines := strings.Split(string(content), "\n") + var result []string + inVars := false + varsInserted := false + remainingVars := make(map[string]string) + for k, v := range vars { + remainingVars[k] = v + } + + for i, line := range lines { + trimmed := strings.TrimSpace(line) + + // Detect vars section + if trimmed == "vars:" { + inVars = true + result = append(result, line) + continue + } + + // Detect end of vars section (next top-level key or tasks:) + if inVars && len(line) > 0 && line[0] != ' ' && line[0] != '\t' && !strings.HasPrefix(trimmed, "#") { + // Insert any remaining vars before leaving vars section + for k, v := range remainingVars { + if v != "" { + result = append(result, fmt.Sprintf(" %s: %q", k, v)) + } + } + remainingVars = make(map[string]string) + varsInserted = true + inVars = false + } + + if inVars { + // Check if this line is a var we want to update + updated := false + for k, v := range remainingVars { + commentedKey := "# " + k + ":" + uncommentedKey := k + ":" + + if strings.Contains(trimmed, commentedKey) || strings.HasPrefix(trimmed, uncommentedKey) { + if v != "" { + // Uncomment and set value + result = append(result, fmt.Sprintf(" %s: %q", k, v)) + } else { + // Keep as comment + result = append(result, line) + } + delete(remainingVars, k) + updated = true + break + } + } + if !updated { + result = append(result, line) + } + } else { + result = append(result, line) + } + + // If we're at the end and haven't inserted vars yet, we need to add vars section + if i == len(lines)-1 && !varsInserted && len(remainingVars) > 0 { + // Find where to insert (after includes, before tasks) + // For simplicity, just append warning + pterm.Warning.Println("Could not find vars section in Taskfile, please add manually") + } + } + + return os.WriteFile(path, []byte(strings.Join(result, "\n")), 0644) +} + +// generatePGPKeyForSetup generates a PGP key pair for signing packages +func generatePGPKeyForSetup(name, email, password, privatePath, publicPath string) error { + // Create a new entity (key pair) + config := &packet.Config{ + DefaultHash: 0, // Use default + DefaultCipher: 0, // Use default + DefaultCompressionAlgo: 0, // Use default + } + + entity, err := openpgp.NewEntity(name, "", email, config) + if err != nil { + return fmt.Errorf("failed to create PGP entity: %w", err) + } + + // Encrypt the private key if password is provided + if password != "" { + err = entity.PrivateKey.Encrypt([]byte(password)) + if err != nil { + return fmt.Errorf("failed to encrypt private key: %w", err) + } + // Also encrypt subkeys + for _, subkey := range entity.Subkeys { + if subkey.PrivateKey != nil { + err = subkey.PrivateKey.Encrypt([]byte(password)) + if err != nil { + return fmt.Errorf("failed to encrypt subkey: %w", err) + } + } + } + } + + // Write private key + privateFile, err := os.Create(privatePath) + if err != nil { + return fmt.Errorf("failed to create private key file: %w", err) + } + defer privateFile.Close() + + privateArmor, err := armor.Encode(privateFile, openpgp.PrivateKeyType, nil) + if err != nil { + return fmt.Errorf("failed to create armor encoder: %w", err) + } + + err = entity.SerializePrivate(privateArmor, config) + if err != nil { + return fmt.Errorf("failed to serialize private key: %w", err) + } + privateArmor.Close() + + // Write public key + publicFile, err := os.Create(publicPath) + if err != nil { + return fmt.Errorf("failed to create public key file: %w", err) + } + defer publicFile.Close() + + publicArmor, err := armor.Encode(publicFile, openpgp.PublicKeyType, nil) + if err != nil { + return fmt.Errorf("failed to create armor encoder: %w", err) + } + + err = entity.Serialize(publicArmor) + if err != nil { + return fmt.Errorf("failed to serialize public key: %w", err) + } + publicArmor.Close() + + return nil +} diff --git a/v3/internal/commands/syso.go b/v3/internal/commands/syso.go new file mode 100644 index 000000000..03f41c80d --- /dev/null +++ b/v3/internal/commands/syso.go @@ -0,0 +1,115 @@ +package commands + +import ( + "fmt" + "github.com/pkg/errors" + "os" + "runtime" + + "github.com/tc-hib/winres" + "github.com/tc-hib/winres/version" +) + +type SysoOptions struct { + 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) (err error) { + + DisableFooter = true + + 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 func() { + err2 := iconFile.Close() + if err == nil && err2 != nil { + err = errors.Wrap(err, "error closing icon file: "+err2.Error()) + } + }() + 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 != "" { + var infoData []byte + 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" + } + var outputFile *os.File + outputFile, err = os.Create(targetFile) + if err != nil { + return err + } + defer func() { + err = outputFile.Close() + }() + + architecture := map[string]winres.Arch{ + "amd64": winres.ArchAMD64, + "arm64": winres.ArchARM64, + "386": winres.ArchI386, + } + targetArch, supported := architecture[options.Arch] + if !supported { + return fmt.Errorf("arch '%s' not supported", options.Arch) + } + + err = rs.WriteObject(outputFile, targetArch) + if err != nil { + return err + } + return nil +} diff --git a/v3/internal/commands/syso_test.go b/v3/internal/commands/syso_test.go new file mode 100644 index 000000000..a7f94c553 --- /dev/null +++ b/v3/internal/commands/syso_test.go @@ -0,0 +1,139 @@ +package commands + +import ( + "path/filepath" + "runtime" + "testing" +) + +func TestGenerateSyso(t *testing.T) { + tests := []struct { + name string + setup func() *SysoOptions + wantErr bool + test func() error + }{ + { + 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 tt.test != nil { + if err := tt.test(); err != nil { + t.Errorf("GenerateSyso() test error = %v", err) + } + } + }) + } +} diff --git a/v3/internal/commands/task.go b/v3/internal/commands/task.go new file mode 100644 index 000000000..98f2e6dca --- /dev/null +++ b/v3/internal/commands/task.go @@ -0,0 +1,185 @@ +package commands + +import ( + "context" + "fmt" + "github.com/wailsapp/wails/v3/internal/term" + "os" + "path/filepath" + "strings" + "time" + + "github.com/wailsapp/task/v3" + "github.com/wailsapp/task/v3/taskfile/ast" +) + +// BuildSettings contains the CLI build settings +var BuildSettings = map[string]string{} + +func fatal(message string) { + term.Error(message) + os.Exit(1) +} + +type RunTaskOptions struct { + Name string `pos:"1"` + Help bool `name:"h" description:"shows Task usage"` + Init bool `name:"i" description:"creates a new Taskfile.yml"` + List bool `name:"list" description:"tasks with description of current Taskfile"` + ListAll bool `name:"list-all" description:"lists tasks with or without a description"` + ListJSON bool `name:"json" description:"formats task list as json"` + Status bool `name:"status" description:"exits with non-zero exit code if any of the given tasks is not up-to-date"` + Force bool `name:"f" description:"forces execution even when the task is up-to-date"` + Watch bool `name:"w" description:"enables watch of the given task"` + Verbose bool `name:"v" description:"enables verbose mode"` + Version bool `name:"version" description:"prints version"` + Silent bool `name:"s" description:"disables echoing"` + Parallel bool `name:"p" description:"executes tasks provided on command line in parallel"` + Dry bool `name:"dry" description:"compiles and prints tasks in the order that they would be run, without executing them"` + Summary bool `name:"summary" description:"show summary about a task"` + ExitCode bool `name:"x" description:"pass-through the exit code of the task command"` + Dir string `name:"dir" description:"sets directory of execution"` + EntryPoint string `name:"taskfile" description:"choose which Taskfile to run."` + OutputName string `name:"output" description:"sets output style: [interleaved|group|prefixed]"` + OutputGroupBegin string `name:"output-group-begin" description:"message template to print before a task's grouped output"` + OutputGroupEnd string `name:"output-group-end" description:"message template to print after a task's grouped output"` + Color bool `name:"c" description:"colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable" default:"true"` + Concurrency int `name:"C" description:"limit number tasks to run concurrently"` + Interval int64 `name:"interval" description:"interval to watch for changes"` +} + +func RunTask(options *RunTaskOptions, otherArgs []string) error { + + if options.Version { + ver := BuildSettings["mod.github.com/wailsapp/task/v3"] + fmt.Println("Task Version:", ver) + return nil + } + + if options.Init { + wd, err := os.Getwd() + if err != nil { + return err + } + return task.InitTaskfile(os.Stdout, wd) + } + + if options.Dir != "" && options.EntryPoint != "" { + return fmt.Errorf("task: You can't set both --dir and --taskfile") + } + + if options.EntryPoint != "" { + options.Dir = filepath.Dir(options.EntryPoint) + options.EntryPoint = filepath.Base(options.EntryPoint) + } + + if options.OutputName != "group" { + if options.OutputGroupBegin != "" { + return fmt.Errorf("task: You can't set --output-group-begin without --output=group") + } + if options.OutputGroupEnd != "" { + return fmt.Errorf("task: You can't set --output-group-end without --output=group") + } + } + + e := task.Executor{ + Force: options.Force, + Watch: options.Watch, + Verbose: options.Verbose, + Silent: options.Silent, + Dir: options.Dir, + Dry: options.Dry, + Entrypoint: options.EntryPoint, + Summary: options.Summary, + Parallel: options.Parallel, + Color: options.Color, + Concurrency: options.Concurrency, + Interval: time.Duration(options.Interval) * time.Second, + DisableVersionCheck: true, + + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, + } + + listOptions := task.NewListOptions(options.List, options.ListAll, options.ListJSON, false) + if err := listOptions.Validate(); err != nil { + fatal(err.Error()) + } + + if listOptions.ShouldListTasks() && options.Silent { + e.ListTaskNames(options.ListAll) + return nil + } + + if err := e.Setup(); err != nil { + fatal(err.Error()) + } + + if listOptions.ShouldListTasks() { + if foundTasks, err := e.ListTasks(listOptions); !foundTasks || err != nil { + os.Exit(1) + } + return nil + } + + // Parse task name and CLI variables from otherArgs or os.Args + var tasksAndVars []string + + // Check if we have a task name specified in options + if options.Name != "" { + // If task name is provided via options, use it and treat otherArgs as CLI variables + tasksAndVars = append([]string{options.Name}, otherArgs...) + } else if len(otherArgs) > 0 { + // Use otherArgs directly if provided + tasksAndVars = otherArgs + } else { + // Fall back to parsing os.Args for backward compatibility + var index int + var arg string + for index, arg = range os.Args[2:] { + if !strings.HasPrefix(arg, "-") { + break + } + } + + for _, taskAndVar := range os.Args[index+2:] { + if taskAndVar == "--" { + break + } + tasksAndVars = append(tasksAndVars, taskAndVar) + } + } + + // Default task + if len(tasksAndVars) == 0 { + tasksAndVars = []string{"default"} + } + + // Parse task name and CLI variables + taskName := tasksAndVars[0] + cliVars := tasksAndVars[1:] + + // Create call with CLI variables + call := &ast.Call{ + Task: taskName, + Vars: &ast.Vars{}, + } + + // Parse CLI variables (format: KEY=VALUE) + for _, v := range cliVars { + if strings.Contains(v, "=") { + parts := strings.SplitN(v, "=", 2) + if len(parts) == 2 { + call.Vars.Set(parts[0], ast.Var{ + Value: parts[1], + }) + } + } + } + + if err := e.RunTask(context.Background(), call); err != nil { + fatal(err.Error()) + } + return nil +} diff --git a/v3/internal/commands/task_integration_test.go b/v3/internal/commands/task_integration_test.go new file mode 100644 index 000000000..c13d73162 --- /dev/null +++ b/v3/internal/commands/task_integration_test.go @@ -0,0 +1,342 @@ +package commands + +import ( + "bytes" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/wailsapp/task/v3/taskfile/ast" +) + +func TestTaskParameterPassing(t *testing.T) { + // Skip if running in CI without proper environment + if os.Getenv("CI") == "true" && os.Getenv("SKIP_INTEGRATION_TESTS") == "true" { + t.Skip("Skipping integration test in CI") + } + + // Create a temporary directory for test + tmpDir, err := os.MkdirTemp("", "wails-task-test-*") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + // Create a test Taskfile + taskfileContent := `version: '3' + +tasks: + build: + cmds: + - echo "PLATFORM={{.PLATFORM | default "default-platform"}}" + - echo "CONFIG={{.CONFIG | default "default-config"}}" + silent: true + + package: + cmds: + - echo "VERSION={{.VERSION | default "1.0.0"}}" + - echo "OUTPUT={{.OUTPUT | default "output.pkg"}}" + silent: true + + test: + cmds: + - echo "ENV={{.ENV | default "test"}}" + - echo "FLAGS={{.FLAGS | default "none"}}" + silent: true +` + + taskfilePath := filepath.Join(tmpDir, "Taskfile.yml") + err = os.WriteFile(taskfilePath, []byte(taskfileContent), 0644) + require.NoError(t, err) + + // Save current directory + originalWd, err := os.Getwd() + require.NoError(t, err) + defer os.Chdir(originalWd) + + // Change to test directory + err = os.Chdir(tmpDir) + require.NoError(t, err) + + tests := []struct { + name string + options *RunTaskOptions + otherArgs []string + expectedOutput []string + }{ + { + name: "Build task with parameters", + options: &RunTaskOptions{Name: "build"}, + otherArgs: []string{"PLATFORM=linux", "CONFIG=production"}, + expectedOutput: []string{ + "PLATFORM=linux", + "CONFIG=production", + }, + }, + { + name: "Package task with parameters", + options: &RunTaskOptions{Name: "package"}, + otherArgs: []string{"VERSION=2.5.0", "OUTPUT=myapp.pkg"}, + expectedOutput: []string{ + "VERSION=2.5.0", + "OUTPUT=myapp.pkg", + }, + }, + { + name: "Task with default values", + options: &RunTaskOptions{Name: "build"}, + otherArgs: []string{}, + expectedOutput: []string{ + "PLATFORM=default-platform", + "CONFIG=default-config", + }, + }, + { + name: "Task with partial parameters", + options: &RunTaskOptions{Name: "test"}, + otherArgs: []string{"ENV=staging"}, + expectedOutput: []string{ + "ENV=staging", + "FLAGS=none", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Capture output + output := captureTaskOutput(t, tt.options, tt.otherArgs) + + // Verify expected output + for _, expected := range tt.expectedOutput { + assert.Contains(t, output, expected, "Output should contain: %s", expected) + } + }) + } +} + +func TestCLIParameterFormats(t *testing.T) { + tests := []struct { + name string + otherArgs []string + expectError bool + expectedVars map[string]string + }{ + { + name: "Standard KEY=VALUE format", + otherArgs: []string{"build", "KEY1=value1", "KEY2=value2"}, + expectedVars: map[string]string{ + "KEY1": "value1", + "KEY2": "value2", + }, + }, + { + name: "Values with equals signs", + otherArgs: []string{"build", "URL=https://example.com?key=value", "FORMULA=a=b+c"}, + expectedVars: map[string]string{ + "URL": "https://example.com?key=value", + "FORMULA": "a=b+c", + }, + }, + { + name: "Values with spaces (quoted)", + otherArgs: []string{"build", "MESSAGE=Hello World", "PATH=/usr/local/bin"}, + expectedVars: map[string]string{ + "MESSAGE": "Hello World", + "PATH": "/usr/local/bin", + }, + }, + { + name: "Mixed valid and invalid arguments", + otherArgs: []string{"build", "VALID=yes", "invalid-arg", "ANOTHER=value", "--flag"}, + expectedVars: map[string]string{ + "VALID": "yes", + "ANOTHER": "value", + }, + }, + { + name: "Empty value", + otherArgs: []string{"build", "EMPTY=", "KEY=value"}, + expectedVars: map[string]string{ + "EMPTY": "", + "KEY": "value", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + call := parseTaskCall(&RunTaskOptions{}, tt.otherArgs) + + // Verify variables + for key, expectedValue := range tt.expectedVars { + var actualValue string + found := false + if call.Vars != nil { + call.Vars.Range(func(k string, v ast.Var) error { + if k == key { + actualValue = v.Value.(string) + found = true + } + return nil + }) + } + assert.True(t, found, "Variable %s not found", key) + assert.Equal(t, expectedValue, actualValue, "Variable %s mismatch", key) + } + }) + } +} + +// Helper function to capture task output +func captureTaskOutput(t *testing.T, options *RunTaskOptions, otherArgs []string) string { + // Save original stdout and stderr + oldStdout := os.Stdout + oldStderr := os.Stderr + defer func() { + os.Stdout = oldStdout + os.Stderr = oldStderr + }() + + // Create pipe to capture output + r, w, err := os.Pipe() + require.NoError(t, err) + + os.Stdout = w + os.Stderr = w + + // Run task in a goroutine + done := make(chan bool) + var taskErr error + go func() { + // Note: This is a simplified version for testing + // In real tests, you might want to mock the Task executor + taskErr = RunTask(options, otherArgs) + w.Close() + done <- true + }() + + // Read output + var buf bytes.Buffer + _, err = buf.ReadFrom(r) + require.NoError(t, err) + + // Wait for task to complete + <-done + + // Check for errors (might be expected in some tests) + if taskErr != nil && !strings.Contains(taskErr.Error(), "expected") { + t.Logf("Task error (might be expected): %v", taskErr) + } + + return buf.String() +} + +func TestBackwardCompatibility(t *testing.T) { + // Test that the old way of calling tasks still works + tests := []struct { + name string + osArgs []string + expectedTask string + expectedVars map[string]string + }{ + { + name: "Legacy os.Args parsing", + osArgs: []string{"wails3", "task", "build", "PLATFORM=windows"}, + expectedTask: "build", + expectedVars: map[string]string{ + "PLATFORM": "windows", + }, + }, + { + name: "Legacy with flags before task", + osArgs: []string{"wails3", "task", "--verbose", "test", "ENV=prod"}, + expectedTask: "test", + expectedVars: map[string]string{ + "ENV": "prod", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Save original os.Args + originalArgs := os.Args + defer func() { os.Args = originalArgs }() + + os.Args = tt.osArgs + + // Parse using the backward compatibility path + call := parseTaskCall(&RunTaskOptions{}, []string{}) + + assert.Equal(t, tt.expectedTask, call.Task) + + for key, expectedValue := range tt.expectedVars { + var actualValue string + if call.Vars != nil { + call.Vars.Range(func(k string, v ast.Var) error { + if k == key { + actualValue = v.Value.(string) + } + return nil + }) + } + assert.Equal(t, expectedValue, actualValue, "Variable %s mismatch", key) + } + }) + } +} + +func TestMkdirWithSpacesInPath(t *testing.T) { + if runtime.GOOS != "darwin" { + t.Skip("Skipping: macOS app bundle test only applies to darwin") + } + if os.Getenv("CI") == "true" && os.Getenv("SKIP_INTEGRATION_TESTS") == "true" { + t.Skip("Skipping integration test in CI") + } + + tmpDir, err := os.MkdirTemp("", "wails task test with spaces-*") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + taskfileContent := `version: '3' + +vars: + BIN_DIR: "` + tmpDir + `/bin" + APP_NAME: "My App" + +tasks: + create-bundle: + cmds: + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS" + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources" +` + + taskfilePath := filepath.Join(tmpDir, "Taskfile.yml") + err = os.WriteFile(taskfilePath, []byte(taskfileContent), 0644) + require.NoError(t, err) + + originalWd, err := os.Getwd() + require.NoError(t, err) + defer os.Chdir(originalWd) + + err = os.Chdir(tmpDir) + require.NoError(t, err) + + err = RunTask(&RunTaskOptions{Name: "create-bundle"}, []string{}) + require.NoError(t, err) + + appContentsDir := filepath.Join(tmpDir, "bin", "My App.app", "Contents") + + macOSDir := filepath.Join(appContentsDir, "MacOS") + info, err := os.Stat(macOSDir) + require.NoError(t, err, "MacOS directory should exist") + assert.True(t, info.IsDir(), "MacOS should be a directory") + + resourcesDir := filepath.Join(appContentsDir, "Resources") + info, err = os.Stat(resourcesDir) + require.NoError(t, err, "Resources directory should exist") + assert.True(t, info.IsDir(), "Resources should be a directory") +} diff --git a/v3/internal/commands/task_test.go b/v3/internal/commands/task_test.go new file mode 100644 index 000000000..dadee8647 --- /dev/null +++ b/v3/internal/commands/task_test.go @@ -0,0 +1,188 @@ +package commands + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/wailsapp/task/v3/taskfile/ast" +) + +func TestParseTaskAndVars(t *testing.T) { + tests := []struct { + name string + options *RunTaskOptions + otherArgs []string + osArgs []string + expectedTask string + expectedVars map[string]string + }{ + { + name: "Task name in options with CLI variables", + options: &RunTaskOptions{Name: "build"}, + otherArgs: []string{"PLATFORM=linux", "CONFIG=production"}, + expectedTask: "build", + expectedVars: map[string]string{ + "PLATFORM": "linux", + "CONFIG": "production", + }, + }, + { + name: "Task name and variables in otherArgs", + options: &RunTaskOptions{}, + otherArgs: []string{"test", "ENV=staging", "DEBUG=true"}, + expectedTask: "test", + expectedVars: map[string]string{ + "ENV": "staging", + "DEBUG": "true", + }, + }, + { + name: "Only task name, no variables", + options: &RunTaskOptions{}, + otherArgs: []string{"deploy"}, + expectedTask: "deploy", + expectedVars: map[string]string{}, + }, + { + name: "Default task when no args provided", + options: &RunTaskOptions{}, + otherArgs: []string{}, + osArgs: []string{"wails3", "task"}, // Set explicit os.Args to avoid test framework interference + expectedTask: "default", + expectedVars: map[string]string{}, + }, + { + name: "Variables with equals signs in values", + options: &RunTaskOptions{Name: "build"}, + otherArgs: []string{"URL=https://example.com?key=value", "CONFIG=key1=val1,key2=val2"}, + expectedTask: "build", + expectedVars: map[string]string{ + "URL": "https://example.com?key=value", + "CONFIG": "key1=val1,key2=val2", + }, + }, + { + name: "Skip non-variable arguments", + options: &RunTaskOptions{Name: "build"}, + otherArgs: []string{"PLATFORM=linux", "some-arg", "CONFIG=debug", "--flag"}, + expectedTask: "build", + expectedVars: map[string]string{ + "PLATFORM": "linux", + "CONFIG": "debug", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Save original os.Args + originalArgs := os.Args + defer func() { os.Args = originalArgs }() + + if tt.osArgs != nil { + os.Args = tt.osArgs + } + + // Parse the task and variables + call := parseTaskCall(tt.options, tt.otherArgs) + + // Verify task name + assert.Equal(t, tt.expectedTask, call.Task) + + // Verify variables + if len(tt.expectedVars) > 0 { + require.NotNil(t, call.Vars) + + // Check each expected variable + for key, expectedValue := range tt.expectedVars { + var actualValue string + call.Vars.Range(func(k string, v ast.Var) error { + if k == key { + actualValue = v.Value.(string) + } + return nil + }) + assert.Equal(t, expectedValue, actualValue, "Variable %s mismatch", key) + } + } else if call.Vars != nil { + // Ensure no variables were set when none expected + count := 0 + call.Vars.Range(func(k string, v ast.Var) error { + count++ + return nil + }) + assert.Equal(t, 0, count, "Expected no variables but found %d", count) + } + }) + } +} + +// Helper function to extract the task parsing logic for testing +func parseTaskCall(options *RunTaskOptions, otherArgs []string) *ast.Call { + var tasksAndVars []string + + // Check if we have a task name specified in options + if options.Name != "" { + // If task name is provided via options, use it and treat otherArgs as CLI variables + tasksAndVars = append([]string{options.Name}, otherArgs...) + } else if len(otherArgs) > 0 { + // Use otherArgs directly if provided + tasksAndVars = otherArgs + } else { + // Fall back to parsing os.Args for backward compatibility + var index int + var arg string + for index, arg = range os.Args[2:] { + if len(arg) > 0 && arg[0] != '-' { + break + } + } + + for _, taskAndVar := range os.Args[index+2:] { + if taskAndVar == "--" { + break + } + tasksAndVars = append(tasksAndVars, taskAndVar) + } + } + + // Default task + if len(tasksAndVars) == 0 { + tasksAndVars = []string{"default"} + } + + // Parse task name and CLI variables + taskName := tasksAndVars[0] + cliVars := tasksAndVars[1:] + + // Create call with CLI variables + call := &ast.Call{ + Task: taskName, + Vars: &ast.Vars{}, + } + + // Parse CLI variables (format: KEY=VALUE) + for _, v := range cliVars { + if idx := findEquals(v); idx != -1 { + key := v[:idx] + value := v[idx+1:] + call.Vars.Set(key, ast.Var{ + Value: value, + }) + } + } + + return call +} + +// Helper to find the first equals sign +func findEquals(s string) int { + for i, r := range s { + if r == '=' { + return i + } + } + return -1 +} \ No newline at end of file diff --git a/v3/internal/commands/task_wrapper.go b/v3/internal/commands/task_wrapper.go new file mode 100644 index 000000000..908c0e53c --- /dev/null +++ b/v3/internal/commands/task_wrapper.go @@ -0,0 +1,73 @@ +package commands + +import ( + "os" + "runtime" + "strings" + + "github.com/wailsapp/wails/v3/internal/flags" +) + +// runTaskFunc is a variable to allow mocking in tests +var runTaskFunc = RunTask + +// validPlatforms for GOOS +var validPlatforms = map[string]bool{ + "windows": true, + "darwin": true, + "linux": true, +} + +func Build(_ *flags.Build, otherArgs []string) error { + return wrapTask("build", otherArgs) +} + +func Package(_ *flags.Package, otherArgs []string) error { + return wrapTask("package", otherArgs) +} + +func SignWrapper(_ *flags.SignWrapper, otherArgs []string) error { + return wrapTask("sign", otherArgs) +} + +func wrapTask(action string, otherArgs []string) error { + // Check environment first, then allow args to override + goos := os.Getenv("GOOS") + if goos == "" { + goos = runtime.GOOS + } + goarch := os.Getenv("GOARCH") + if goarch == "" { + goarch = runtime.GOARCH + } + + var remainingArgs []string + + // Args override environment + for _, arg := range otherArgs { + switch { + case strings.HasPrefix(arg, "GOOS="): + goos = strings.TrimPrefix(arg, "GOOS=") + case strings.HasPrefix(arg, "GOARCH="): + goarch = strings.TrimPrefix(arg, "GOARCH=") + default: + remainingArgs = append(remainingArgs, arg) + } + } + + // Determine task name based on GOOS + taskName := action + if validPlatforms[goos] { + taskName = goos + ":" + action + } + + // Pass ARCH to task (always set, defaults to current architecture) + remainingArgs = append(remainingArgs, "ARCH="+goarch) + + // Rebuild os.Args to include the command and all additional arguments + newArgs := []string{"wails3", "task", taskName} + newArgs = append(newArgs, remainingArgs...) + os.Args = newArgs + // Pass the task name via options and remainingArgs as CLI variables + return runTaskFunc(&RunTaskOptions{Name: taskName}, remainingArgs) +} diff --git a/v3/internal/commands/task_wrapper_test.go b/v3/internal/commands/task_wrapper_test.go new file mode 100644 index 000000000..5a7d8c3a0 --- /dev/null +++ b/v3/internal/commands/task_wrapper_test.go @@ -0,0 +1,296 @@ +package commands + +import ( + "os" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/wailsapp/wails/v3/internal/flags" +) + +func TestWrapTask(t *testing.T) { + // Get current platform info for expected values + currentOS := runtime.GOOS + currentArch := runtime.GOARCH + + tests := []struct { + name string + command string + otherArgs []string + envGOOS string + envGOARCH string + expectedTaskName string + expectedArgs []string + expectedOsArgs []string + }{ + { + name: "Build with parameters uses current platform", + command: "build", + otherArgs: []string{"CONFIG=debug"}, + expectedTaskName: currentOS + ":build", + expectedArgs: []string{"CONFIG=debug", "ARCH=" + currentArch}, + expectedOsArgs: []string{"wails3", "task", currentOS + ":build", "CONFIG=debug", "ARCH=" + currentArch}, + }, + { + name: "Package with parameters uses current platform", + command: "package", + otherArgs: []string{"VERSION=1.0.0", "OUTPUT=app.pkg"}, + expectedTaskName: currentOS + ":package", + expectedArgs: []string{"VERSION=1.0.0", "OUTPUT=app.pkg", "ARCH=" + currentArch}, + expectedOsArgs: []string{"wails3", "task", currentOS + ":package", "VERSION=1.0.0", "OUTPUT=app.pkg", "ARCH=" + currentArch}, + }, + { + name: "Build without parameters", + command: "build", + otherArgs: []string{}, + expectedTaskName: currentOS + ":build", + expectedArgs: []string{"ARCH=" + currentArch}, + expectedOsArgs: []string{"wails3", "task", currentOS + ":build", "ARCH=" + currentArch}, + }, + { + name: "GOOS override changes task prefix", + command: "build", + otherArgs: []string{"GOOS=darwin", "CONFIG=release"}, + expectedTaskName: "darwin:build", + expectedArgs: []string{"CONFIG=release", "ARCH=" + currentArch}, + expectedOsArgs: []string{"wails3", "task", "darwin:build", "CONFIG=release", "ARCH=" + currentArch}, + }, + { + name: "GOARCH override changes ARCH arg", + command: "build", + otherArgs: []string{"GOARCH=arm64"}, + expectedTaskName: currentOS + ":build", + expectedArgs: []string{"ARCH=arm64"}, + expectedOsArgs: []string{"wails3", "task", currentOS + ":build", "ARCH=arm64"}, + }, + { + name: "Both GOOS and GOARCH override", + command: "package", + otherArgs: []string{"GOOS=windows", "GOARCH=386", "VERSION=2.0"}, + expectedTaskName: "windows:package", + expectedArgs: []string{"VERSION=2.0", "ARCH=386"}, + expectedOsArgs: []string{"wails3", "task", "windows:package", "VERSION=2.0", "ARCH=386"}, + }, + { + name: "Environment GOOS is used when no arg override", + command: "build", + otherArgs: []string{"CONFIG=debug"}, + envGOOS: "darwin", + expectedTaskName: "darwin:build", + expectedArgs: []string{"CONFIG=debug", "ARCH=" + currentArch}, + expectedOsArgs: []string{"wails3", "task", "darwin:build", "CONFIG=debug", "ARCH=" + currentArch}, + }, + { + name: "Arg GOOS overrides environment GOOS", + command: "build", + otherArgs: []string{"GOOS=linux"}, + envGOOS: "darwin", + expectedTaskName: "linux:build", + expectedArgs: []string{"ARCH=" + currentArch}, + expectedOsArgs: []string{"wails3", "task", "linux:build", "ARCH=" + currentArch}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Save and restore os.Args + originalArgs := os.Args + defer func() { os.Args = originalArgs }() + + // Save and restore environment variables + originalGOOS := os.Getenv("GOOS") + originalGOARCH := os.Getenv("GOARCH") + defer func() { + if originalGOOS == "" { + os.Unsetenv("GOOS") + } else { + os.Setenv("GOOS", originalGOOS) + } + if originalGOARCH == "" { + os.Unsetenv("GOARCH") + } else { + os.Setenv("GOARCH", originalGOARCH) + } + }() + + // Set test environment + if tt.envGOOS != "" { + os.Setenv("GOOS", tt.envGOOS) + } else { + os.Unsetenv("GOOS") + } + if tt.envGOARCH != "" { + os.Setenv("GOARCH", tt.envGOARCH) + } else { + os.Unsetenv("GOARCH") + } + + // Mock RunTask to capture the arguments + originalRunTask := runTaskFunc + var capturedOptions *RunTaskOptions + var capturedOtherArgs []string + runTaskFunc = func(options *RunTaskOptions, otherArgs []string) error { + capturedOptions = options + capturedOtherArgs = otherArgs + return nil + } + defer func() { runTaskFunc = originalRunTask }() + + // Execute wrapTask + err := wrapTask(tt.command, tt.otherArgs) + assert.NoError(t, err) + + // Verify os.Args was set correctly + assert.Equal(t, tt.expectedOsArgs, os.Args) + + // Verify RunTask was called with correct parameters + assert.Equal(t, tt.expectedTaskName, capturedOptions.Name) + assert.Equal(t, tt.expectedArgs, capturedOtherArgs) + }) + } +} + +func TestBuildCommand(t *testing.T) { + currentOS := runtime.GOOS + currentArch := runtime.GOARCH + + // Save original RunTask + originalRunTask := runTaskFunc + defer func() { runTaskFunc = originalRunTask }() + + // Mock RunTask to capture the arguments + var capturedOptions *RunTaskOptions + var capturedOtherArgs []string + runTaskFunc = func(options *RunTaskOptions, otherArgs []string) error { + capturedOptions = options + capturedOtherArgs = otherArgs + return nil + } + + // Save original os.Args and environment + originalArgs := os.Args + defer func() { os.Args = originalArgs }() + + originalGOOS := os.Getenv("GOOS") + originalGOARCH := os.Getenv("GOARCH") + defer func() { + if originalGOOS == "" { + os.Unsetenv("GOOS") + } else { + os.Setenv("GOOS", originalGOOS) + } + if originalGOARCH == "" { + os.Unsetenv("GOARCH") + } else { + os.Setenv("GOARCH", originalGOARCH) + } + }() + os.Unsetenv("GOOS") + os.Unsetenv("GOARCH") + + // Test Build command + buildFlags := &flags.Build{} + otherArgs := []string{"CONFIG=release"} + + err := Build(buildFlags, otherArgs) + assert.NoError(t, err) + assert.Equal(t, currentOS+":build", capturedOptions.Name) + assert.Equal(t, []string{"CONFIG=release", "ARCH=" + currentArch}, capturedOtherArgs) +} + +func TestPackageCommand(t *testing.T) { + currentOS := runtime.GOOS + currentArch := runtime.GOARCH + + // Save original RunTask + originalRunTask := runTaskFunc + defer func() { runTaskFunc = originalRunTask }() + + // Mock RunTask to capture the arguments + var capturedOptions *RunTaskOptions + var capturedOtherArgs []string + runTaskFunc = func(options *RunTaskOptions, otherArgs []string) error { + capturedOptions = options + capturedOtherArgs = otherArgs + return nil + } + + // Save original os.Args and environment + originalArgs := os.Args + defer func() { os.Args = originalArgs }() + + originalGOOS := os.Getenv("GOOS") + originalGOARCH := os.Getenv("GOARCH") + defer func() { + if originalGOOS == "" { + os.Unsetenv("GOOS") + } else { + os.Setenv("GOOS", originalGOOS) + } + if originalGOARCH == "" { + os.Unsetenv("GOARCH") + } else { + os.Setenv("GOARCH", originalGOARCH) + } + }() + os.Unsetenv("GOOS") + os.Unsetenv("GOARCH") + + // Test Package command + packageFlags := &flags.Package{} + otherArgs := []string{"VERSION=2.0.0", "OUTPUT=myapp.dmg"} + + err := Package(packageFlags, otherArgs) + assert.NoError(t, err) + assert.Equal(t, currentOS+":package", capturedOptions.Name) + assert.Equal(t, []string{"VERSION=2.0.0", "OUTPUT=myapp.dmg", "ARCH=" + currentArch}, capturedOtherArgs) +} + +func TestSignWrapperCommand(t *testing.T) { + currentOS := runtime.GOOS + currentArch := runtime.GOARCH + + // Save original RunTask + originalRunTask := runTaskFunc + defer func() { runTaskFunc = originalRunTask }() + + // Mock RunTask to capture the arguments + var capturedOptions *RunTaskOptions + var capturedOtherArgs []string + runTaskFunc = func(options *RunTaskOptions, otherArgs []string) error { + capturedOptions = options + capturedOtherArgs = otherArgs + return nil + } + + // Save original os.Args and environment + originalArgs := os.Args + defer func() { os.Args = originalArgs }() + + originalGOOS := os.Getenv("GOOS") + originalGOARCH := os.Getenv("GOARCH") + defer func() { + if originalGOOS == "" { + os.Unsetenv("GOOS") + } else { + os.Setenv("GOOS", originalGOOS) + } + if originalGOARCH == "" { + os.Unsetenv("GOARCH") + } else { + os.Setenv("GOARCH", originalGOARCH) + } + }() + os.Unsetenv("GOOS") + os.Unsetenv("GOARCH") + + // Test SignWrapper command + signFlags := &flags.SignWrapper{} + otherArgs := []string{"IDENTITY=Developer ID"} + + err := SignWrapper(signFlags, otherArgs) + assert.NoError(t, err) + assert.Equal(t, currentOS+":sign", capturedOptions.Name) + assert.Equal(t, []string{"IDENTITY=Developer ID", "ARCH=" + currentArch}, capturedOtherArgs) +} diff --git a/v3/internal/commands/tool_buildinfo.go b/v3/internal/commands/tool_buildinfo.go new file mode 100644 index 000000000..d8547d8ce --- /dev/null +++ b/v3/internal/commands/tool_buildinfo.go @@ -0,0 +1,18 @@ +package commands + +import ( + "fmt" + "github.com/wailsapp/wails/v3/internal/buildinfo" +) + +type BuildInfoOptions struct{} + +func BuildInfo(_ *BuildInfoOptions) error { + + info, err := buildinfo.Get() + if err != nil { + return err + } + fmt.Printf("%+v\n", info) + return nil +} diff --git a/v3/internal/commands/tool_checkport.go b/v3/internal/commands/tool_checkport.go new file mode 100644 index 000000000..f0ba75bc4 --- /dev/null +++ b/v3/internal/commands/tool_checkport.go @@ -0,0 +1,57 @@ +package commands + +import ( + "fmt" + "net" + "net/url" + "strconv" + "time" +) + +type ToolCheckPortOptions struct { + URL string `name:"u" description:"URL to check"` + Host string `name:"h" description:"Host to check" default:"localhost"` + Port int `name:"p" description:"Port to check"` +} + +func isPortOpen(ip string, port int) bool { + timeout := time.Second + conn, err := net.DialTimeout("tcp", net.JoinHostPort(ip, fmt.Sprintf("%d", port)), timeout) + if err != nil { + return false + } + + // If there's no error, close the connection and return true. + if conn != nil { + _ = conn.Close() + } + return true +} + +func ToolCheckPort(options *ToolCheckPortOptions) error { + DisableFooter = true + + if options.URL != "" { + // Parse URL + u, err := url.Parse(options.URL) + if err != nil { + return err + } + options.Host = u.Hostname() + options.Port, err = strconv.Atoi(u.Port()) + if err != nil { + return err + } + } else { + if options.Port == 0 { + return fmt.Errorf("please use the -p flag to specify a port") + } + if !isPortOpen(options.Host, options.Port) { + return fmt.Errorf("port %d is not open on %s", options.Port, options.Host) + } + } + if !isPortOpen(options.Host, options.Port) { + return fmt.Errorf("port %d is not open on %s", options.Port, options.Host) + } + return nil +} diff --git a/v3/internal/commands/tool_cp.go b/v3/internal/commands/tool_cp.go new file mode 100644 index 000000000..5e0a1526a --- /dev/null +++ b/v3/internal/commands/tool_cp.go @@ -0,0 +1,40 @@ +package commands + +import ( + "fmt" + "os" + "path/filepath" +) + +type CpOptions struct{} + +func Cp(_ *CpOptions) error { + DisableFooter = true + + // extract the source and destination from os.Args + if len(os.Args) != 5 { + return fmt.Errorf("cp requires a source and destination") + } + // Extract source + source := os.Args[3] + for _, destination := range os.Args[4:] { + src, err := filepath.Abs(source) + if err != nil { + return err + } + dst, err := filepath.Abs(destination) + if err != nil { + return err + } + input, err := os.ReadFile(src) + if err != nil { + return err + } + + err = os.WriteFile(dst, input, 0644) + if err != nil { + return fmt.Errorf("error creating %s: %s", dst, err.Error()) + } + } + return nil +} diff --git a/v3/internal/commands/tool_lipo.go b/v3/internal/commands/tool_lipo.go new file mode 100644 index 000000000..a33ebd777 --- /dev/null +++ b/v3/internal/commands/tool_lipo.go @@ -0,0 +1,31 @@ +package commands + +import ( + "fmt" + + "github.com/konoui/lipo/pkg/lipo" + "github.com/wailsapp/wails/v3/internal/flags" +) + +func ToolLipo(options *flags.Lipo) error { + DisableFooter = true + + if len(options.Inputs) < 2 { + return fmt.Errorf("lipo requires at least 2 input files") + } + + if options.Output == "" { + return fmt.Errorf("output file is required (-output)") + } + + l := lipo.New( + lipo.WithInputs(options.Inputs...), + lipo.WithOutput(options.Output), + ) + + if err := l.Create(); err != nil { + return fmt.Errorf("failed to create universal binary: %w", err) + } + + return nil +} diff --git a/v3/internal/commands/tool_package.go b/v3/internal/commands/tool_package.go new file mode 100644 index 000000000..a7d8cfd52 --- /dev/null +++ b/v3/internal/commands/tool_package.go @@ -0,0 +1,116 @@ +package commands + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + // "github.com/wailsapp/wails/v3/internal/commands/dmg" // TODO: Missing package + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/packager" +) + +// ToolPackage generates a package in the specified format +func ToolPackage(options *flags.ToolPackage) error { + DisableFooter = true + + // Check if we're creating a DMG + isDMG := strings.ToLower(options.Format) == "dmg" || options.CreateDMG + + // Config file is required for Linux packages but optional for DMG + if options.ConfigPath == "" && !isDMG { + return fmt.Errorf("please provide a config file using the -config flag") + } + + if options.ExecutableName == "" { + return fmt.Errorf("please provide an executable name using the -name flag") + } + + // Handle DMG creation for macOS + if isDMG { + if runtime.GOOS != "darwin" { + return fmt.Errorf("DMG creation is only supported on macOS") + } + + // For DMG, we expect the .app bundle to already exist + appPath := filepath.Join(options.Out, fmt.Sprintf("%s.app", options.ExecutableName)) + if _, err := os.Stat(appPath); os.IsNotExist(err) { + return fmt.Errorf("application bundle not found: %s", appPath) + } + + // Create output path for DMG + dmgPath := filepath.Join(options.Out, fmt.Sprintf("%s.dmg", options.ExecutableName)) + + // DMG creation temporarily disabled - missing dmg package + _ = dmgPath // avoid unused variable warning + return fmt.Errorf("DMG creation is temporarily disabled due to missing dmg package") + + // // Create DMG creator + // dmgCreator, err := dmg.New(appPath, dmgPath, options.ExecutableName) + // if err != nil { + // return fmt.Errorf("error creating DMG: %w", err) + // } + + // // Set background image if provided + // if options.BackgroundImage != "" { + // if err := dmgCreator.SetBackgroundImage(options.BackgroundImage); err != nil { + // return fmt.Errorf("error setting background image: %w", err) + // } + // } + + // // Set default icon positions + // dmgCreator.AddIconPosition(filepath.Base(appPath), 150, 175) + // dmgCreator.AddIconPosition("Applications", 450, 175) + + // // Create the DMG + // if err := dmgCreator.Create(); err != nil { + // return fmt.Errorf("error creating DMG: %w", err) + // } + + // fmt.Printf("DMG created successfully: %s\n", dmgPath) + // return nil + } + + // For Linux packages, continue with existing logic + var pkgType packager.PackageType + switch strings.ToLower(options.Format) { + case "deb": + pkgType = packager.DEB + case "rpm": + pkgType = packager.RPM + case "archlinux": + pkgType = packager.ARCH + default: + return fmt.Errorf("unsupported package format '%s'. Supported formats: deb, rpm, archlinux, dmg", options.Format) + } + + // Get absolute path of config file + configPath, err := filepath.Abs(options.ConfigPath) + if err != nil { + return fmt.Errorf("error getting absolute path of config file: %w", err) + } + + // Check if config file exists + if info, err := os.Stat(configPath); err != nil { + return fmt.Errorf("config file not found: %s", configPath) + } else if info.Mode().Perm()&0444 == 0 { + return fmt.Errorf("config file is not readable: %s", configPath) + } + + // Generate output filename based on format + if options.Format == "archlinux" { + // Arch linux packages are not .archlinux files, they are .pkg.tar.zst + options.Format = "pkg.tar.zst" + } + outputFile := filepath.Join(options.Out, fmt.Sprintf("%s.%s", options.ExecutableName, options.Format)) + + // Create the package + err = packager.CreatePackageFromConfig(pkgType, configPath, outputFile) + if err != nil { + return fmt.Errorf("error creating package: %w", err) + } + + return nil +} diff --git a/v3/internal/commands/tool_package_test.go b/v3/internal/commands/tool_package_test.go new file mode 100644 index 000000000..21d52962d --- /dev/null +++ b/v3/internal/commands/tool_package_test.go @@ -0,0 +1,143 @@ +package commands + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/wailsapp/wails/v3/internal/flags" +) + +func TestToolPackage(t *testing.T) { + tests := []struct { + name string + setup func() (*flags.ToolPackage, func()) + wantErr bool + errMsg string + }{ + { + name: "should fail with invalid format", + setup: func() (*flags.ToolPackage, func()) { + return &flags.ToolPackage{ + Format: "invalid", + ConfigPath: "config.yaml", + ExecutableName: "myapp", + }, func() {} + }, + wantErr: true, + errMsg: "unsupported package format", + }, + { + name: "should fail with missing config file", + setup: func() (*flags.ToolPackage, func()) { + return &flags.ToolPackage{ + Format: "deb", + ConfigPath: "nonexistent.yaml", + ExecutableName: "myapp", + }, func() {} + }, + wantErr: true, + errMsg: "config file not found", + }, + { + name: "should handle case-insensitive format (DEB)", + setup: func() (*flags.ToolPackage, func()) { + // Create a temporary config file + dir := t.TempDir() + configPath := filepath.Join(dir, "config.yaml") + err := os.WriteFile(configPath, []byte("name: test"), 0644) + if err != nil { + t.Fatal(err) + } + + // Create bin directory + err = os.MkdirAll(filepath.Join(dir, "bin"), 0755) + if err != nil { + t.Fatal(err) + } + + return &flags.ToolPackage{ + Format: "DEB", + ConfigPath: configPath, + ExecutableName: "myapp", + }, func() { + os.RemoveAll(filepath.Join(dir, "bin")) + } + }, + wantErr: false, + }, + { + name: "should handle case-insensitive format (RPM)", + setup: func() (*flags.ToolPackage, func()) { + // Create a temporary config file + dir := t.TempDir() + configPath := filepath.Join(dir, "config.yaml") + err := os.WriteFile(configPath, []byte("name: test"), 0644) + if err != nil { + t.Fatal(err) + } + + // Create bin directory + err = os.MkdirAll(filepath.Join(dir, "bin"), 0755) + if err != nil { + t.Fatal(err) + } + + return &flags.ToolPackage{ + Format: "RPM", + ConfigPath: configPath, + ExecutableName: "myapp", + }, func() { + os.RemoveAll(filepath.Join(dir, "bin")) + } + }, + wantErr: false, + }, + { + name: "should handle case-insensitive format (ARCHLINUX)", + setup: func() (*flags.ToolPackage, func()) { + // Create a temporary config file + dir := t.TempDir() + configPath := filepath.Join(dir, "config.yaml") + err := os.WriteFile(configPath, []byte("name: test"), 0644) + if err != nil { + t.Fatal(err) + } + + // Create bin directory + err = os.MkdirAll(filepath.Join(dir, "bin"), 0755) + if err != nil { + t.Fatal(err) + } + + return &flags.ToolPackage{ + Format: "ARCHLINUX", + ConfigPath: configPath, + ExecutableName: "myapp", + }, func() { + os.RemoveAll(filepath.Join(dir, "bin")) + } + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + options, cleanup := tt.setup() + defer cleanup() + + err := ToolPackage(options) + + if (err != nil) != tt.wantErr { + t.Errorf("ToolPackage() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if tt.wantErr && !strings.Contains(err.Error(), tt.errMsg) { + t.Errorf("ToolPackage() error = %v, want error containing %v", err, tt.errMsg) + } + }) + } +} diff --git a/v3/internal/commands/tool_version.go b/v3/internal/commands/tool_version.go new file mode 100644 index 000000000..fe0deb5ec --- /dev/null +++ b/v3/internal/commands/tool_version.go @@ -0,0 +1,123 @@ +package commands + +import ( + "fmt" + "strconv" + "strings" + + "github.com/wailsapp/wails/v3/internal/github" +) + +type ToolVersionOptions struct { + Version string `name:"v" description:"Current version to bump"` + Major bool `name:"major" description:"Bump major version"` + Minor bool `name:"minor" description:"Bump minor version"` + Patch bool `name:"patch" description:"Bump patch version"` + Prerelease bool `name:"prerelease" description:"Bump prerelease version (e.g., alpha.5 to alpha.6)"` +} + +// bumpPrerelease increments the numeric part of a prerelease string +// For example, "alpha.5" becomes "alpha.6" +func bumpPrerelease(prerelease string) string { + // If prerelease is empty, return it as is + if prerelease == "" { + return prerelease + } + + // Split the prerelease string by dots + parts := strings.Split(prerelease, ".") + + // If there's only one part (e.g., "alpha"), return it as is + if len(parts) == 1 { + return prerelease + } + + // Try to parse the last part as a number + lastPart := parts[len(parts)-1] + num, err := strconv.Atoi(lastPart) + if err != nil { + // If the last part is not a number, return the prerelease as is + return prerelease + } + + // Increment the number + num++ + + // Replace the last part with the incremented number + parts[len(parts)-1] = strconv.Itoa(num) + + // Join the parts back together + return strings.Join(parts, ".") +} + +// ToolVersion bumps a semantic version based on the provided flags +func ToolVersion(options *ToolVersionOptions) error { + DisableFooter = true + + if options.Version == "" { + return fmt.Errorf("please provide a version using the -v flag") + } + + // Check if the version has a "v" prefix + hasVPrefix := false + versionStr := options.Version + if len(versionStr) > 0 && versionStr[0] == 'v' { + hasVPrefix = true + versionStr = versionStr[1:] + } + + // Parse the current version + semver, err := github.NewSemanticVersion(versionStr) + if err != nil { + return fmt.Errorf("invalid version format: %v", err) + } + + // Get the current version components + major := semver.Version.Major() + minor := semver.Version.Minor() + patch := semver.Version.Patch() + prerelease := semver.Version.Prerelease() + metadata := semver.Version.Metadata() + + // Check if at least one flag is specified + if !options.Major && !options.Minor && !options.Patch && !options.Prerelease { + return fmt.Errorf("please specify one of -major, -minor, -patch, or -prerelease") + } + + // Bump the version based on the flags (major takes precedence over minor, which takes precedence over patch) + if options.Major { + major++ + minor = 0 + patch = 0 + } else if options.Minor { + minor++ + patch = 0 + } else if options.Patch { + patch++ + } else if options.Prerelease { + // If only prerelease flag is specified, bump the prerelease version + if prerelease == "" { + return fmt.Errorf("cannot bump prerelease version: no prerelease part in the version") + } + prerelease = bumpPrerelease(prerelease) + } + + // Format the new version + newVersion := fmt.Sprintf("%d.%d.%d", major, minor, patch) + if prerelease != "" { + newVersion += "-" + prerelease + } + if metadata != "" { + newVersion += "+" + metadata + } + + // Add the "v" prefix back if it was present in the input + if hasVPrefix { + newVersion = "v" + newVersion + } + + // Print the new version + fmt.Println(newVersion) + + return nil +} diff --git a/v3/internal/commands/tool_version_test.go b/v3/internal/commands/tool_version_test.go new file mode 100644 index 000000000..1da58585f --- /dev/null +++ b/v3/internal/commands/tool_version_test.go @@ -0,0 +1,231 @@ +package commands + +import ( + "bytes" + "io" + "os" + "strings" + "testing" +) + +func TestToolVersion(t *testing.T) { + // Create a table of test cases + testCases := []struct { + name string + options ToolVersionOptions + expectedOutput string + expectedError bool + }{ + { + name: "Bump major version", + options: ToolVersionOptions{ + Version: "1.2.3", + Major: true, + }, + expectedOutput: "2.0.0", + expectedError: false, + }, + { + name: "Bump minor version", + options: ToolVersionOptions{ + Version: "1.2.3", + Minor: true, + }, + expectedOutput: "1.3.0", + expectedError: false, + }, + { + name: "Bump patch version", + options: ToolVersionOptions{ + Version: "1.2.3", + Patch: true, + }, + expectedOutput: "1.2.4", + expectedError: false, + }, + { + name: "Bump major version with v prefix", + options: ToolVersionOptions{ + Version: "v1.2.3", + Major: true, + }, + expectedOutput: "v2.0.0", + expectedError: false, + }, + { + name: "Bump minor version with v prefix", + options: ToolVersionOptions{ + Version: "v1.2.3", + Minor: true, + }, + expectedOutput: "v1.3.0", + expectedError: false, + }, + { + name: "Bump patch version with v prefix", + options: ToolVersionOptions{ + Version: "v1.2.3", + Patch: true, + }, + expectedOutput: "v1.2.4", + expectedError: false, + }, + { + name: "Bump version with prerelease", + options: ToolVersionOptions{ + Version: "1.2.3-alpha", + Patch: true, + }, + expectedOutput: "1.2.4-alpha", + expectedError: false, + }, + { + name: "Bump version with metadata", + options: ToolVersionOptions{ + Version: "1.2.3+build123", + Patch: true, + }, + expectedOutput: "1.2.4+build123", + expectedError: false, + }, + { + name: "Bump version with prerelease and metadata", + options: ToolVersionOptions{ + Version: "1.2.3-alpha+build123", + Patch: true, + }, + expectedOutput: "1.2.4-alpha+build123", + expectedError: false, + }, + { + name: "Bump version with v prefix, prerelease and metadata", + options: ToolVersionOptions{ + Version: "v1.2.3-alpha+build123", + Patch: true, + }, + expectedOutput: "v1.2.4-alpha+build123", + expectedError: false, + }, + { + name: "No version provided", + options: ToolVersionOptions{ + Major: true, + }, + expectedOutput: "", + expectedError: true, + }, + { + name: "No bump flag provided", + options: ToolVersionOptions{ + Version: "1.2.3", + }, + expectedOutput: "", + expectedError: true, + }, + { + name: "Invalid version format", + options: ToolVersionOptions{ + Version: "invalid", + Major: true, + }, + expectedOutput: "", + expectedError: true, + }, + { + name: "Bump prerelease version with numeric component", + options: ToolVersionOptions{ + Version: "1.2.3-alpha.5", + Prerelease: true, + }, + expectedOutput: "1.2.3-alpha.6", + expectedError: false, + }, + { + name: "Bump prerelease version with v prefix", + options: ToolVersionOptions{ + Version: "v1.2.3-alpha.5", + Prerelease: true, + }, + expectedOutput: "v1.2.3-alpha.6", + expectedError: false, + }, + { + name: "Bump prerelease version with metadata", + options: ToolVersionOptions{ + Version: "1.2.3-alpha.5+build123", + Prerelease: true, + }, + expectedOutput: "1.2.3-alpha.6+build123", + expectedError: false, + }, + { + name: "Bump prerelease version without numeric component", + options: ToolVersionOptions{ + Version: "1.2.3-alpha", + Prerelease: true, + }, + expectedOutput: "1.2.3-alpha", + expectedError: false, + }, + { + name: "Bump prerelease version when no prerelease part exists", + options: ToolVersionOptions{ + Version: "1.2.3", + Prerelease: true, + }, + expectedOutput: "", + expectedError: true, + }, + { + name: "Bump prerelease version for issue example v3.0.0-alpha.5", + options: ToolVersionOptions{ + Version: "v3.0.0-alpha.5", + Prerelease: true, + }, + expectedOutput: "v3.0.0-alpha.6", + expectedError: false, + }, + } + + // Run each test case + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Capture stdout + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + // Call the function + err := ToolVersion(&tc.options) + + // Restore stdout + err2 := w.Close() + if err2 != nil { + t.Fail() + } + os.Stdout = oldStdout + + // Read captured output + var buf bytes.Buffer + _, err2 = io.Copy(&buf, r) + if err2 != nil { + t.Fail() + } + + output := strings.TrimSpace(buf.String()) + + // Check error + if tc.expectedError && err == nil { + t.Errorf("Expected error but got none") + } + if !tc.expectedError && err != nil { + t.Errorf("Expected no error but got: %v", err) + } + + // Check output + if !tc.expectedError && output != tc.expectedOutput { + t.Errorf("Expected output '%s' but got '%s'", tc.expectedOutput, output) + } + }) + } +} diff --git a/v3/internal/commands/updatable_build_assets/darwin/Info.dev.plist.tmpl b/v3/internal/commands/updatable_build_assets/darwin/Info.dev.plist.tmpl new file mode 100644 index 000000000..151629739 --- /dev/null +++ b/v3/internal/commands/updatable_build_assets/darwin/Info.dev.plist.tmpl @@ -0,0 +1,74 @@ + + + + CFBundlePackageType + APPL + CFBundleName + {{.ProductName}} + CFBundleExecutable + {{.BinaryName}} + CFBundleIdentifier + {{.ProductIdentifier}} + CFBundleVersion + {{.ProductVersion}} + CFBundleGetInfoString + {{.ProductComments}} + CFBundleShortVersionString + {{.ProductVersion}} + CFBundleIconFile + icons + {{- if .CFBundleIconName}} + CFBundleIconName + {{.CFBundleIconName}} + {{- end}} + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + {{.ProductCopyright}} + {{- if .FileAssociations}} + CFBundleDocumentTypes + + {{- range .FileAssociations}} + + CFBundleTypeExtensions + + {{.Ext}} + + CFBundleTypeName + {{.Name}} + CFBundleTypeRole + {{.Role}} + CFBundleTypeIconFile + {{.IconName}} + {{- if .MimeType}} + CFBundleTypeMimeType + {{.MimeType}} + {{- end}} + + {{- end}} + + {{- end}} + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + {{- if .Protocols}} + CFBundleURLTypes + + {{- range .Protocols}} + + CFBundleURLName + wails.com.{{.Scheme}} + CFBundleURLSchemes + + {{.Scheme}} + + + {{- end}} + + {{- end}} + + \ No newline at end of file diff --git a/v3/internal/commands/updatable_build_assets/darwin/Info.plist.tmpl b/v3/internal/commands/updatable_build_assets/darwin/Info.plist.tmpl new file mode 100644 index 000000000..5792cbf93 --- /dev/null +++ b/v3/internal/commands/updatable_build_assets/darwin/Info.plist.tmpl @@ -0,0 +1,69 @@ + + + + CFBundlePackageType + APPL + CFBundleName + {{.ProductName}} + CFBundleExecutable + {{.BinaryName}} + CFBundleIdentifier + {{.ProductIdentifier}} + CFBundleVersion + {{.ProductVersion}} + CFBundleGetInfoString + {{.ProductComments}} + CFBundleShortVersionString + {{.ProductVersion}} + CFBundleIconFile + icons + {{- if .CFBundleIconName}} + CFBundleIconName + {{.CFBundleIconName}} + {{- end}} + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + {{.ProductCopyright}} + {{- if .FileAssociations}} + CFBundleDocumentTypes + + {{- range .FileAssociations}} + + CFBundleTypeExtensions + + {{.Ext}} + + CFBundleTypeName + {{.Name}} + CFBundleTypeRole + {{.Role}} + CFBundleTypeIconFile + {{.IconName}} + {{- if .MimeType}} + CFBundleTypeMimeType + {{.MimeType}} + {{- end}} + + {{- end}} + + {{- end}} + {{- if .Protocols}} + CFBundleURLTypes + + {{- range .Protocols}} + + CFBundleURLName + wails.com.{{.Scheme}} + CFBundleURLSchemes + + {{.Scheme}} + + + {{- end}} + + {{- end}} + + \ No newline at end of file diff --git a/v3/internal/commands/updatable_build_assets/ios/Assets.xcassets.tmpl b/v3/internal/commands/updatable_build_assets/ios/Assets.xcassets.tmpl new file mode 100644 index 000000000..46fbb8787 --- /dev/null +++ b/v3/internal/commands/updatable_build_assets/ios/Assets.xcassets.tmpl @@ -0,0 +1,116 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "images" : [ + { + "filename" : "icon-20@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-20@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "icon-29@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-29@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "icon-40@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-40@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "icon-60@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "icon-60@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "icon-20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "icon-20@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "icon-29@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "icon-40@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "icon-76@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "icon-83.5@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon-1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ] +} \ No newline at end of file diff --git a/v3/internal/commands/updatable_build_assets/ios/Info.dev.plist.tmpl b/v3/internal/commands/updatable_build_assets/ios/Info.dev.plist.tmpl new file mode 100644 index 000000000..92810cb32 --- /dev/null +++ b/v3/internal/commands/updatable_build_assets/ios/Info.dev.plist.tmpl @@ -0,0 +1,62 @@ + + + + + CFBundleExecutable + {{.BinaryName}} + CFBundleIdentifier + {{.ProductIdentifier}}.dev + CFBundleName + {{.ProductName}} (Dev) + CFBundleDisplayName + {{.ProductName}} (Dev) + CFBundlePackageType + APPL + CFBundleShortVersionString + {{.ProductVersion}}-dev + CFBundleVersion + {{.ProductVersion}} + LSRequiresIPhoneOS + + MinimumOSVersion + 15.0 + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + + + + WailsDevelopmentMode + + {{if .ProductCopyright}} + NSHumanReadableCopyright + {{.ProductCopyright}} + {{end}} + {{if .ProductComments}} + CFBundleGetInfoString + {{.ProductComments}} + {{end}} + + \ No newline at end of file diff --git a/v3/internal/commands/updatable_build_assets/ios/Info.plist.tmpl b/v3/internal/commands/updatable_build_assets/ios/Info.plist.tmpl new file mode 100644 index 000000000..d4b5ca517 --- /dev/null +++ b/v3/internal/commands/updatable_build_assets/ios/Info.plist.tmpl @@ -0,0 +1,59 @@ + + + + + CFBundleExecutable + {{.BinaryName}} + CFBundleIdentifier + {{.ProductIdentifier}} + CFBundleName + {{.ProductName}} + CFBundleDisplayName + {{.ProductName}} + CFBundlePackageType + APPL + CFBundleShortVersionString + {{.ProductVersion}} + CFBundleVersion + {{.ProductVersion}} + LSRequiresIPhoneOS + + MinimumOSVersion + 15.0 + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + + + {{if .ProductCopyright}} + NSHumanReadableCopyright + {{.ProductCopyright}} + {{end}} + {{if .ProductComments}} + CFBundleGetInfoString + {{.ProductComments}} + {{end}} + + \ No newline at end of file diff --git a/v3/internal/commands/updatable_build_assets/ios/LaunchScreen.storyboard.tmpl b/v3/internal/commands/updatable_build_assets/ios/LaunchScreen.storyboard.tmpl new file mode 100644 index 000000000..e3e28f38c --- /dev/null +++ b/v3/internal/commands/updatable_build_assets/ios/LaunchScreen.storyboard.tmpl @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + {{if .ProductDescription}} + + {{end}} + + + + + + + + {{if .ProductDescription}} + + + + {{end}} + + + + + + + + + \ No newline at end of file diff --git a/v3/internal/commands/updatable_build_assets/ios/build.sh.tmpl b/v3/internal/commands/updatable_build_assets/ios/build.sh.tmpl new file mode 100644 index 000000000..26b0cbaf3 --- /dev/null +++ b/v3/internal/commands/updatable_build_assets/ios/build.sh.tmpl @@ -0,0 +1,72 @@ +#!/bin/bash +set -e + +# Build configuration +APP_NAME="{{.BinaryName}}" +BUNDLE_ID="{{.ProductIdentifier}}" +VERSION="{{.ProductVersion}}" +BUILD_NUMBER="{{.ProductVersion}}" +BUILD_DIR="build/ios" +TARGET="simulator" + +echo "Building iOS app: $APP_NAME" +echo "Bundle ID: $BUNDLE_ID" +echo "Version: $VERSION ($BUILD_NUMBER)" +echo "Target: $TARGET" + +# Ensure build directory exists +mkdir -p "$BUILD_DIR" + +# Determine SDK and target architecture +if [ "$TARGET" = "simulator" ]; then + SDK="iphonesimulator" + ARCH="arm64-apple-ios15.0-simulator" +elif [ "$TARGET" = "device" ]; then + SDK="iphoneos" + ARCH="arm64-apple-ios15.0" +else + echo "Unknown target: $TARGET" + exit 1 +fi + +# Get SDK path +SDK_PATH=$(xcrun --sdk $SDK --show-sdk-path) + +# Compile the application +echo "Compiling with SDK: $SDK" +xcrun -sdk $SDK clang \ + -target $ARCH \ + -isysroot "$SDK_PATH" \ + -framework Foundation \ + -framework UIKit \ + -framework WebKit \ + -framework CoreGraphics \ + -o "$BUILD_DIR/$APP_NAME" \ + "$BUILD_DIR/main.m" + +# Create app bundle +echo "Creating app bundle..." +APP_BUNDLE="$BUILD_DIR/$APP_NAME.app" +rm -rf "$APP_BUNDLE" +mkdir -p "$APP_BUNDLE" + +# Move executable +mv "$BUILD_DIR/$APP_NAME" "$APP_BUNDLE/" + +# Copy Info.plist +cp "$BUILD_DIR/Info.plist" "$APP_BUNDLE/" + +# Sign the app +echo "Signing app..." +codesign --force --sign - "$APP_BUNDLE" + +echo "Build complete: $APP_BUNDLE" + +# Deploy to simulator if requested +if [ "$TARGET" = "simulator" ]; then + echo "Deploying to simulator..." + xcrun simctl terminate booted "$BUNDLE_ID" 2>/dev/null || true + xcrun simctl install booted "$APP_BUNDLE" + xcrun simctl launch booted "$BUNDLE_ID" + echo "App launched on simulator" +fi \ No newline at end of file diff --git a/v3/internal/commands/updatable_build_assets/ios/entitlements.plist.tmpl b/v3/internal/commands/updatable_build_assets/ios/entitlements.plist.tmpl new file mode 100644 index 000000000..cc5d9582b --- /dev/null +++ b/v3/internal/commands/updatable_build_assets/ios/entitlements.plist.tmpl @@ -0,0 +1,21 @@ + + + + + + get-task-allow + + + + com.apple.security.app-sandbox + + + + com.apple.security.network.client + + + + com.apple.security.files.user-selected.read-only + + + \ No newline at end of file diff --git a/v3/internal/commands/updatable_build_assets/ios/project.pbxproj.tmpl b/v3/internal/commands/updatable_build_assets/ios/project.pbxproj.tmpl new file mode 100644 index 000000000..5c9944b24 --- /dev/null +++ b/v3/internal/commands/updatable_build_assets/ios/project.pbxproj.tmpl @@ -0,0 +1,222 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = {}; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + C0DEBEEF0000000000000001 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000002 /* main.m */; }; + C0DEBEEF00000000000000F1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000101 /* UIKit.framework */; }; + C0DEBEEF00000000000000F2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000102 /* Foundation.framework */; }; + C0DEBEEF00000000000000F3 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000103 /* WebKit.framework */; }; + C0DEBEEF00000000000000F4 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000104 /* Security.framework */; }; + C0DEBEEF00000000000000F5 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000105 /* CoreFoundation.framework */; }; + C0DEBEEF00000000000000F6 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000106 /* libresolv.tbd */; }; + C0DEBEEF00000000000000F7 /* {{.ProductName}}.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000107 /* {{.ProductName}}.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + C0DEBEEF0000000000000002 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + C0DEBEEF0000000000000003 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C0DEBEEF0000000000000004 /* {{.ProductName}}.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "{{.ProductName}}.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + C0DEBEEF0000000000000101 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000102 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000103 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000104 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000105 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000106 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.text-based-dylib-definition; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; }; + C0DEBEEF0000000000000107 /* {{.ProductName}}.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "{{.ProductName}}.a"; path = ../../../bin/{{.ProductName}}.a; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + C0DEBEEF0000000000000010 = { + isa = PBXGroup; + children = ( + C0DEBEEF0000000000000020 /* Products */, + C0DEBEEF0000000000000045 /* Frameworks */, + C0DEBEEF0000000000000030 /* main */, + ); + sourceTree = ""; + }; + C0DEBEEF0000000000000020 /* Products */ = { + isa = PBXGroup; + children = ( + C0DEBEEF0000000000000004 /* {{.ProductName}}.app */, + ); + name = Products; + sourceTree = ""; + }; + C0DEBEEF0000000000000030 /* main */ = { + isa = PBXGroup; + children = ( + C0DEBEEF0000000000000002 /* main.m */, + C0DEBEEF0000000000000003 /* Info.plist */, + ); + path = main; + sourceTree = SOURCE_ROOT; + }; + C0DEBEEF0000000000000045 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C0DEBEEF0000000000000101 /* UIKit.framework */, + C0DEBEEF0000000000000102 /* Foundation.framework */, + C0DEBEEF0000000000000103 /* WebKit.framework */, + C0DEBEEF0000000000000104 /* Security.framework */, + C0DEBEEF0000000000000105 /* CoreFoundation.framework */, + C0DEBEEF0000000000000106 /* libresolv.tbd */, + C0DEBEEF0000000000000107 /* {{.ProductName}}.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + C0DEBEEF0000000000000040 /* {{.ProductName}} */ = { + isa = PBXNativeTarget; + buildConfigurationList = C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "{{.ProductName}}" */; + buildPhases = ( + C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */, + C0DEBEEF0000000000000050 /* Sources */, + C0DEBEEF0000000000000056 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "{{.ProductName}}"; + productName = "{{.ProductName}}"; + productReference = C0DEBEEF0000000000000004 /* {{.ProductName}}.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C0DEBEEF0000000000000060 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1500; + ORGANIZATIONNAME = "{{.ProductCompany}}"; + TargetAttributes = { + C0DEBEEF0000000000000040 = { + CreatedOnToolsVersion = 15.0; + }; + }; + }; + buildConfigurationList = C0DEBEEF0000000000000080 /* Build configuration list for PBXProject "main" */; + compatibilityVersion = "Xcode 15.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = C0DEBEEF0000000000000010; + productRefGroup = C0DEBEEF0000000000000020 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C0DEBEEF0000000000000040 /* {{.ProductName}} */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXFrameworksBuildPhase section */ + C0DEBEEF0000000000000056 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C0DEBEEF00000000000000F7 /* {{.ProductName}}.a in Frameworks */, + C0DEBEEF00000000000000F1 /* UIKit.framework in Frameworks */, + C0DEBEEF00000000000000F2 /* Foundation.framework in Frameworks */, + C0DEBEEF00000000000000F3 /* WebKit.framework in Frameworks */, + C0DEBEEF00000000000000F4 /* Security.framework in Frameworks */, + C0DEBEEF00000000000000F5 /* CoreFoundation.framework in Frameworks */, + C0DEBEEF00000000000000F6 /* libresolv.tbd in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Prebuild: Wails Go Archive"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\nAPP_ROOT=\"${PROJECT_DIR}/../../..\"\nSDK_PATH=$(xcrun --sdk iphonesimulator --show-sdk-path)\nexport GOOS=ios\nexport GOARCH=arm64\nexport CGO_ENABLED=1\nexport CGO_CFLAGS=\"-isysroot ${SDK_PATH} -target arm64-apple-ios15.0-simulator -mios-simulator-version-min=15.0\"\nexport CGO_LDFLAGS=\"-isysroot ${SDK_PATH} -target arm64-apple-ios15.0-simulator\"\ncd \"${APP_ROOT}\"\n# Ensure overlay exists\nif [ ! -f build/ios/xcode/overlay.json ]; then\n wails3 ios overlay:gen -out build/ios/xcode/overlay.json -config build/config.yml || true\nfi\n# Build Go c-archive if missing or older than sources\nif [ ! -f bin/{{.ProductName}}.a ]; then\n echo \"Building Go c-archive...\"\n go build -buildmode=c-archive -overlay build/ios/xcode/overlay.json -o bin/{{.ProductName}}.a\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C0DEBEEF0000000000000050 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C0DEBEEF0000000000000001 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + C0DEBEEF0000000000000090 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = main/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + PRODUCT_BUNDLE_IDENTIFIER = "{{.ProductIdentifier}}"; + PRODUCT_NAME = "{{.ProductName}}"; + CODE_SIGNING_ALLOWED = NO; + SDKROOT = iphonesimulator; + }; + name = Debug; + }; + C0DEBEEF00000000000000A0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = main/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + PRODUCT_BUNDLE_IDENTIFIER = "{{.ProductIdentifier}}"; + PRODUCT_NAME = "{{.ProductName}}"; + CODE_SIGNING_ALLOWED = NO; + SDKROOT = iphonesimulator; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "{{.ProductName}}" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C0DEBEEF0000000000000090 /* Debug */, + C0DEBEEF00000000000000A0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + C0DEBEEF0000000000000080 /* Build configuration list for PBXProject "main" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C0DEBEEF0000000000000090 /* Debug */, + C0DEBEEF00000000000000A0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = C0DEBEEF0000000000000060 /* Project object */; +} diff --git a/v3/internal/commands/updatable_build_assets/linux/desktop.tmpl b/v3/internal/commands/updatable_build_assets/linux/desktop.tmpl new file mode 100644 index 000000000..ab22e2be4 --- /dev/null +++ b/v3/internal/commands/updatable_build_assets/linux/desktop.tmpl @@ -0,0 +1,15 @@ +[Desktop Entry] +Version=1.0 +Name={{.ProductName}} +Comment={{.ProductDescription}} +# The Exec line includes %u to pass the URL to the application +Exec=/usr/local/bin/{{.BinaryName}} %u +Terminal=false +Type=Application +Icon={{.BinaryName}} +Categories=Utility; +StartupWMClass={{.BinaryName}} + +{{if .Protocols -}} +MimeType={{range $index, $protocol := .Protocols}}x-scheme-handler/{{$protocol.Scheme}};{{end}} +{{- end}} diff --git a/v3/internal/commands/updatable_build_assets/linux/nfpm/nfpm.yaml.tmpl b/v3/internal/commands/updatable_build_assets/linux/nfpm/nfpm.yaml.tmpl new file mode 100644 index 000000000..06ad22120 --- /dev/null +++ b/v3/internal/commands/updatable_build_assets/linux/nfpm/nfpm.yaml.tmpl @@ -0,0 +1,67 @@ +# Feel free to remove those if you don't want/need to use them. +# Make sure to check the documentation at https://nfpm.goreleaser.com +# +# The lines below are called `modelines`. See `:help modeline` + +name: "{{.BinaryName}}" +arch: ${GOARCH} +platform: "linux" +version: "{{.ProductVersion}}" +section: "default" +priority: "extra" +maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> +description: "{{.ProductDescription}}" +vendor: "{{.ProductCompany}}" +homepage: "https://wails.io" +license: "MIT" +release: "1" + +contents: + - src: "./bin/{{.BinaryName}}" + dst: "/usr/local/bin/{{.BinaryName}}" + - src: "./build/appicon.png" + dst: "/usr/share/icons/hicolor/128x128/apps/{{.BinaryName}}.png" + - src: "./build/linux/{{.BinaryName}}.desktop" + dst: "/usr/share/applications/{{.BinaryName}}.desktop" + +# Default dependencies for Debian 12/Ubuntu 22.04+ with WebKit 4.1 +depends: + - libgtk-3-0 + - libwebkit2gtk-4.1-0 + +# Distribution-specific overrides for different package formats and WebKit versions +overrides: + # RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0) + rpm: + depends: + - gtk3 + - webkit2gtk4.1 + + # Arch Linux packages (WebKit 4.1) + archlinux: + depends: + - gtk3 + - webkit2gtk-4.1 + +# scripts section to ensure desktop database is updated after install +scripts: + postinstall: "./build/linux/nfpm/scripts/postinstall.sh" + # You can also add preremove, postremove if needed + # preremove: "./build/linux/nfpm/scripts/preremove.sh" + # postremove: "./build/linux/nfpm/scripts/postremove.sh" + +# replaces: +# - foobar +# provides: +# - bar +# depends: +# - gtk3 +# - libwebkit2gtk +# recommends: +# - whatever +# suggests: +# - something-else +# conflicts: +# - not-foo +# - not-bar +# changelog: "changelog.yaml" diff --git a/v3/internal/commands/updatable_build_assets/windows/info.json.tmpl b/v3/internal/commands/updatable_build_assets/windows/info.json.tmpl new file mode 100644 index 000000000..c3423b2dd --- /dev/null +++ b/v3/internal/commands/updatable_build_assets/windows/info.json.tmpl @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "{{.ProductVersion}}" + }, + "info": { + "0000": { + "ProductVersion": "{{.ProductVersion}}", + "CompanyName": "{{.ProductCompany}}", + "FileDescription": "{{.ProductDescription}}", + "LegalCopyright": "{{.ProductCopyright}}", + "ProductName": "{{.ProductName}}", + "Comments": "{{.ProductComments}}" + } + } +} \ No newline at end of file diff --git a/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl b/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl new file mode 100644 index 000000000..b4b0c2b4e --- /dev/null +++ b/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl @@ -0,0 +1,246 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "{{.Name}}" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "{{.ProductCompany}}" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "{{.ProductName}}" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "{{.ProductVersion}}" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "{{.ProductCopyright}}" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + {{range .FileAssociations}} + !insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" + File "..\{{.IconName}}.ico" + {{end}} +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + {{range .FileAssociations}} + !insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}" + Delete "$INSTDIR\{{.IconName}}.ico" + {{end}} +!macroend + +!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" +!macroend + +!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" +!macroend + +!macro wails.associateCustomProtocols + ; Create custom protocols associations + {{range .Protocols}} + !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" + {{end}} +!macroend + +!macro wails.unassociateCustomProtocols + ; Delete app custom protocol associations + {{range .Protocols}} + !insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}" + {{end}} +!macroend \ No newline at end of file diff --git a/v3/internal/commands/updatable_build_assets/windows/wails.exe.manifest.tmpl b/v3/internal/commands/updatable_build_assets/windows/wails.exe.manifest.tmpl new file mode 100644 index 000000000..54fb9f41a --- /dev/null +++ b/v3/internal/commands/updatable_build_assets/windows/wails.exe.manifest.tmpl @@ -0,0 +1,22 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + + + + + + + + \ No newline at end of file diff --git a/v3/internal/commands/update_cli.go b/v3/internal/commands/update_cli.go new file mode 100644 index 000000000..a7f51796f --- /dev/null +++ b/v3/internal/commands/update_cli.go @@ -0,0 +1,178 @@ +package commands + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v3/internal/debug" + "github.com/wailsapp/wails/v3/internal/github" + "github.com/wailsapp/wails/v3/internal/term" + "github.com/wailsapp/wails/v3/internal/version" +) + +type UpdateCLIOptions struct { + NoColour bool `name:"n" description:"Disable colour output"` + PreRelease bool `name:"pre" description:"Update to the latest pre-release (eg beta)"` + Version string `name:"version" description:"Update to a specific version (eg v3.0.0)"` + Latest bool `name:"latest" description:"Install the latest stable release"` +} + +func UpdateCLI(options *UpdateCLIOptions) error { + if options.NoColour { + term.DisableColor() + } + + term.Header("Update CLI") + + // Check if this CLI has been installed from vcs + if debug.LocalModulePath != "" && !options.Latest { + v3Path := filepath.ToSlash(debug.LocalModulePath + "/v3") + term.Println("This Wails CLI has been installed from source. To update to the latest stable release, run the following commands in the `" + v3Path + "` directory:") + term.Println(" - git pull") + term.Println(" - wails3 task install") + term.Println("") + term.Println("If you want to install the latest release, please run `wails3 update cli -latest`") + return nil + } + + if options.Latest { + latestVersion, err := github.GetLatestStableRelease() + if err != nil { + return err + } + return updateToVersion(latestVersion, true, version.String()) + } + + term.Println("Checking for updates...") + + var desiredVersion *github.SemanticVersion + var err error + var valid bool + + if len(options.Version) > 0 { + // Check if this is a valid version + valid, err = github.IsValidTag(options.Version) + if err == nil { + if !valid { + err = fmt.Errorf("version '%s' is invalid", options.Version) + } else { + desiredVersion, err = github.NewSemanticVersion(options.Version) + } + } + } else { + if options.PreRelease { + desiredVersion, err = github.GetLatestPreRelease() + } else { + desiredVersion, err = github.GetLatestStableRelease() + if err != nil { + pterm.Println("") + pterm.Println("No stable release found for this major version. To update to the latest pre-release (eg beta), run:") + pterm.Println(" wails3 update cli -pre") + return nil + } + } + } + if err != nil { + return err + } + pterm.Println() + + currentVersion := version.String() + pterm.Printf(" Current Version : %s\n", currentVersion) + + if len(options.Version) > 0 { + fmt.Printf(" Desired Version : v%s\n", desiredVersion) + } else { + if options.PreRelease { + fmt.Printf(" Latest Prerelease : v%s\n", desiredVersion) + } else { + fmt.Printf(" Latest Release : v%s\n", desiredVersion) + } + } + + return updateToVersion(desiredVersion, len(options.Version) > 0, currentVersion) +} + +func updateToVersion(targetVersion *github.SemanticVersion, force bool, currentVersion string) error { + targetVersionString := "v" + targetVersion.String() + + if targetVersionString == currentVersion { + pterm.Println("\nLooks like you're up to date!") + return nil + } + + var desiredVersion string + + if !force { + compareVersion := currentVersion + + currentVersion, err := github.NewSemanticVersion(compareVersion) + if err != nil { + return err + } + + var success bool + + // Release -> Pre-Release = Massage current version to prerelease format + if targetVersion.IsPreRelease() && currentVersion.IsRelease() { + testVersion, err := github.NewSemanticVersion(compareVersion + "-0") + if err != nil { + return err + } + success, _ = targetVersion.IsGreaterThan(testVersion) + } + // Pre-Release -> Release = Massage target version to prerelease format + if targetVersion.IsRelease() && currentVersion.IsPreRelease() { + mainversion := currentVersion.MainVersion() + targetVersion, err = github.NewSemanticVersion(targetVersion.String()) + if err != nil { + return err + } + success, _ = targetVersion.IsGreaterThanOrEqual(mainversion) + } + + // Release -> Release = Standard check + if (targetVersion.IsRelease() && currentVersion.IsRelease()) || + (targetVersion.IsPreRelease() && currentVersion.IsPreRelease()) { + success, _ = targetVersion.IsGreaterThan(currentVersion) + } + + // Compare + if !success { + pterm.Println("Error: The requested version is lower than the current version.") + pterm.Printf("If this is what you really want to do, use `wails3 update cli -version %s`\n", targetVersionString) + return nil + } + + desiredVersion = "v" + targetVersion.String() + } else { + desiredVersion = "v" + targetVersion.String() + } + + pterm.Println() + pterm.Print("Installing Wails CLI " + desiredVersion + "...") + + // Run command in non module directory + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("cannot find home directory: %w", err) + } + + cmd := exec.Command("go", "install", "github.com/wailsapp/wails/v3/cmd/wails@"+desiredVersion) + cmd.Dir = homeDir + sout, serr := cmd.CombinedOutput() + if err := cmd.Run(); err != nil { + pterm.Println("Failed.") + pterm.Error.Println(string(sout) + "\n" + serr.Error()) + return err + } + pterm.Println("Done.") + pterm.Println("\nMake sure you update your project go.mod file to use " + desiredVersion + ":") + pterm.Println(" require github.com/wailsapp/wails/v3 " + desiredVersion) + pterm.Println("\nTo view the release notes, please run `wails3 releasenotes`") + + return nil +} diff --git a/v3/internal/commands/version.go b/v3/internal/commands/version.go new file mode 100644 index 000000000..65bf66113 --- /dev/null +++ b/v3/internal/commands/version.go @@ -0,0 +1,14 @@ +package commands + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/internal/version" +) + +type VersionOptions struct{} + +func Version(_ *VersionOptions) error { + DisableFooter = true + println(version.String()) + return nil +} diff --git a/v3/internal/commands/watcher.go b/v3/internal/commands/watcher.go new file mode 100644 index 000000000..13f83ad61 --- /dev/null +++ b/v3/internal/commands/watcher.go @@ -0,0 +1,65 @@ +package commands + +import ( + "github.com/atterpac/refresh/engine" + "github.com/wailsapp/wails/v3/internal/signal" + "gopkg.in/yaml.v3" + "os" +) + +type WatcherOptions struct { + Config string `description:"The config file including path" default:"."` +} + +func Watcher(options *WatcherOptions) error { + stopChan := make(chan struct{}) + + // Parse the config file + type devConfig struct { + Config engine.Config `yaml:"dev_mode"` + } + + var devconfig devConfig + + // Parse the config file + c, err := os.ReadFile(options.Config) + if err != nil { + return err + } + err = yaml.Unmarshal(c, &devconfig) + if err != nil { + return err + } + + watcherEngine, err := engine.NewEngineFromConfig(devconfig.Config) + if err != nil { + return err + } + + // Setup cleanup function that stops the engine + cleanup := func() { + watcherEngine.Stop() + } + defer cleanup() + + // Signal handler needs to notify when to stop + signalCleanup := func() { + cleanup() + stopChan <- struct{}{} + } + + signalHandler := signal.NewSignalHandler(signalCleanup) + signalHandler.ExitMessage = func(sig os.Signal) string { + return "" + } + signalHandler.Start() + + // Start the engine + err = watcherEngine.Start() + if err != nil { + return err + } + + <-stopChan + return nil +} diff --git a/v3/internal/commands/webview2/MicrosoftEdgeWebview2Setup.exe b/v3/internal/commands/webview2/MicrosoftEdgeWebview2Setup.exe new file mode 100644 index 000000000..89a56ec16 Binary files /dev/null and b/v3/internal/commands/webview2/MicrosoftEdgeWebview2Setup.exe differ diff --git a/v3/internal/dbus/DbusMenu.xml b/v3/internal/dbus/DbusMenu.xml new file mode 100644 index 000000000..db6959845 --- /dev/null +++ b/v3/internal/dbus/DbusMenu.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v3/internal/dbus/README.md b/v3/internal/dbus/README.md new file mode 100644 index 000000000..d2cfce105 --- /dev/null +++ b/v3/internal/dbus/README.md @@ -0,0 +1,5 @@ +//Note that you need to have github.com/knightpp/dbus-codegen-go installed from "custom" branch + +//go:generate dbus-codegen-go -prefix org.kde -package notifier -output internal/generated/notifier/status_notifier_item.go internal/StatusNotifierItem.xml +//go:generate dbus-codegen-go -prefix com.canonical -package menu -output internal/generated/menu/dbus_menu.go internal/DbusMenu.xml + diff --git a/v3/internal/dbus/StatusNotifierItem.xml b/v3/internal/dbus/StatusNotifierItem.xml new file mode 100644 index 000000000..1093d3d18 --- /dev/null +++ b/v3/internal/dbus/StatusNotifierItem.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v3/internal/dbus/dbus.go b/v3/internal/dbus/dbus.go new file mode 100644 index 000000000..4e4a3adbd --- /dev/null +++ b/v3/internal/dbus/dbus.go @@ -0,0 +1,4 @@ +package dbus + +//go:generate dbus-codegen-go -prefix org.kde -package notifier -output notifier/status_notifier_item.go StatusNotifierItem.xml +//go:generate dbus-codegen-go -prefix com.canonical -package menu -output menu/dbus_menu.go DbusMenu.xml diff --git a/v3/internal/dbus/generate.sh b/v3/internal/dbus/generate.sh new file mode 100755 index 000000000..03716e522 --- /dev/null +++ b/v3/internal/dbus/generate.sh @@ -0,0 +1,2 @@ +dbus-codegen-go -prefix com.canonical -package menu -output dbus_menu.go DbusMenu.xml +dbus-codegen-go -prefix org.kde -package notifier -output status_notifier_item.go StatusNotifierItem.xml diff --git a/v3/internal/dbus/menu/.keep b/v3/internal/dbus/menu/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/v3/internal/dbus/menu/dbus_menu.go b/v3/internal/dbus/menu/dbus_menu.go new file mode 100644 index 000000000..cbf4fd320 --- /dev/null +++ b/v3/internal/dbus/menu/dbus_menu.go @@ -0,0 +1,483 @@ +// Code generated by dbus-codegen-go DO NOT EDIT. +package menu + +import ( + "context" + "errors" + "fmt" + "github.com/godbus/dbus/v5" + "github.com/godbus/dbus/v5/introspect" +) + +var ( + // Introspection for com.canonical.dbusmenu + IntrospectDataDbusmenu = introspect.Interface{ + Name: "com.canonical.dbusmenu", + Methods: []introspect.Method{{Name: "GetLayout", Args: []introspect.Arg{ + {Name: "parentId", Type: "i", Direction: "in"}, + {Name: "recursionDepth", Type: "i", Direction: "in"}, + {Name: "propertyNames", Type: "as", Direction: "in"}, + {Name: "revision", Type: "u", Direction: "out"}, + {Name: "layout", Type: "(ia{sv}av)", Direction: "out"}, + }}, + {Name: "GetGroupProperties", Args: []introspect.Arg{ + {Name: "ids", Type: "ai", Direction: "in"}, + {Name: "propertyNames", Type: "as", Direction: "in"}, + {Name: "properties", Type: "a(ia{sv})", Direction: "out"}, + }}, + {Name: "GetProperty", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "in"}, + {Name: "name", Type: "s", Direction: "in"}, + {Name: "value", Type: "v", Direction: "out"}, + }}, + {Name: "Event", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "in"}, + {Name: "eventId", Type: "s", Direction: "in"}, + {Name: "data", Type: "v", Direction: "in"}, + {Name: "timestamp", Type: "u", Direction: "in"}, + }}, + {Name: "EventGroup", Args: []introspect.Arg{ + {Name: "events", Type: "a(isvu)", Direction: "in"}, + {Name: "idErrors", Type: "ai", Direction: "out"}, + }}, + {Name: "AboutToShow", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "in"}, + {Name: "needUpdate", Type: "b", Direction: "out"}, + }}, + {Name: "AboutToShowGroup", Args: []introspect.Arg{ + {Name: "ids", Type: "ai", Direction: "in"}, + {Name: "updatesNeeded", Type: "ai", Direction: "out"}, + {Name: "idErrors", Type: "ai", Direction: "out"}, + }}, + }, + Signals: []introspect.Signal{{Name: "ItemsPropertiesUpdated", Args: []introspect.Arg{ + {Name: "updatedProps", Type: "a(ia{sv})", Direction: "out"}, + {Name: "removedProps", Type: "a(ias)", Direction: "out"}, + }}, + {Name: "LayoutUpdated", Args: []introspect.Arg{ + {Name: "revision", Type: "u", Direction: "out"}, + {Name: "parent", Type: "i", Direction: "out"}, + }}, + {Name: "ItemActivationRequested", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "out"}, + {Name: "timestamp", Type: "u", Direction: "out"}, + }}, + }, + Properties: []introspect.Property{{Name: "Version", Type: "u", Access: "read"}, + {Name: "TextDirection", Type: "s", Access: "read"}, + {Name: "Status", Type: "s", Access: "read"}, + {Name: "IconThemePath", Type: "as", Access: "read"}, + }, + Annotations: []introspect.Annotation{}, + } +) + +// Signal is a common interface for all signals. +type Signal interface { + Name() string + Interface() string + Sender() string + + path() dbus.ObjectPath + values() []interface{} +} + +// Emit sends the given signal to the bus. +func Emit(conn *dbus.Conn, s Signal) error { + return conn.Emit(s.path(), s.Interface()+"."+s.Name(), s.values()...) +} + +// ErrUnknownSignal is returned by LookupSignal when a signal cannot be resolved. +var ErrUnknownSignal = errors.New("unknown signal") + +// LookupSignal converts the given raw D-Bus signal with variable body +// into one with typed structured body or returns ErrUnknownSignal error. +func LookupSignal(signal *dbus.Signal) (Signal, error) { + switch signal.Name { + case InterfaceDbusmenu + "." + "ItemsPropertiesUpdated": + v0, ok := signal.Body[0].([]struct { + V0 int32 + V1 map[string]dbus.Variant + }) + if !ok { + return nil, fmt.Errorf("prop .UpdatedProps is %T, not []struct {V0 int32;V1 map[string]dbus.Variant}", signal.Body[0]) + } + v1, ok := signal.Body[1].([]struct { + V0 int32 + V1 []string + }) + if !ok { + return nil, fmt.Errorf("prop .RemovedProps is %T, not []struct {V0 int32;V1 []string}", signal.Body[1]) + } + return &Dbusmenu_ItemsPropertiesUpdatedSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &Dbusmenu_ItemsPropertiesUpdatedSignalBody{ + UpdatedProps: v0, + RemovedProps: v1, + }, + }, nil + case InterfaceDbusmenu + "." + "LayoutUpdated": + v0, ok := signal.Body[0].(uint32) + if !ok { + return nil, fmt.Errorf("prop .Revision is %T, not uint32", signal.Body[0]) + } + v1, ok := signal.Body[1].(int32) + if !ok { + return nil, fmt.Errorf("prop .Parent is %T, not int32", signal.Body[1]) + } + return &Dbusmenu_LayoutUpdatedSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &Dbusmenu_LayoutUpdatedSignalBody{ + Revision: v0, + Parent: v1, + }, + }, nil + case InterfaceDbusmenu + "." + "ItemActivationRequested": + v0, ok := signal.Body[0].(int32) + if !ok { + return nil, fmt.Errorf("prop .Id is %T, not int32", signal.Body[0]) + } + v1, ok := signal.Body[1].(uint32) + if !ok { + return nil, fmt.Errorf("prop .Timestamp is %T, not uint32", signal.Body[1]) + } + return &Dbusmenu_ItemActivationRequestedSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &Dbusmenu_ItemActivationRequestedSignalBody{ + Id: v0, + Timestamp: v1, + }, + }, nil + default: + return nil, ErrUnknownSignal + } +} + +// AddMatchSignal registers a match rule for the given signal, +// opts are appended to the automatically generated signal's rules. +func AddMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.AddMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// RemoveMatchSignal unregisters the previously registered subscription. +func RemoveMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.RemoveMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// Interface name constants. +const ( + InterfaceDbusmenu = "com.canonical.dbusmenu" +) + +// Dbusmenuer is com.canonical.dbusmenu interface. +type Dbusmenuer interface { + // GetLayout is com.canonical.dbusmenu.GetLayout method. + GetLayout(parentId int32, recursionDepth int32, propertyNames []string) (revision uint32, layout struct { + V0 int32 + V1 map[string]dbus.Variant + V2 []dbus.Variant + }, err *dbus.Error) + // GetGroupProperties is com.canonical.dbusmenu.GetGroupProperties method. + GetGroupProperties(ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant + }, err *dbus.Error) + // GetProperty is com.canonical.dbusmenu.GetProperty method. + GetProperty(id int32, name string) (value dbus.Variant, err *dbus.Error) + // Event is com.canonical.dbusmenu.Event method. + Event(id int32, eventId string, data dbus.Variant, timestamp uint32) (err *dbus.Error) + // EventGroup is com.canonical.dbusmenu.EventGroup method. + EventGroup(events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 + }) (idErrors []int32, err *dbus.Error) + // AboutToShow is com.canonical.dbusmenu.AboutToShow method. + AboutToShow(id int32) (needUpdate bool, err *dbus.Error) + // AboutToShowGroup is com.canonical.dbusmenu.AboutToShowGroup method. + AboutToShowGroup(ids []int32) (updatesNeeded []int32, idErrors []int32, err *dbus.Error) +} + +// ExportDbusmenu exports the given object that implements com.canonical.dbusmenu on the bus. +func ExportDbusmenu(conn *dbus.Conn, path dbus.ObjectPath, v Dbusmenuer) error { + return conn.ExportSubtreeMethodTable(map[string]interface{}{ + "GetLayout": v.GetLayout, + "GetGroupProperties": v.GetGroupProperties, + "GetProperty": v.GetProperty, + "Event": v.Event, + "EventGroup": v.EventGroup, + "AboutToShow": v.AboutToShow, + "AboutToShowGroup": v.AboutToShowGroup, + }, path, InterfaceDbusmenu) +} + +// UnexportDbusmenu unexports com.canonical.dbusmenu interface on the named path. +func UnexportDbusmenu(conn *dbus.Conn, path dbus.ObjectPath) error { + return conn.Export(nil, path, InterfaceDbusmenu) +} + +// UnimplementedDbusmenu can be embedded to have forward compatible server implementations. +type UnimplementedDbusmenu struct{} + +func (*UnimplementedDbusmenu) iface() string { + return InterfaceDbusmenu +} + +func (*UnimplementedDbusmenu) GetLayout(parentId int32, recursionDepth int32, propertyNames []string) (revision uint32, layout struct { + V0 int32 + V1 map[string]dbus.Variant + V2 []dbus.Variant +}, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) GetGroupProperties(ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant +}, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) GetProperty(id int32, name string) (value dbus.Variant, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) Event(id int32, eventId string, data dbus.Variant, timestamp uint32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) EventGroup(events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 +}) (idErrors []int32, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) AboutToShow(id int32) (needUpdate bool, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) AboutToShowGroup(ids []int32) (updatesNeeded []int32, idErrors []int32, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +// NewDbusmenu creates and allocates com.canonical.dbusmenu. +func NewDbusmenu(object dbus.BusObject) *Dbusmenu { + return &Dbusmenu{object} +} + +// Dbusmenu implements com.canonical.dbusmenu D-Bus interface. +type Dbusmenu struct { + object dbus.BusObject +} + +// GetLayout calls com.canonical.dbusmenu.GetLayout method. +func (o *Dbusmenu) GetLayout(ctx context.Context, parentId int32, recursionDepth int32, propertyNames []string) (revision uint32, layout struct { + V0 int32 + V1 map[string]dbus.Variant + V2 []dbus.Variant +}, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".GetLayout", 0, parentId, recursionDepth, propertyNames).Store(&revision, &layout) + return +} + +// GetGroupProperties calls com.canonical.dbusmenu.GetGroupProperties method. +func (o *Dbusmenu) GetGroupProperties(ctx context.Context, ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant +}, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".GetGroupProperties", 0, ids, propertyNames).Store(&properties) + return +} + +// GetProperty calls com.canonical.dbusmenu.GetProperty method. +func (o *Dbusmenu) GetProperty(ctx context.Context, id int32, name string) (value dbus.Variant, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".GetProperty", 0, id, name).Store(&value) + return +} + +// Event calls com.canonical.dbusmenu.Event method. +func (o *Dbusmenu) Event(ctx context.Context, id int32, eventId string, data dbus.Variant, timestamp uint32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".Event", 0, id, eventId, data, timestamp).Store() + return +} + +// EventGroup calls com.canonical.dbusmenu.EventGroup method. +func (o *Dbusmenu) EventGroup(ctx context.Context, events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 +}) (idErrors []int32, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".EventGroup", 0, events).Store(&idErrors) + return +} + +// AboutToShow calls com.canonical.dbusmenu.AboutToShow method. +func (o *Dbusmenu) AboutToShow(ctx context.Context, id int32) (needUpdate bool, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".AboutToShow", 0, id).Store(&needUpdate) + return +} + +// AboutToShowGroup calls com.canonical.dbusmenu.AboutToShowGroup method. +func (o *Dbusmenu) AboutToShowGroup(ctx context.Context, ids []int32) (updatesNeeded []int32, idErrors []int32, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".AboutToShowGroup", 0, ids).Store(&updatesNeeded, &idErrors) + return +} + +// GetVersion gets com.canonical.dbusmenu.Version property. +func (o *Dbusmenu) GetVersion(ctx context.Context) (version uint32, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "Version").Store(&version) + return +} + +// GetTextDirection gets com.canonical.dbusmenu.TextDirection property. +func (o *Dbusmenu) GetTextDirection(ctx context.Context) (textDirection string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "TextDirection").Store(&textDirection) + return +} + +// GetStatus gets com.canonical.dbusmenu.Status property. +func (o *Dbusmenu) GetStatus(ctx context.Context) (status string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "Status").Store(&status) + return +} + +// GetIconThemePath gets com.canonical.dbusmenu.IconThemePath property. +func (o *Dbusmenu) GetIconThemePath(ctx context.Context) (iconThemePath []string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "IconThemePath").Store(&iconThemePath) + return +} + +// Dbusmenu_ItemsPropertiesUpdatedSignal represents com.canonical.dbusmenu.ItemsPropertiesUpdated signal. +type Dbusmenu_ItemsPropertiesUpdatedSignal struct { + sender string + Path dbus.ObjectPath + Body *Dbusmenu_ItemsPropertiesUpdatedSignalBody +} + +// Name returns the signal's name. +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) Name() string { + return "ItemsPropertiesUpdated" +} + +// Interface returns the signal's interface. +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) Interface() string { + return InterfaceDbusmenu +} + +// Sender returns the signal's sender unique name. +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) Sender() string { + return s.sender +} + +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) values() []interface{} { + return []interface{}{s.Body.UpdatedProps, s.Body.RemovedProps} +} + +// Dbusmenu_ItemsPropertiesUpdatedSignalBody is body container. +type Dbusmenu_ItemsPropertiesUpdatedSignalBody struct { + UpdatedProps []struct { + V0 int32 + V1 map[string]dbus.Variant + } + RemovedProps []struct { + V0 int32 + V1 []string + } +} + +// Dbusmenu_LayoutUpdatedSignal represents com.canonical.dbusmenu.LayoutUpdated signal. +type Dbusmenu_LayoutUpdatedSignal struct { + sender string + Path dbus.ObjectPath + Body *Dbusmenu_LayoutUpdatedSignalBody +} + +// Name returns the signal's name. +func (s *Dbusmenu_LayoutUpdatedSignal) Name() string { + return "LayoutUpdated" +} + +// Interface returns the signal's interface. +func (s *Dbusmenu_LayoutUpdatedSignal) Interface() string { + return InterfaceDbusmenu +} + +// Sender returns the signal's sender unique name. +func (s *Dbusmenu_LayoutUpdatedSignal) Sender() string { + return s.sender +} + +func (s *Dbusmenu_LayoutUpdatedSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *Dbusmenu_LayoutUpdatedSignal) values() []interface{} { + return []interface{}{s.Body.Revision, s.Body.Parent} +} + +// Dbusmenu_LayoutUpdatedSignalBody is body container. +type Dbusmenu_LayoutUpdatedSignalBody struct { + Revision uint32 + Parent int32 +} + +// Dbusmenu_ItemActivationRequestedSignal represents com.canonical.dbusmenu.ItemActivationRequested signal. +type Dbusmenu_ItemActivationRequestedSignal struct { + sender string + Path dbus.ObjectPath + Body *Dbusmenu_ItemActivationRequestedSignalBody +} + +// Name returns the signal's name. +func (s *Dbusmenu_ItemActivationRequestedSignal) Name() string { + return "ItemActivationRequested" +} + +// Interface returns the signal's interface. +func (s *Dbusmenu_ItemActivationRequestedSignal) Interface() string { + return InterfaceDbusmenu +} + +// Sender returns the signal's sender unique name. +func (s *Dbusmenu_ItemActivationRequestedSignal) Sender() string { + return s.sender +} + +func (s *Dbusmenu_ItemActivationRequestedSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *Dbusmenu_ItemActivationRequestedSignal) values() []interface{} { + return []interface{}{s.Body.Id, s.Body.Timestamp} +} + +// Dbusmenu_ItemActivationRequestedSignalBody is body container. +type Dbusmenu_ItemActivationRequestedSignalBody struct { + Id int32 + Timestamp uint32 +} diff --git a/v3/internal/dbus/notifier/.keep b/v3/internal/dbus/notifier/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/v3/internal/dbus/notifier/status_notifier_item.go b/v3/internal/dbus/notifier/status_notifier_item.go new file mode 100644 index 000000000..998916440 --- /dev/null +++ b/v3/internal/dbus/notifier/status_notifier_item.go @@ -0,0 +1,636 @@ +// Code generated by dbus-codegen-go DO NOT EDIT. +package notifier + +import ( + "context" + "errors" + "fmt" + "github.com/godbus/dbus/v5" + "github.com/godbus/dbus/v5/introspect" +) + +var ( + // Introspection for org.kde.StatusNotifierItem + IntrospectDataStatusNotifierItem = introspect.Interface{ + Name: "org.kde.StatusNotifierItem", + Methods: []introspect.Method{{Name: "ContextMenu", Args: []introspect.Arg{ + {Name: "x", Type: "i", Direction: "in"}, + {Name: "y", Type: "i", Direction: "in"}, + }}, + {Name: "Activate", Args: []introspect.Arg{ + {Name: "x", Type: "i", Direction: "in"}, + {Name: "y", Type: "i", Direction: "in"}, + }}, + {Name: "SecondaryActivate", Args: []introspect.Arg{ + {Name: "x", Type: "i", Direction: "in"}, + {Name: "y", Type: "i", Direction: "in"}, + }}, + {Name: "Scroll", Args: []introspect.Arg{ + {Name: "delta", Type: "i", Direction: "in"}, + {Name: "orientation", Type: "s", Direction: "in"}, + }}, + }, + Signals: []introspect.Signal{{Name: "NewTitle"}, + {Name: "NewIcon"}, + {Name: "NewAttentionIcon"}, + {Name: "NewOverlayIcon"}, + {Name: "NewStatus", Args: []introspect.Arg{ + {Name: "status", Type: "s", Direction: ""}, + }}, + {Name: "NewIconThemePath", Args: []introspect.Arg{ + {Name: "icon_theme_path", Type: "s", Direction: "out"}, + }}, + {Name: "NewMenu"}, + }, + Properties: []introspect.Property{{Name: "Category", Type: "s", Access: "read"}, + {Name: "Id", Type: "s", Access: "read"}, + {Name: "Title", Type: "s", Access: "read"}, + {Name: "Status", Type: "s", Access: "read"}, + {Name: "WindowId", Type: "i", Access: "read"}, + {Name: "IconThemePath", Type: "s", Access: "read"}, + {Name: "Menu", Type: "o", Access: "read"}, + {Name: "ItemIsMenu", Type: "b", Access: "read"}, + {Name: "IconName", Type: "s", Access: "read"}, + {Name: "IconPixmap", Type: "a(iiay)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusImageVector"}, + }}, + {Name: "OverlayIconName", Type: "s", Access: "read"}, + {Name: "OverlayIconPixmap", Type: "a(iiay)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusImageVector"}, + }}, + {Name: "AttentionIconName", Type: "s", Access: "read"}, + {Name: "AttentionIconPixmap", Type: "a(iiay)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusImageVector"}, + }}, + {Name: "AttentionMovieName", Type: "s", Access: "read"}, + {Name: "ToolTip", Type: "(sa(iiay)ss)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusToolTipStruct"}, + }}, + }, + Annotations: []introspect.Annotation{}, + } +) + +// Signal is a common interface for all signals. +type Signal interface { + Name() string + Interface() string + Sender() string + + path() dbus.ObjectPath + values() []interface{} +} + +// Emit sends the given signal to the bus. +func Emit(conn *dbus.Conn, s Signal) error { + return conn.Emit(s.path(), s.Interface()+"."+s.Name(), s.values()...) +} + +// ErrUnknownSignal is returned by LookupSignal when a signal cannot be resolved. +var ErrUnknownSignal = errors.New("unknown signal") + +// LookupSignal converts the given raw D-Bus signal with variable body +// into one with typed structured body or returns ErrUnknownSignal error. +func LookupSignal(signal *dbus.Signal) (Signal, error) { + switch signal.Name { + case InterfaceStatusNotifierItem + "." + "NewTitle": + return &StatusNotifierItem_NewTitleSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewTitleSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewIcon": + return &StatusNotifierItem_NewIconSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewIconSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewAttentionIcon": + return &StatusNotifierItem_NewAttentionIconSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewAttentionIconSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewOverlayIcon": + return &StatusNotifierItem_NewOverlayIconSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewOverlayIconSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewStatus": + v0, ok := signal.Body[0].(string) + if !ok { + return nil, fmt.Errorf("prop .Status is %T, not string", signal.Body[0]) + } + return &StatusNotifierItem_NewStatusSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewStatusSignalBody{ + Status: v0, + }, + }, nil + case InterfaceStatusNotifierItem + "." + "NewIconThemePath": + v0, ok := signal.Body[0].(string) + if !ok { + return nil, fmt.Errorf("prop .IconThemePath is %T, not string", signal.Body[0]) + } + return &StatusNotifierItem_NewIconThemePathSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewIconThemePathSignalBody{ + IconThemePath: v0, + }, + }, nil + case InterfaceStatusNotifierItem + "." + "NewMenu": + return &StatusNotifierItem_NewMenuSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewMenuSignalBody{}, + }, nil + default: + return nil, ErrUnknownSignal + } +} + +// AddMatchSignal registers a match rule for the given signal, +// opts are appended to the automatically generated signal's rules. +func AddMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.AddMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// RemoveMatchSignal unregisters the previously registered subscription. +func RemoveMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.RemoveMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// Interface name constants. +const ( + InterfaceStatusNotifierItem = "org.kde.StatusNotifierItem" +) + +// StatusNotifierItemer is org.kde.StatusNotifierItem interface. +type StatusNotifierItemer interface { + // ContextMenu is org.kde.StatusNotifierItem.ContextMenu method. + ContextMenu(x int32, y int32) (err *dbus.Error) + // Activate is org.kde.StatusNotifierItem.Activate method. + Activate(x int32, y int32) (err *dbus.Error) + // SecondaryActivate is org.kde.StatusNotifierItem.SecondaryActivate method. + SecondaryActivate(x int32, y int32) (err *dbus.Error) + // Scroll is org.kde.StatusNotifierItem.Scroll method. + Scroll(delta int32, orientation string) (err *dbus.Error) +} + +// ExportStatusNotifierItem exports the given object that implements org.kde.StatusNotifierItem on the bus. +func ExportStatusNotifierItem(conn *dbus.Conn, path dbus.ObjectPath, v StatusNotifierItemer) error { + return conn.ExportSubtreeMethodTable(map[string]interface{}{ + "ContextMenu": v.ContextMenu, + "Activate": v.Activate, + "SecondaryActivate": v.SecondaryActivate, + "Scroll": v.Scroll, + }, path, InterfaceStatusNotifierItem) +} + +// UnexportStatusNotifierItem unexports org.kde.StatusNotifierItem interface on the named path. +func UnexportStatusNotifierItem(conn *dbus.Conn, path dbus.ObjectPath) error { + return conn.Export(nil, path, InterfaceStatusNotifierItem) +} + +// UnimplementedStatusNotifierItem can be embedded to have forward compatible server implementations. +type UnimplementedStatusNotifierItem struct{} + +func (*UnimplementedStatusNotifierItem) iface() string { + return InterfaceStatusNotifierItem +} + +func (*UnimplementedStatusNotifierItem) ContextMenu(x int32, y int32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedStatusNotifierItem) Activate(x int32, y int32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedStatusNotifierItem) SecondaryActivate(x int32, y int32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedStatusNotifierItem) Scroll(delta int32, orientation string) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +// NewStatusNotifierItem creates and allocates org.kde.StatusNotifierItem. +func NewStatusNotifierItem(object dbus.BusObject) *StatusNotifierItem { + return &StatusNotifierItem{object} +} + +// StatusNotifierItem implements org.kde.StatusNotifierItem D-Bus interface. +type StatusNotifierItem struct { + object dbus.BusObject +} + +// ContextMenu calls org.kde.StatusNotifierItem.ContextMenu method. +func (o *StatusNotifierItem) ContextMenu(ctx context.Context, x int32, y int32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".ContextMenu", 0, x, y).Store() + return +} + +// Activate calls org.kde.StatusNotifierItem.Activate method. +func (o *StatusNotifierItem) Activate(ctx context.Context, x int32, y int32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".Activate", 0, x, y).Store() + return +} + +// SecondaryActivate calls org.kde.StatusNotifierItem.SecondaryActivate method. +func (o *StatusNotifierItem) SecondaryActivate(ctx context.Context, x int32, y int32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".SecondaryActivate", 0, x, y).Store() + return +} + +// Scroll calls org.kde.StatusNotifierItem.Scroll method. +func (o *StatusNotifierItem) Scroll(ctx context.Context, delta int32, orientation string) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".Scroll", 0, delta, orientation).Store() + return +} + +// GetCategory gets org.kde.StatusNotifierItem.Category property. +func (o *StatusNotifierItem) GetCategory(ctx context.Context) (category string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Category").Store(&category) + return +} + +// GetId gets org.kde.StatusNotifierItem.Id property. +func (o *StatusNotifierItem) GetId(ctx context.Context) (id string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Id").Store(&id) + return +} + +// GetTitle gets org.kde.StatusNotifierItem.Title property. +func (o *StatusNotifierItem) GetTitle(ctx context.Context) (title string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Title").Store(&title) + return +} + +// GetStatus gets org.kde.StatusNotifierItem.Status property. +func (o *StatusNotifierItem) GetStatus(ctx context.Context) (status string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Status").Store(&status) + return +} + +// GetWindowId gets org.kde.StatusNotifierItem.WindowId property. +func (o *StatusNotifierItem) GetWindowId(ctx context.Context) (windowId int32, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "WindowId").Store(&windowId) + return +} + +// GetIconThemePath gets org.kde.StatusNotifierItem.IconThemePath property. +func (o *StatusNotifierItem) GetIconThemePath(ctx context.Context) (iconThemePath string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "IconThemePath").Store(&iconThemePath) + return +} + +// GetMenu gets org.kde.StatusNotifierItem.Menu property. +func (o *StatusNotifierItem) GetMenu(ctx context.Context) (menu dbus.ObjectPath, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Menu").Store(&menu) + return +} + +// GetItemIsMenu gets org.kde.StatusNotifierItem.ItemIsMenu property. +func (o *StatusNotifierItem) GetItemIsMenu(ctx context.Context) (itemIsMenu bool, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "ItemIsMenu").Store(&itemIsMenu) + return +} + +// GetIconName gets org.kde.StatusNotifierItem.IconName property. +func (o *StatusNotifierItem) GetIconName(ctx context.Context) (iconName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "IconName").Store(&iconName) + return +} + +// GetIconPixmap gets org.kde.StatusNotifierItem.IconPixmap property. +// +// Annotations: +// +// @org.qtproject.QtDBus.QtTypeName = KDbusImageVector +func (o *StatusNotifierItem) GetIconPixmap(ctx context.Context) (iconPixmap []struct { + V0 int32 + V1 int32 + V2 []byte +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "IconPixmap").Store(&iconPixmap) + return +} + +// GetOverlayIconName gets org.kde.StatusNotifierItem.OverlayIconName property. +func (o *StatusNotifierItem) GetOverlayIconName(ctx context.Context) (overlayIconName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "OverlayIconName").Store(&overlayIconName) + return +} + +// GetOverlayIconPixmap gets org.kde.StatusNotifierItem.OverlayIconPixmap property. +// +// Annotations: +// +// @org.qtproject.QtDBus.QtTypeName = KDbusImageVector +func (o *StatusNotifierItem) GetOverlayIconPixmap(ctx context.Context) (overlayIconPixmap []struct { + V0 int32 + V1 int32 + V2 []byte +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "OverlayIconPixmap").Store(&overlayIconPixmap) + return +} + +// GetAttentionIconName gets org.kde.StatusNotifierItem.AttentionIconName property. +func (o *StatusNotifierItem) GetAttentionIconName(ctx context.Context) (attentionIconName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "AttentionIconName").Store(&attentionIconName) + return +} + +// GetAttentionIconPixmap gets org.kde.StatusNotifierItem.AttentionIconPixmap property. +// +// Annotations: +// +// @org.qtproject.QtDBus.QtTypeName = KDbusImageVector +func (o *StatusNotifierItem) GetAttentionIconPixmap(ctx context.Context) (attentionIconPixmap []struct { + V0 int32 + V1 int32 + V2 []byte +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "AttentionIconPixmap").Store(&attentionIconPixmap) + return +} + +// GetAttentionMovieName gets org.kde.StatusNotifierItem.AttentionMovieName property. +func (o *StatusNotifierItem) GetAttentionMovieName(ctx context.Context) (attentionMovieName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "AttentionMovieName").Store(&attentionMovieName) + return +} + +// GetToolTip gets org.kde.StatusNotifierItem.ToolTip property. +// +// Annotations: +// +// @org.qtproject.QtDBus.QtTypeName = KDbusToolTipStruct +func (o *StatusNotifierItem) GetToolTip(ctx context.Context) (toolTip struct { + V0 string + V1 []struct { + V0 int32 + V1 int32 + V2 []byte + } + V2 string + V3 string +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "ToolTip").Store(&toolTip) + return +} + +// StatusNotifierItem_NewTitleSignal represents org.kde.StatusNotifierItem.NewTitle signal. +type StatusNotifierItem_NewTitleSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewTitleSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewTitleSignal) Name() string { + return "NewTitle" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewTitleSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewTitleSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewTitleSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewTitleSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewTitleSignalBody is body container. +type StatusNotifierItem_NewTitleSignalBody struct { +} + +// StatusNotifierItem_NewIconSignal represents org.kde.StatusNotifierItem.NewIcon signal. +type StatusNotifierItem_NewIconSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewIconSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewIconSignal) Name() string { + return "NewIcon" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewIconSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewIconSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewIconSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewIconSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewIconSignalBody is body container. +type StatusNotifierItem_NewIconSignalBody struct { +} + +// StatusNotifierItem_NewAttentionIconSignal represents org.kde.StatusNotifierItem.NewAttentionIcon signal. +type StatusNotifierItem_NewAttentionIconSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewAttentionIconSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewAttentionIconSignal) Name() string { + return "NewAttentionIcon" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewAttentionIconSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewAttentionIconSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewAttentionIconSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewAttentionIconSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewAttentionIconSignalBody is body container. +type StatusNotifierItem_NewAttentionIconSignalBody struct { +} + +// StatusNotifierItem_NewOverlayIconSignal represents org.kde.StatusNotifierItem.NewOverlayIcon signal. +type StatusNotifierItem_NewOverlayIconSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewOverlayIconSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewOverlayIconSignal) Name() string { + return "NewOverlayIcon" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewOverlayIconSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewOverlayIconSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewOverlayIconSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewOverlayIconSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewOverlayIconSignalBody is body container. +type StatusNotifierItem_NewOverlayIconSignalBody struct { +} + +// StatusNotifierItem_NewStatusSignal represents org.kde.StatusNotifierItem.NewStatus signal. +type StatusNotifierItem_NewStatusSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewStatusSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewStatusSignal) Name() string { + return "NewStatus" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewStatusSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewStatusSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewStatusSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewStatusSignal) values() []interface{} { + return []interface{}{s.Body.Status} +} + +// StatusNotifierItem_NewStatusSignalBody is body container. +type StatusNotifierItem_NewStatusSignalBody struct { + Status string +} + +// StatusNotifierItem_NewIconThemePathSignal represents org.kde.StatusNotifierItem.NewIconThemePath signal. +type StatusNotifierItem_NewIconThemePathSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewIconThemePathSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewIconThemePathSignal) Name() string { + return "NewIconThemePath" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewIconThemePathSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewIconThemePathSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewIconThemePathSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewIconThemePathSignal) values() []interface{} { + return []interface{}{s.Body.IconThemePath} +} + +// StatusNotifierItem_NewIconThemePathSignalBody is body container. +type StatusNotifierItem_NewIconThemePathSignalBody struct { + IconThemePath string +} + +// StatusNotifierItem_NewMenuSignal represents org.kde.StatusNotifierItem.NewMenu signal. +type StatusNotifierItem_NewMenuSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewMenuSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewMenuSignal) Name() string { + return "NewMenu" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewMenuSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewMenuSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewMenuSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewMenuSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewMenuSignalBody is body container. +type StatusNotifierItem_NewMenuSignalBody struct { +} diff --git a/v3/internal/debug/debug.go b/v3/internal/debug/debug.go new file mode 100644 index 000000000..394688ce7 --- /dev/null +++ b/v3/internal/debug/debug.go @@ -0,0 +1,42 @@ +package debug + +import ( + "os" + "path/filepath" + "runtime" +) + +var LocalModulePath = "" + +func init() { + // Check if .git exists in the relative directory from here: ../../.. + // If it does, we are in a local build + gitDir := RelativePath("..", "..", "..", ".git") + if _, err := os.Stat(gitDir); err == nil { + modulePath := RelativePath("..", "..", "..") + LocalModulePath, _ = filepath.Abs(modulePath) + } +} + +// RelativePath returns a qualified path created by joining the +// directory of the calling file and the given relative path. +func RelativePath(relativepath string, optionalpaths ...string) string { + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + + // If we have optional paths, join them to the relativepath + if len(optionalpaths) > 0 { + paths := []string{relativepath} + paths = append(paths, optionalpaths...) + relativepath = filepath.Join(paths...) + } + result, err := filepath.Abs(filepath.Join(localDir, relativepath)) + if err != nil { + // I'm allowing this for 1 reason only: It's fatal if the path + // supplied is wrong as it's only used internally in Wails. If we get + // that path wrong, we should know about it immediately. The other reason is + // that it cuts down a ton of unnecassary error handling. + panic(err) + } + return result +} diff --git a/v3/internal/defaults/defaults.go b/v3/internal/defaults/defaults.go new file mode 100644 index 000000000..69e99f3e6 --- /dev/null +++ b/v3/internal/defaults/defaults.go @@ -0,0 +1,219 @@ +// Package defaults provides functionality for loading and saving global default settings +// for Wails projects. Settings are stored in ~/.config/wails/defaults.yaml +package defaults + +import ( + "os" + "path/filepath" + "time" + + "gopkg.in/yaml.v3" +) + +// GlobalDefaults represents the user's default project settings +// These are stored in ~/.config/wails/defaults.yaml and used when creating new projects +type GlobalDefaults struct { + // Author information + Author AuthorDefaults `json:"author" yaml:"author"` + + // Default project settings + Project ProjectDefaults `json:"project" yaml:"project"` +} + +// AuthorDefaults contains the author's information +type AuthorDefaults struct { + Name string `json:"name" yaml:"name"` + Company string `json:"company" yaml:"company"` +} + +// ProjectDefaults contains default project settings +type ProjectDefaults struct { + // ProductIdentifierPrefix is the prefix for app identifiers (e.g., "com.mycompany") + ProductIdentifierPrefix string `json:"productIdentifierPrefix" yaml:"productIdentifierPrefix"` + + // DefaultTemplate is the default frontend template to use + DefaultTemplate string `json:"defaultTemplate" yaml:"defaultTemplate"` + + // Copyright template - can include {year} and {company} placeholders + CopyrightTemplate string `json:"copyrightTemplate" yaml:"copyrightTemplate"` + + // Description template for new projects - can include {name} placeholder + DescriptionTemplate string `json:"descriptionTemplate" yaml:"descriptionTemplate"` + + // Default product version for new projects + DefaultVersion string `json:"defaultVersion" yaml:"defaultVersion"` +} + +// Default returns sensible defaults for first-time users +func Default() GlobalDefaults { + return GlobalDefaults{ + Author: AuthorDefaults{ + Name: "", + Company: "", + }, + Project: ProjectDefaults{ + ProductIdentifierPrefix: "com.example", + DefaultTemplate: "vanilla", + CopyrightTemplate: "© {year}, {company}", + DescriptionTemplate: "A {name} application", + DefaultVersion: "0.1.0", + }, + } +} + +// GetConfigDir returns the path to the Wails config directory +func GetConfigDir() (string, error) { + // Use XDG_CONFIG_HOME if set, otherwise use ~/.config + configHome := os.Getenv("XDG_CONFIG_HOME") + if configHome == "" { + homeDir, err := os.UserHomeDir() + if err != nil { + return "", err + } + configHome = filepath.Join(homeDir, ".config") + } + return filepath.Join(configHome, "wails"), nil +} + +// GetDefaultsPath returns the path to the defaults.yaml file +func GetDefaultsPath() (string, error) { + configDir, err := GetConfigDir() + if err != nil { + return "", err + } + return filepath.Join(configDir, "defaults.yaml"), nil +} + +// Load loads the global defaults from the config file +// Returns default values if the file doesn't exist +func Load() (GlobalDefaults, error) { + defaults := Default() + + path, err := GetDefaultsPath() + if err != nil { + return defaults, err + } + + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return defaults, nil + } + return defaults, err + } + + if err := yaml.Unmarshal(data, &defaults); err != nil { + return Default(), err + } + + return defaults, nil +} + +// Save saves the global defaults to the config file +func Save(defaults GlobalDefaults) error { + path, err := GetDefaultsPath() + if err != nil { + return err + } + + // Ensure the config directory exists + configDir := filepath.Dir(path) + if err := os.MkdirAll(configDir, 0755); err != nil { + return err + } + + data, err := yaml.Marshal(&defaults) + if err != nil { + return err + } + + return os.WriteFile(path, data, 0644) +} + +// GenerateCopyright generates a copyright string from the template +func (d *GlobalDefaults) GenerateCopyright() string { + template := d.Project.CopyrightTemplate + if template == "" { + template = "© {year}, {company}" + } + + year := time.Now().Format("2006") + company := d.Author.Company + if company == "" { + company = "My Company" + } + + result := template + result = replaceAll(result, "{year}", year) + result = replaceAll(result, "{company}", company) + return result +} + +// GenerateProductIdentifier generates a product identifier from prefix and project name +func (d *GlobalDefaults) GenerateProductIdentifier(projectName string) string { + prefix := d.Project.ProductIdentifierPrefix + if prefix == "" { + prefix = "com.example" + } + return prefix + "." + sanitizeIdentifier(projectName) +} + +// GenerateDescription generates a description string from the template +func (d *GlobalDefaults) GenerateDescription(projectName string) string { + template := d.Project.DescriptionTemplate + if template == "" { + template = "A {name} application" + } + return replaceAll(template, "{name}", projectName) +} + +// GetDefaultVersion returns the default version or the fallback +func (d *GlobalDefaults) GetDefaultVersion() string { + if d.Project.DefaultVersion != "" { + return d.Project.DefaultVersion + } + return "0.1.0" +} + +// replaceAll replaces all occurrences of old with new in s +func replaceAll(s, old, new string) string { + result := s + for { + newResult := replaceOnce(result, old, new) + if newResult == result { + break + } + result = newResult + } + return result +} + +func replaceOnce(s, old, new string) string { + for i := 0; i <= len(s)-len(old); i++ { + if s[i:i+len(old)] == old { + return s[:i] + new + s[i+len(old):] + } + } + return s +} + +// sanitizeIdentifier creates a valid identifier from a project name +func sanitizeIdentifier(name string) string { + var result []byte + for i := 0; i < len(name); i++ { + c := name[i] + if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') { + result = append(result, c) + } + } + if len(result) == 0 { + return "app" + } + // Lowercase the result + for i := range result { + if result[i] >= 'A' && result[i] <= 'Z' { + result[i] = result[i] + 32 + } + } + return string(result) +} diff --git a/v3/internal/doctor/diagnostics.go b/v3/internal/doctor/diagnostics.go new file mode 100644 index 000000000..bc6b36670 --- /dev/null +++ b/v3/internal/doctor/diagnostics.go @@ -0,0 +1,93 @@ +package doctor + +import ( + "fmt" + "path/filepath" + "runtime" + "strings" +) + +// DiagnosticTest represents a single diagnostic test to be run +type DiagnosticTest struct { + Name string + Run func() (bool, string) // Returns success and error message if failed + HelpURL string +} + +// DiagnosticResult represents the result of a diagnostic test +type DiagnosticResult struct { + TestName string + ErrorMsg string + HelpURL string +} + +// platformDiagnostics maps platform names to their diagnostic tests +var platformDiagnostics = map[string][]DiagnosticTest{ + // Tests that run on all platforms + "all": { + { + Name: "Check Go installation", + Run: func() (bool, string) { + // This is just an example test for all platforms + if runtime.Version() == "" { + return false, "Go installation not found" + } + return true, "" + }, + HelpURL: "/getting-started/installation/", + }, + }, + // Platform specific tests + "darwin": { + { + Name: "Check for .syso file", + Run: func() (bool, string) { + // Check for .syso files in current directory + matches, err := filepath.Glob("*.syso") + if err != nil { + return false, "Error checking for .syso files" + } + if len(matches) > 0 { + return false, fmt.Sprintf("Found .syso file(s): %v. These may cause issues when building on macOS", strings.Join(matches, ", ")) + } + return true, "" + }, + HelpURL: "/troubleshooting/mac-syso", + }, + }, +} + +// RunDiagnostics executes all diagnostic tests for the current platform +func RunDiagnostics() []DiagnosticResult { + var results []DiagnosticResult + + // Run tests that apply to all platforms + if tests, exists := platformDiagnostics["all"]; exists { + for _, test := range tests { + success, errMsg := test.Run() + if !success { + results = append(results, DiagnosticResult{ + TestName: test.Name, + ErrorMsg: errMsg, + HelpURL: test.HelpURL, + }) + } + } + } + + // Run platform-specific tests + if tests, exists := platformDiagnostics[runtime.GOOS]; exists { + for _, test := range tests { + success, errMsg := test.Run() + if !success { + results = append(results, DiagnosticResult{ + TestName: test.Name, + ErrorMsg: errMsg, + HelpURL: test.HelpURL, + }) + } + } + } + + return results +} diff --git a/v3/internal/doctor/doctor.go b/v3/internal/doctor/doctor.go new file mode 100644 index 000000000..a14fcf3b3 --- /dev/null +++ b/v3/internal/doctor/doctor.go @@ -0,0 +1,274 @@ +package doctor + +import ( + "bytes" + "fmt" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "runtime/debug" + "slices" + "strconv" + "strings" + + "github.com/wailsapp/wails/v3/internal/term" + + "github.com/wailsapp/wails/v3/internal/buildinfo" + + "github.com/go-git/go-git/v5" + "github.com/jaypipes/ghw" + "github.com/pterm/pterm" + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/internal/operatingsystem" + "github.com/wailsapp/wails/v3/internal/version" +) + +func Run() (err error) { + + get, err := buildinfo.Get() + if err != nil { + return err + } + _ = get + + term.Header("Wails Doctor") + + spinner, _ := pterm.DefaultSpinner.WithRemoveWhenDone().Start("Scanning system - Please wait (this may take a long time)...") + + defer func() { + if err != nil { + spinner.Fail() + } + }() + + /** Build **/ + + // BuildSettings contains the build settings for the application + var BuildSettings map[string]string + + // BuildInfo contains the build info for the application + var BuildInfo *debug.BuildInfo + + var ok bool + BuildInfo, ok = debug.ReadBuildInfo() + if !ok { + return fmt.Errorf("could not read build info from binary") + } + BuildSettings = lo.Associate(BuildInfo.Settings, func(setting debug.BuildSetting) (string, string) { + return setting.Key, setting.Value + }) + + /** Operating System **/ + + // Get system info + info, err := operatingsystem.Info() + if err != nil { + term.Error("Failed to get system information") + return err + } + + /** Wails **/ + + wailsPackage, _ := lo.Find(BuildInfo.Deps, func(dep *debug.Module) bool { + return dep.Path == "github.com/wailsapp/wails/v3" + }) + + wailsVersion := strings.TrimSpace(version.String()) + if wailsPackage != nil && wailsPackage.Replace != nil { + wailsVersion = "(local) => " + filepath.ToSlash(wailsPackage.Replace.Path) + // Get the latest commit hash + repo, err := git.PlainOpen(filepath.Join(wailsPackage.Replace.Path, "..")) + if err == nil { + head, err := repo.Head() + if err == nil { + wailsVersion += " (" + head.Hash().String()[:8] + ")" + } + } + } + + platformExtras, ok := getInfo() + + dependencies := make(map[string]string) + checkPlatformDependencies(dependencies, &ok) + + spinner.Success() + + /** Output **/ + + term.Section("System") + + systemTabledata := pterm.TableData{ + {pterm.Sprint("Name"), info.Name}, + {pterm.Sprint("Version"), info.Version}, + {pterm.Sprint("ID"), info.ID}, + {pterm.Sprint("Branding"), info.Branding}, + + {pterm.Sprint("Platform"), runtime.GOOS}, + {pterm.Sprint("Architecture"), runtime.GOARCH}, + } + + mapKeys := lo.Keys(platformExtras) + slices.Sort(mapKeys) + for _, key := range mapKeys { + systemTabledata = append(systemTabledata, []string{key, platformExtras[key]}) + } + + // Probe CPU + cpus, _ := ghw.CPU() + if cpus != nil { + prefix := "CPU" + for idx, cpu := range cpus.Processors { + if len(cpus.Processors) > 1 { + prefix = "CPU " + strconv.Itoa(idx+1) + } + systemTabledata = append(systemTabledata, []string{prefix, cpu.Model}) + } + } else { + systemTabledata = append(systemTabledata, []string{"CPU", "Unknown"}) + } + + // Probe GPU + gpu, _ := ghw.GPU(ghw.WithDisableWarnings()) + if gpu != nil { + for idx, card := range gpu.GraphicsCards { + details := "Unknown" + prefix := "GPU " + strconv.Itoa(idx+1) + if card.DeviceInfo != nil { + details = fmt.Sprintf("%s (%s) - Driver: %s ", card.DeviceInfo.Product.Name, card.DeviceInfo.Vendor.Name, card.DeviceInfo.Driver) + } + systemTabledata = append(systemTabledata, []string{prefix, details}) + } + } else { + if runtime.GOOS == "darwin" { + var numCoresValue string + cmd := exec.Command("sh", "-c", "ioreg -l | grep gpu-core-count") + output, err := cmd.Output() + if err == nil { + // Look for an `=` sign, optional spaces and then an integer + re := regexp.MustCompile(`= *(\d+)`) + matches := re.FindAllStringSubmatch(string(output), -1) + numCoresValue = "Unknown" + if len(matches) > 0 { + numCoresValue = matches[0][1] + } + + } + + // Run `system_profiler SPDisplaysDataType | grep Metal` + var metalSupport string + cmd = exec.Command("sh", "-c", "system_profiler SPDisplaysDataType | grep Metal") + output, err = cmd.Output() + if err == nil { + metalSupport = ", " + strings.TrimSpace(string(output)) + } + systemTabledata = append(systemTabledata, []string{"GPU", numCoresValue + " cores" + metalSupport}) + + } else { + systemTabledata = append(systemTabledata, []string{"GPU", "Unknown"}) + } + } + + memory, _ := ghw.Memory() + var memoryText = "Unknown" + if memory != nil { + memoryText = strconv.Itoa(int(memory.TotalPhysicalBytes/1024/1024/1024)) + "GB" + } else { + if runtime.GOOS == "darwin" { + cmd := exec.Command("sh", "-c", "system_profiler SPHardwareDataType | grep 'Memory'") + output, err := cmd.Output() + if err == nil { + output = bytes.Replace(output, []byte("Memory: "), []byte(""), 1) + memoryText = strings.TrimSpace(string(output)) + } + } + } + systemTabledata = append(systemTabledata, []string{"Memory", memoryText}) + + err = pterm.DefaultTable.WithBoxed().WithData(systemTabledata).Render() + if err != nil { + return err + } + + // Build Environment + + term.Section("Build Environment") + + tableData := pterm.TableData{ + {"Wails CLI", wailsVersion}, + {"Go Version", runtime.Version()}, + } + + if buildInfo, _ := debug.ReadBuildInfo(); buildInfo != nil { + buildSettingToName := map[string]string{ + "vcs.revision": "Revision", + "vcs.modified": "Modified", + } + for _, buildSetting := range buildInfo.Settings { + name := buildSettingToName[buildSetting.Key] + if name == "" { + continue + } + tableData = append(tableData, []string{name, buildSetting.Value}) + } + } + + mapKeys = lo.Keys(BuildSettings) + slices.Sort(mapKeys) + for _, key := range mapKeys { + tableData = append(tableData, []string{key, BuildSettings[key]}) + } + + err = pterm.DefaultTable.WithBoxed(true).WithData(tableData).Render() + if err != nil { + return err + } + + // Dependencies + term.Section("Dependencies") + dependenciesBox := pterm.DefaultBox.WithTitleBottomCenter().WithTitle(pterm.Gray("*") + " - Optional Dependency") + dependencyTableData := pterm.TableData{} + if len(dependencies) == 0 { + pterm.Info.Println("No dependencies found") + } else { + var optionals pterm.TableData + mapKeys = lo.Keys(dependencies) + for _, key := range mapKeys { + if strings.HasPrefix(dependencies[key], "*") { + optionals = append(optionals, []string{key, dependencies[key]}) + } else { + dependencyTableData = append(dependencyTableData, []string{key, dependencies[key]}) + } + } + dependencyTableData = append(dependencyTableData, optionals...) + dependenciesTableString, _ := pterm.DefaultTable.WithData(dependencyTableData).Srender() + dependenciesBox.Println(dependenciesTableString) + } + + // Run diagnostics after system info + term.Section("Checking for issues") + + diagnosticResults := RunDiagnostics() + if len(diagnosticResults) == 0 { + pterm.Success.Println("No issues found") + } else { + pterm.Warning.Println("Found potential issues:") + for _, result := range diagnosticResults { + pterm.Printf("• %s: %s\n", result.TestName, result.ErrorMsg) + url := result.HelpURL + if strings.HasPrefix(url, "/") { + url = "https://v3.wails.io" + url + } + pterm.Printf(" For more information: %s\n", term.Hyperlink(url, url)) + } + } + + term.Section("Diagnosis") + if !ok { + term.Warning("There are some items above that need addressing!") + } else { + term.Success("Your system is ready for Wails development!") + } + + return nil +} diff --git a/v3/internal/doctor/doctor_common.go b/v3/internal/doctor/doctor_common.go new file mode 100644 index 000000000..dab765586 --- /dev/null +++ b/v3/internal/doctor/doctor_common.go @@ -0,0 +1,61 @@ +package doctor + +import ( + "bytes" + "os/exec" + "strconv" + "strings" +) + +func checkCommonDependencies(result map[string]string, ok *bool) { + // Check for npm + npmVersion := []byte("Not Installed. Requires npm >= 7.0.0") + npmVersion, err := exec.Command("npm", "-v").Output() + if err != nil { + *ok = false + } else { + npmVersion = bytes.TrimSpace(npmVersion) + // Check that it's at least version 7 by converting first byte to int and checking if it's >= 7 + // Parse the semver string + semver := strings.Split(string(npmVersion), ".") + if len(semver) > 0 { + major, _ := strconv.Atoi(semver[0]) + if major < 7 { + *ok = false + npmVersion = append(npmVersion, []byte(". Installed, but requires npm >= 7.0.0")...) + } else { + *ok = true + } + } + } + result["npm"] = string(npmVersion) + + // Check for Docker (optional - used for macOS cross-compilation from Linux) + checkDocker(result) +} + +func checkDocker(result map[string]string) { + dockerVersion, err := exec.Command("docker", "--version").Output() + if err != nil { + result["docker"] = "*Not installed (optional - for cross-compilation)" + return + } + + // Check if Docker daemon is running + _, err = exec.Command("docker", "info").Output() + if err != nil { + version := strings.TrimSpace(string(dockerVersion)) + result["docker"] = "*" + version + " (daemon not running)" + return + } + + version := strings.TrimSpace(string(dockerVersion)) + + // Check for the unified cross-compilation image + imageCheck, _ := exec.Command("docker", "image", "inspect", "wails-cross").Output() + if len(imageCheck) == 0 { + result["docker"] = "*" + version + " (wails-cross image not built - run: wails3 task setup:docker)" + } else { + result["docker"] = "*" + version + " (cross-compilation ready)" + } +} diff --git a/v3/internal/doctor/doctor_darwin.go b/v3/internal/doctor/doctor_darwin.go new file mode 100644 index 000000000..9ffb368f1 --- /dev/null +++ b/v3/internal/doctor/doctor_darwin.go @@ -0,0 +1,63 @@ +//go:build darwin + +package doctor + +import ( + "bytes" + "github.com/samber/lo" + "os/exec" + "strings" + "syscall" +) + +func getSysctl(name string) string { + value, err := syscall.Sysctl(name) + if err != nil { + return "unknown" + } + return value +} + +func getInfo() (map[string]string, bool) { + result := make(map[string]string) + ok := true + + // Determine if the app is running on Apple Silicon + // Credit: https://www.yellowduck.be/posts/detecting-apple-silicon-via-go/ + appleSilicon := "unknown" + r, err := syscall.Sysctl("sysctl.proc_translated") + if err == nil { + appleSilicon = lo.Ternary(r == "\x00\x00\x00" || r == "\x01\x00\x00", "true", "false") + } + result["Apple Silicon"] = appleSilicon + result["CPU"] = getSysctl("machdep.cpu.brand_string") + + return result, ok +} + +func checkPlatformDependencies(result map[string]string, ok *bool) { + + // Check for xcode command line tools + output, err := exec.Command("xcode-select", "-v").Output() + cliToolsVersion := "N/A. Install by running: `xcode-select --install`" + if err != nil { + *ok = false + } else { + cliToolsVersion = strings.TrimPrefix(string(output), "xcode-select version ") + cliToolsVersion = strings.TrimSpace(cliToolsVersion) + cliToolsVersion = strings.TrimSuffix(cliToolsVersion, ".") + } + result["Xcode cli tools"] = cliToolsVersion + + checkCommonDependencies(result, ok) + + // Check for nsis + nsisVersion := []byte("Not Installed. Install with `brew install makensis`.") + output, err = exec.Command("makensis", "-VERSION").Output() + if err == nil && output != nil { + nsisVersion = output + } + nsisVersion = bytes.TrimSpace(nsisVersion) + + result["*NSIS"] = string(nsisVersion) +} diff --git a/v3/internal/doctor/doctor_linux.go b/v3/internal/doctor/doctor_linux.go new file mode 100644 index 000000000..4952cffab --- /dev/null +++ b/v3/internal/doctor/doctor_linux.go @@ -0,0 +1,87 @@ +//go:build linux + +package doctor + +import ( + "os" + "strings" + + "github.com/wailsapp/wails/v3/internal/doctor/packagemanager" + "github.com/wailsapp/wails/v3/internal/operatingsystem" +) + +func getInfo() (map[string]string, bool) { + result := make(map[string]string) + + // Check session type (X11/Wayland) + result["XDG_SESSION_TYPE"] = getSessionType() + + // Check desktop environment + result["Desktop Environment"] = getDesktopEnvironment() + + // Check for NVIDIA driver + result["NVIDIA Driver"] = getNvidiaDriverInfo() + + return result, true +} + +func getSessionType() string { + sessionType := os.Getenv("XDG_SESSION_TYPE") + if sessionType == "" { + return "unset" + } + return sessionType +} + +func getDesktopEnvironment() string { + de := os.Getenv("XDG_CURRENT_DESKTOP") + if de == "" { + return "unset" + } + return de +} + +func getNvidiaDriverInfo() string { + version, err := os.ReadFile("/sys/module/nvidia/version") + if err != nil { + return "N/A" + } + + versionStr := strings.TrimSpace(string(version)) + + srcVersion, err := os.ReadFile("/sys/module/nvidia/srcversion") + if err != nil { + return versionStr + } + + return versionStr + " (" + strings.TrimSpace(string(srcVersion)) + ")" +} + +func checkPlatformDependencies(result map[string]string, ok *bool) { + info, _ := operatingsystem.Info() + + pm := packagemanager.Find(info.ID) + deps, _ := packagemanager.Dependencies(pm) + for _, dep := range deps { + var status string + + switch true { + case !dep.Installed: + if dep.Optional { + status = "[Optional] " + } else { + *ok = false + } + status += "not installed." + if dep.InstallCommand != "" { + status += " Install with: " + dep.InstallCommand + } + case dep.Version != "": + status = dep.Version + } + + result[dep.Name] = status + } + + checkCommonDependencies(result, ok) +} diff --git a/v3/internal/doctor/doctor_test.go b/v3/internal/doctor/doctor_test.go new file mode 100644 index 000000000..98f4c3f8a --- /dev/null +++ b/v3/internal/doctor/doctor_test.go @@ -0,0 +1,10 @@ +package doctor + +import "testing" + +func TestRun(t *testing.T) { + err := Run() + if err != nil { + t.Errorf("TestRun failed: %v", err) + } +} diff --git a/v3/internal/doctor/doctor_windows.go b/v3/internal/doctor/doctor_windows.go new file mode 100644 index 000000000..2ae1dd2c8 --- /dev/null +++ b/v3/internal/doctor/doctor_windows.go @@ -0,0 +1,86 @@ +//go:build windows + +package doctor + +import ( + "os/exec" + "strings" + + "github.com/samber/lo" + "github.com/wailsapp/go-webview2/webviewloader" +) + +func getInfo() (map[string]string, bool) { + ok := true + result := make(map[string]string) + result["Go WebView2Loader"] = lo.Ternary(webviewloader.UsingGoWebview2Loader, "true", "false") + webviewVersion, err := webviewloader.GetAvailableCoreWebView2BrowserVersionString("") + if err != nil { + ok = false + webviewVersion = "Error:" + err.Error() + } + result["WebView2 Version"] = webviewVersion + return result, ok +} + +func getNSISVersion() string { + // Execute nsis + output, err := exec.Command("makensis", "-VERSION").Output() + if err != nil { + return "Not Installed" + } + return string(output) +} + +func getMakeAppxVersion() string { + // Check if MakeAppx.exe is available (part of Windows SDK) + _, err := exec.LookPath("MakeAppx.exe") + if err != nil { + return "Not Installed" + } + return "Installed" +} + +func getMSIXPackagingToolVersion() string { + // Check if MSIX Packaging Tool is installed + // Use PowerShell to check if the app is installed from Microsoft Store + cmd := exec.Command("powershell", "-Command", "Get-AppxPackage -Name Microsoft.MSIXPackagingTool") + output, err := cmd.Output() + if err != nil || len(output) == 0 || !strings.Contains(string(output), "Microsoft.MSIXPackagingTool") { + return "Not Installed" + } + + if strings.Contains(string(output), "Version") { + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.Contains(line, "Version") { + parts := strings.Split(line, ":") + if len(parts) > 1 { + return strings.TrimSpace(parts[1]) + } + } + } + } + + return "Installed (Version Unknown)" +} + +func getSignToolVersion() string { + // Check if signtool.exe is available (part of Windows SDK) + _, err := exec.LookPath("signtool.exe") + if err != nil { + return "Not Installed" + } + return "Installed" +} + +func checkPlatformDependencies(result map[string]string, ok *bool) { + checkCommonDependencies(result, ok) + // add nsis + result["NSIS"] = getNSISVersion() + + // Add MSIX tooling checks + result["MakeAppx.exe (Windows SDK)"] = getMakeAppxVersion() + result["MSIX Packaging Tool"] = getMSIXPackagingToolVersion() + result["SignTool.exe (Windows SDK)"] = getSignToolVersion() +} diff --git a/v3/internal/doctor/packagemanager/apt.go b/v3/internal/doctor/packagemanager/apt.go new file mode 100644 index 000000000..257279081 --- /dev/null +++ b/v3/internal/doctor/packagemanager/apt.go @@ -0,0 +1,96 @@ +//go:build linux + +package packagemanager + +import ( + "regexp" + "strings" +) + +// Apt represents the Apt manager +type Apt struct { + name string + osid string +} + +// NewApt creates a new Apt instance +func NewApt(osid string) *Apt { + return &Apt{ + name: "apt", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (a *Apt) Packages() Packagemap { + return Packagemap{ + "gtk3": []*Package{ + {Name: "libgtk-3-dev", SystemPackage: true, Library: true}, + }, + "webkit2gtk": []*Package{ + {Name: "libwebkit2gtk-4.1-dev", SystemPackage: true, Library: true}, + {Name: "libwebkit2gtk-4.0-dev", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "build-essential", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "npm", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (a *Apt) Name() string { + return a.name +} + +func (a *Apt) listPackage(name string) (string, error) { + return execCmd("apt", "list", "-qq", name) +} + +// PackageInstalled tests if the given package name is installed +func (a *Apt) PackageInstalled(pkg *Package) (bool, error) { + if !pkg.SystemPackage { + if pkg.InstallCheck != nil { + return pkg.InstallCheck(), nil + } + return false, nil + } + output, err := a.listPackage(pkg.Name) + // apt list -qq returns "all" if you have packages installed globally and locally + return strings.Contains(output, "installed") || strings.Contains(output, " all"), err +} + +// PackageAvailable tests if the given package is available for installation +func (a *Apt) PackageAvailable(pkg *Package) (bool, error) { + if !pkg.SystemPackage { + return true, nil + } + output, err := a.listPackage(pkg.Name) + // We add a space to ensure we get a full match, not partial match + escapechars, _ := regexp.Compile(`\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])`) + escapechars.ReplaceAllString(output, "") + installed := strings.HasPrefix(output, pkg.Name) + a.getPackageVersion(pkg, output) + return installed, err +} + +// InstallCommand returns the package manager specific command to install a package +func (a *Apt) InstallCommand(pkg *Package) string { + if !pkg.SystemPackage { + return pkg.InstallCommand + } + return "sudo apt install " + pkg.Name +} + +func (a *Apt) getPackageVersion(pkg *Package, output string) { + splitOutput := strings.Split(output, " ") + if len(splitOutput) > 1 { + pkg.Version = splitOutput[1] + } +} diff --git a/v3/internal/doctor/packagemanager/dnf.go b/v3/internal/doctor/packagemanager/dnf.go new file mode 100644 index 000000000..5713680da --- /dev/null +++ b/v3/internal/doctor/packagemanager/dnf.go @@ -0,0 +1,124 @@ +//go:build linux + +package packagemanager + +import ( + "os/exec" + "strings" +) + +// Dnf represents the Dnf manager +type Dnf struct { + name string + osid string +} + +// NewDnf creates a new Dnf instance +func NewDnf(osid string) *Dnf { + return &Dnf{ + name: "dnf", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (y *Dnf) Packages() Packagemap { + return Packagemap{ + "gtk3": []*Package{ + {Name: "gtk3-devel", SystemPackage: true, Library: true}, + }, + "webkit2gtk": []*Package{ + {Name: "webkit2gtk4.1-devel", SystemPackage: true, Library: true}, + {Name: "webkit2gtk3-devel", SystemPackage: true, Library: true}, + {Name: "webkit2gtk4.0-devel", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "gcc-c++", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkgconf-pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "npm", SystemPackage: true}, + {Name: "nodejs-npm", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (y *Dnf) Name() string { + return y.name +} + +// PackageInstalled tests if the given package name is installed +func (y *Dnf) PackageInstalled(pkg *Package) (bool, error) { + if !pkg.SystemPackage { + if pkg.InstallCheck != nil { + return pkg.InstallCheck(), nil + } + return false, nil + } + stdout, err := execCmd("dnf", "-q", "list", "--installed", pkg.Name) + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + // Output format: "package-name.arch version repo" + // e.g., "webkit2gtk4.0-devel.x86_64 2.46.5-1.fc41 @updates" + splitoutput := strings.Split(stdout, "\n") + for _, line := range splitoutput { + if strings.HasPrefix(line, pkg.Name) { + fields := strings.Fields(line) + if len(fields) >= 2 { + pkg.Version = fields[1] + } + return true, nil + } + } + + return false, nil +} + +// PackageAvailable tests if the given package is available for installation +func (y *Dnf) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("dnf", "info", pkg.Name) + // We add a space to ensure we get a full match, not partial match + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + splitoutput := strings.Split(stdout, "\n") + for _, line := range splitoutput { + if strings.HasPrefix(line, "Version") { + splitline := strings.Split(line, ":") + pkg.Version = strings.TrimSpace(splitline[1]) + } + } + return true, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (y *Dnf) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand + } + return "sudo dnf install " + pkg.Name +} + +func (y *Dnf) getPackageVersion(pkg *Package, output string) { + splitOutput := strings.Split(output, " ") + if len(splitOutput) > 0 { + pkg.Version = splitOutput[1] + } +} diff --git a/v3/internal/doctor/packagemanager/emerge.go b/v3/internal/doctor/packagemanager/emerge.go new file mode 100644 index 000000000..301955051 --- /dev/null +++ b/v3/internal/doctor/packagemanager/emerge.go @@ -0,0 +1,113 @@ +//go:build linux + +package packagemanager + +import ( + "os/exec" + "regexp" + "strings" +) + +// Emerge represents the Emerge package manager +type Emerge struct { + name string + osid string +} + +// NewEmerge creates a new Emerge instance +func NewEmerge(osid string) *Emerge { + return &Emerge{ + name: "emerge", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (e *Emerge) Packages() Packagemap { + return Packagemap{ + "gtk3": []*Package{ + {Name: "x11-libs/gtk+", SystemPackage: true, Library: true}, + }, + "webkit2gtk": []*Package{ + {Name: "net-libs/webkit-gtk:6", SystemPackage: true, Library: true}, + {Name: "net-libs/webkit-gtk:4", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "sys-devel/gcc", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "dev-util/pkgconf", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "net-libs/nodejs", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (e *Emerge) Name() string { + return e.name +} + +// PackageInstalled tests if the given package name is installed +func (e *Emerge) PackageInstalled(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("emerge", "-s", pkg.Name+"$") + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + regex := `.*\*\s+` + regexp.QuoteMeta(pkg.Name) + `\n(?:\S|\s)+?Latest version installed: (.*)` + installedRegex := regexp.MustCompile(regex) + matches := installedRegex.FindStringSubmatch(stdout) + pkg.Version = "" + noOfMatches := len(matches) + installed := false + if noOfMatches > 1 && matches[1] != "[ Not Installed ]" { + installed = true + pkg.Version = strings.TrimSpace(matches[1]) + } + return installed, err +} + +// PackageAvailable tests if the given package is available for installation +func (e *Emerge) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("emerge", "-s", pkg.Name+"$") + // We add a space to ensure we get a full match, not partial match + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + installedRegex := regexp.MustCompile(`.*\*\s+` + regexp.QuoteMeta(pkg.Name) + `\n(?:\S|\s)+?Latest version available: (.*)`) + matches := installedRegex.FindStringSubmatch(stdout) + pkg.Version = "" + noOfMatches := len(matches) + available := false + if noOfMatches > 1 { + available = true + pkg.Version = strings.TrimSpace(matches[1]) + } + return available, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (e *Emerge) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand + } + return "sudo emerge " + pkg.Name +} diff --git a/v3/internal/doctor/packagemanager/eopkg.go b/v3/internal/doctor/packagemanager/eopkg.go new file mode 100644 index 000000000..060e59595 --- /dev/null +++ b/v3/internal/doctor/packagemanager/eopkg.go @@ -0,0 +1,111 @@ +//go:build linux + +package packagemanager + +import ( + "regexp" + "strings" +) + +type Eopkg struct { + name string + osid string +} + +// NewEopkg creates a new Eopkg instance +func NewEopkg(osid string) *Eopkg { + result := &Eopkg{ + name: "eopkg", + osid: osid, + } + result.intialiseName() + return result +} + +// Packages returns the packages that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (e *Eopkg) Packages() Packagemap { + return Packagemap{ + "gtk3": []*Package{ + {Name: "libgtk-3-devel", SystemPackage: true, Library: true}, + }, + "webkit2gtk": []*Package{ + {Name: "libwebkit-gtk-devel", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "gcc", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "nodejs", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (e *Eopkg) Name() string { + return e.name +} + +// PackageInstalled tests if the given package is installed +func (e *Eopkg) PackageInstalled(pkg *Package) (bool, error) { + if !pkg.SystemPackage { + if pkg.InstallCheck != nil { + return pkg.InstallCheck(), nil + } + return false, nil + } + stdout, err := execCmd("eopkg", "info", pkg.Name) + return strings.HasPrefix(stdout, "Installed"), err +} + +// PackageAvailable tests if the given package is available for installation +func (e *Eopkg) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("eopkg", "info", pkg.Name) + // We add a space to ensure we get a full match, not partial match + output := e.removeEscapeSequences(stdout) + installed := strings.Contains(output, "Package found in Solus repository") + e.getPackageVersion(pkg, output) + return installed, err +} + +// InstallCommand returns the package manager specific command to install a package +func (e *Eopkg) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand + } + return "sudo eopkg it " + pkg.Name +} + +func (e *Eopkg) removeEscapeSequences(in string) string { + escapechars, _ := regexp.Compile(`\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])`) + return escapechars.ReplaceAllString(in, "") +} + +func (e *Eopkg) intialiseName() { + result := "eopkg" + stdout, err := execCmd("eopkg", "--version") + if err == nil { + result = strings.TrimSpace(stdout) + } + e.name = result +} + +func (e *Eopkg) getPackageVersion(pkg *Package, output string) { + + versionRegex := regexp.MustCompile(`.*Name.*version:\s+(.*)+, release: (.*)`) + matches := versionRegex.FindStringSubmatch(output) + pkg.Version = "" + noOfMatches := len(matches) + if noOfMatches > 1 { + pkg.Version = matches[1] + if noOfMatches > 2 { + pkg.Version += " (r" + matches[2] + ")" + } + } +} diff --git a/v3/internal/doctor/packagemanager/nixpkgs.go b/v3/internal/doctor/packagemanager/nixpkgs.go new file mode 100644 index 000000000..4141de056 --- /dev/null +++ b/v3/internal/doctor/packagemanager/nixpkgs.go @@ -0,0 +1,151 @@ +//go:build linux + +package packagemanager + +import ( + "encoding/json" +) + +// Nixpkgs represents the Nixpkgs manager +type Nixpkgs struct { + name string + osid string +} + +type NixPackageDetail struct { + Name string + Pname string + Version string +} + +var available map[string]NixPackageDetail + +// NewNixpkgs creates a new Nixpkgs instance +func NewNixpkgs(osid string) *Nixpkgs { + available = map[string]NixPackageDetail{} + + return &Nixpkgs{ + name: "nixpkgs", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (n *Nixpkgs) Packages() Packagemap { + // Currently, only support checking the default channel. + channel := "nixpkgs" + if n.osid == "nixos" { + channel = "nixos" + } + + return Packagemap{ + "gtk3": []*Package{ + {Name: channel + ".gtk3", SystemPackage: true, Library: true}, + }, + "webkit2gtk": []*Package{ + {Name: channel + ".webkitgtk", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: channel + ".gcc", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: channel + ".pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: channel + ".nodejs", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (n *Nixpkgs) Name() string { + return n.name +} + +// PackageInstalled tests if the given package name is installed +func (n *Nixpkgs) PackageInstalled(pkg *Package) (bool, error) { + if !pkg.SystemPackage { + if pkg.InstallCheck != nil { + return pkg.InstallCheck(), nil + } + return false, nil + } + + stdout, err := execCmd("nix-env", "--json", "-qA", pkg.Name) + if err != nil { + return false, nil + } + + var attributes map[string]NixPackageDetail + err = json.Unmarshal([]byte(stdout), &attributes) + if err != nil { + return false, err + } + + // Did we get one? + installed := false + for attribute, detail := range attributes { + if attribute == pkg.Name { + installed = true + pkg.Version = detail.Version + } + break + } + + // If on NixOS, package may be installed via system config, so check the nix store. + detail, ok := available[pkg.Name] + if !installed && n.osid == "nixos" && ok { + cmd := "nix-store --query --requisites /run/current-system | cut -d- -f2- | sort | uniq | grep '^" + detail.Pname + "'" + + if pkg.Library { + cmd += " | grep 'dev$'" + } + + stdout, err = execCmd("sh", "-c", cmd) + if err != nil { + return false, nil + } + + if len(stdout) > 0 { + installed = true + } + } + + return installed, nil +} + +// PackageAvailable tests if the given package is available for installation +func (n *Nixpkgs) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + + stdout, err := execCmd("nix-env", "--json", "-qaA", pkg.Name) + if err != nil { + return false, nil + } + + var attributes map[string]NixPackageDetail + err = json.Unmarshal([]byte(stdout), &attributes) + if err != nil { + return false, err + } + + // Grab first version. + for attribute, detail := range attributes { + pkg.Version = detail.Version + available[attribute] = detail + break + } + + return len(pkg.Version) > 0, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (n *Nixpkgs) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand + } + return "nix-env -iA " + pkg.Name +} diff --git a/v3/internal/doctor/packagemanager/packagemanager.go b/v3/internal/doctor/packagemanager/packagemanager.go new file mode 100644 index 000000000..800b05f7a --- /dev/null +++ b/v3/internal/doctor/packagemanager/packagemanager.go @@ -0,0 +1,169 @@ +//go:build linux + +package packagemanager + +import ( + "bytes" + "os" + "os/exec" + "sort" + "strings" +) + +func execCmd(command string, args ...string) (string, error) { + cmd := exec.Command(command, args...) + var stdo, stde bytes.Buffer + cmd.Stdout = &stdo + cmd.Stderr = &stde + cmd.Env = append(os.Environ(), "LANGUAGE=en_US.utf-8") + err := cmd.Run() + return stdo.String(), err +} + +// A list of package manager commands +var pmcommands = []string{ + "eopkg", + "apt", + "dnf", + "pacman", + "emerge", + "zypper", + "nix-env", +} + +// commandExists returns true if the given command can be found on the shell +func commandExists(name string) bool { + _, err := exec.LookPath(name) + if err != nil { + return false + } + return true +} + +// Find will attempt to find the system package manager +func Find(osid string) PackageManager { + + // Loop over pmcommands + for _, pmname := range pmcommands { + if commandExists(pmname) { + return newPackageManager(pmname, osid) + } + } + return nil +} + +func newPackageManager(pmname string, osid string) PackageManager { + switch pmname { + case "eopkg": + return NewEopkg(osid) + case "apt": + return NewApt(osid) + case "dnf": + return NewDnf(osid) + case "pacman": + return NewPacman(osid) + case "emerge": + return NewEmerge(osid) + case "zypper": + return NewZypper(osid) + case "nix-env": + return NewNixpkgs(osid) + } + return nil +} + +// Dependencies scans the system for required dependencies +// Returns a list of dependencies search for, whether they were found +// and whether they were installed +func Dependencies(p PackageManager) (DependencyList, error) { + + var dependencies DependencyList + + for name, packages := range p.Packages() { + dependency := &Dependency{Name: name} + for _, pkg := range packages { + dependency.Optional = pkg.Optional + dependency.External = !pkg.SystemPackage + dependency.InstallCommand = p.InstallCommand(pkg) + packageavailable, err := p.PackageAvailable(pkg) + if err != nil { + return nil, err + } + if packageavailable { + dependency.Version = pkg.Version + dependency.PackageName = pkg.Name + installed, err := p.PackageInstalled(pkg) + if err != nil { + return nil, err + } + if installed { + dependency.Installed = true + dependency.Version = pkg.Version + if !pkg.SystemPackage { + dependency.Version = AppVersion(name) + } + } else { + dependency.InstallCommand = p.InstallCommand(pkg) + } + break + } + } + dependencies = append(dependencies, dependency) + } + + // Sort dependencies + sort.Slice(dependencies, func(i, j int) bool { + return dependencies[i].Name < dependencies[j].Name + }) + + return dependencies, nil +} + +// AppVersion returns the version for application related to the given package +func AppVersion(name string) string { + + if name == "gcc" { + return gccVersion() + } + + if name == "pkg-config" { + return pkgConfigVersion() + } + + if name == "npm" { + return npmVersion() + } + + return "" + +} + +func gccVersion() string { + + var version string + var err error + + // Try "-dumpfullversion" + version, err = execCmd("gcc", "-dumpfullversion") + if err != nil { + + // Try -dumpversion + // We ignore the error as this function is not for testing whether the + // application exists, only that we can get the version number + dumpversion, err := execCmd("gcc", "-dumpversion") + if err == nil { + version = dumpversion + } + } + return strings.TrimSpace(version) +} + +func pkgConfigVersion() string { + version, _ := execCmd("pkg-config", "--version") + return strings.TrimSpace(version) +} + +func npmVersion() string { + version, _ := execCmd("npm", "--version") + return strings.TrimSpace(version) +} diff --git a/v3/internal/doctor/packagemanager/pacman.go b/v3/internal/doctor/packagemanager/pacman.go new file mode 100644 index 000000000..48f24164c --- /dev/null +++ b/v3/internal/doctor/packagemanager/pacman.go @@ -0,0 +1,113 @@ +//go:build linux + +package packagemanager + +import ( + "os/exec" + "regexp" + "strings" +) + +// Pacman represents the Pacman package manager +type Pacman struct { + name string + osid string +} + +// NewPacman creates a new Pacman instance +func NewPacman(osid string) *Pacman { + return &Pacman{ + name: "pacman", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (p *Pacman) Packages() Packagemap { + return Packagemap{ + "gtk3": []*Package{ + {Name: "gtk3", SystemPackage: true, Library: true}, + }, + "webkit2gtk": []*Package{ + {Name: "webkit2gtk-4.1", SystemPackage: true, Library: true}, + {Name: "webkit2gtk", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "gcc", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkgconf", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "npm", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (p *Pacman) Name() string { + return p.name +} + +// PackageInstalled tests if the given package name is installed +func (p *Pacman) PackageInstalled(pkg *Package) (bool, error) { + if !pkg.SystemPackage { + if pkg.InstallCheck != nil { + return pkg.InstallCheck(), nil + } + return false, nil + } + stdout, err := execCmd("pacman", "-Q", pkg.Name) + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + splitoutput := strings.Split(stdout, "\n") + for _, line := range splitoutput { + if strings.HasPrefix(line, pkg.Name) { + splitline := strings.Split(line, " ") + pkg.Version = strings.TrimSpace(splitline[1]) + } + } + + return true, err +} + +// PackageAvailable tests if the given package is available for installation +func (p *Pacman) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + output, err := execCmd("pacman", "-Si", pkg.Name) + // We add a space to ensure we get a full match, not partial match + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + reg := regexp.MustCompile(`.*Version.*?:\s+(.*)`) + matches := reg.FindStringSubmatch(output) + pkg.Version = "" + noOfMatches := len(matches) + if noOfMatches > 1 { + pkg.Version = strings.TrimSpace(matches[1]) + } + + return true, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (p *Pacman) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand + } + return "sudo pacman -S " + pkg.Name +} diff --git a/v3/internal/doctor/packagemanager/pm.go b/v3/internal/doctor/packagemanager/pm.go new file mode 100644 index 000000000..6b6ced9c0 --- /dev/null +++ b/v3/internal/doctor/packagemanager/pm.go @@ -0,0 +1,65 @@ +//go:build linux + +package packagemanager + +// Package contains information about a system package +type Package struct { + Name string + Version string + InstallCommand string + InstallCheck func() bool + SystemPackage bool + Library bool + Optional bool +} + +type Packagemap = map[string][]*Package + +// PackageManager is a common interface across all package managers +type PackageManager interface { + Name() string + Packages() Packagemap + PackageInstalled(*Package) (bool, error) + PackageAvailable(*Package) (bool, error) + InstallCommand(*Package) string +} + +// Dependency represents a system package that we require +type Dependency struct { + Name string + PackageName string + Installed bool + InstallCommand string + Version string + Optional bool + External bool +} + +// DependencyList is a list of Dependency instances +type DependencyList []*Dependency + +// InstallAllRequiredCommand returns the command you need to use to install all required dependencies +func (d DependencyList) InstallAllRequiredCommand() string { + + result := "" + for _, dependency := range d { + if !dependency.Installed && !dependency.Optional { + result += " - " + dependency.Name + ": " + dependency.InstallCommand + "\n" + } + } + + return result +} + +// InstallAllOptionalCommand returns the command you need to use to install all optional dependencies +func (d DependencyList) InstallAllOptionalCommand() string { + + result := "" + for _, dependency := range d { + if !dependency.Installed && dependency.Optional { + result += " - " + dependency.Name + ": " + dependency.InstallCommand + "\n" + } + } + + return result +} diff --git a/v3/internal/doctor/packagemanager/zypper.go b/v3/internal/doctor/packagemanager/zypper.go new file mode 100644 index 000000000..7f24b0aca --- /dev/null +++ b/v3/internal/doctor/packagemanager/zypper.go @@ -0,0 +1,122 @@ +//go:build linux +// +build linux + +package packagemanager + +import ( + "os/exec" + "regexp" + "strings" +) + +// Zypper represents the Zypper package manager +type Zypper struct { + name string + osid string +} + +// NewZypper creates a new Zypper instance +func NewZypper(osid string) *Zypper { + return &Zypper{ + name: "zypper", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (z *Zypper) Packages() Packagemap { + return Packagemap{ + "gtk3": []*Package{ + {Name: "gtk3-devel", SystemPackage: true, Library: true}, + }, + "webkit2gtk": []*Package{ + {Name: "webkit2gtk4_1-devel", SystemPackage: true, Library: true}, + {Name: "webkit2gtk3-soup2-devel", SystemPackage: true, Library: true}, + {Name: "webkit2gtk3-devel", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "gcc-c++", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkg-config", SystemPackage: true}, + {Name: "pkgconf-pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "npm10", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (z *Zypper) Name() string { + return z.name +} + +// PackageInstalled tests if the given package name is installed +func (z *Zypper) PackageInstalled(pkg *Package) (bool, error) { + if !pkg.SystemPackage { + if pkg.InstallCheck != nil { + return pkg.InstallCheck(), nil + } + return false, nil + } + stdout, err := execCmd("zypper", "info", pkg.Name) + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + reg := regexp.MustCompile(`.*Installed\s*:\s*(Yes)\s*`) + matches := reg.FindStringSubmatch(stdout) + pkg.Version = "" + noOfMatches := len(matches) + if noOfMatches > 1 { + z.getPackageVersion(pkg, stdout) + } + return noOfMatches > 1, err +} + +// PackageAvailable tests if the given package is available for installation +func (z *Zypper) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("zypper", "info", pkg.Name) + // We add a space to ensure we get a full match, not partial match + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + available := strings.Contains(stdout, "Information for package") + if available { + z.getPackageVersion(pkg, stdout) + } + + return available, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (z *Zypper) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand + } + return "sudo zypper in " + pkg.Name +} + +func (z *Zypper) getPackageVersion(pkg *Package, output string) { + + reg := regexp.MustCompile(`.*Version.*:(.*)`) + matches := reg.FindStringSubmatch(output) + pkg.Version = "" + noOfMatches := len(matches) + if noOfMatches > 1 { + pkg.Version = strings.TrimSpace(matches[1]) + } +} diff --git a/v3/internal/fileexplorer/desktopfile.go b/v3/internal/fileexplorer/desktopfile.go new file mode 100644 index 000000000..4cdd0af04 --- /dev/null +++ b/v3/internal/fileexplorer/desktopfile.go @@ -0,0 +1,96 @@ +package fileexplorer + +import ( + "bufio" + "io" + "os" + "strings" +) + +// DesktopEntry represents a parsed .desktop file's [Desktop Entry] section. +// This is a minimal parser that only extracts the fields we need, +// replacing the full gopkg.in/ini.v1 dependency (~34KB + 68 transitive deps). +type DesktopEntry struct { + Exec string +} + +// ParseDesktopFile parses a .desktop file and returns the Desktop Entry section. +// It follows the Desktop Entry Specification: +// ParseDesktopFile parses the `[Desktop Entry]` section of the desktop file at path and returns a DesktopEntry. +// It returns an error if the file cannot be opened or if parsing the file fails. +func ParseDesktopFile(path string) (*DesktopEntry, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + return ParseDesktopReader(f) +} + +// ParseDesktopReader parses the [Desktop Entry] section of a .desktop file from r and extracts the Exec value. +// It ignores empty lines and lines starting with '#', treats section names as case-sensitive, and stops parsing after leaving the [Desktop Entry] section. +// The returned *DesktopEntry has Exec set to the exact value of the Exec key if present (whitespace preserved). +// An error is returned if reading from r fails. +func ParseDesktopReader(r io.Reader) (*DesktopEntry, error) { + scanner := bufio.NewScanner(r) + entry := &DesktopEntry{} + + inDesktopEntry := false + + for scanner.Scan() { + line := scanner.Text() + + // Skip empty lines + if len(line) == 0 { + continue + } + + // Skip comments (# at start of line) + if line[0] == '#' { + continue + } + + // Handle section headers + if line[0] == '[' { + // Check if this is the [Desktop Entry] section + // The spec says section names are case-sensitive + trimmed := strings.TrimSpace(line) + if trimmed == "[Desktop Entry]" { + inDesktopEntry = true + } else if inDesktopEntry { + // We've left the [Desktop Entry] section + // (e.g., entering [Desktop Action new-window]) + // We already have what we need, so we can stop + break + } + continue + } + + // Only process key=value pairs in [Desktop Entry] section + if !inDesktopEntry { + continue + } + + // Parse key=value (spec says no spaces around =, but be lenient) + eqIdx := strings.Index(line, "=") + if eqIdx == -1 { + continue + } + + key := strings.TrimSpace(line[:eqIdx]) + value := line[eqIdx+1:] // Don't trim value - preserve intentional whitespace + + // We only need the Exec key + // Per spec, keys are case-sensitive and Exec is always "Exec" + if key == "Exec" { + entry.Exec = value + // Continue parsing in case there are multiple Exec lines (shouldn't happen but be safe) + } + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return entry, nil +} \ No newline at end of file diff --git a/v3/internal/fileexplorer/desktopfile_test.go b/v3/internal/fileexplorer/desktopfile_test.go new file mode 100644 index 000000000..feac60fcb --- /dev/null +++ b/v3/internal/fileexplorer/desktopfile_test.go @@ -0,0 +1,532 @@ +package fileexplorer + +import ( + "strings" + "testing" +) + +func TestParseDesktopReader(t *testing.T) { + tests := []struct { + name string + input string + wantExec string + wantErr bool + }{ + { + name: "simple desktop file", + input: `[Desktop Entry] +Name=Files +Exec=nautilus %U +Icon=org.gnome.Nautilus +`, + wantExec: "nautilus %U", + }, + { + name: "exec with full path", + input: `[Desktop Entry] +Name=1Password +Exec=/opt/1Password/1password %U +`, + wantExec: "/opt/1Password/1password %U", + }, + { + name: "exec without arguments", + input: `[Desktop Entry] +Name=Btop +Exec=btop +Terminal=true +`, + wantExec: "btop", + }, + { + name: "exec with spaces in path", + input: `[Desktop Entry] +Name=My App +Exec="/path/with spaces/myapp" %f +`, + wantExec: `"/path/with spaces/myapp" %f`, + }, + { + name: "comments are ignored", + input: `# This is a comment +[Desktop Entry] +# Another comment +Name=Files +Exec=nautilus +# Comment after +`, + wantExec: "nautilus", + }, + { + name: "empty lines are ignored", + input: ` + +[Desktop Entry] + +Name=Files + +Exec=nautilus + +`, + wantExec: "nautilus", + }, + { + name: "key before section is ignored", + input: `Exec=ignored +[Desktop Entry] +Exec=nautilus +`, + wantExec: "nautilus", + }, + { + name: "other sections after Desktop Entry are ignored", + input: `[Desktop Entry] +Exec=nautilus --new-window %U +Icon=nautilus + +[Desktop Action new-window] +Name=New Window +Exec=nautilus --new-window +`, + wantExec: "nautilus --new-window %U", + }, + { + name: "section before Desktop Entry is ignored", + input: `[Some Other Section] +Exec=ignored + +[Desktop Entry] +Exec=nautilus +`, + wantExec: "nautilus", + }, + { + name: "case sensitive section name", + input: `[desktop entry] +Exec=ignored + +[Desktop Entry] +Exec=correct +`, + wantExec: "correct", + }, + { + name: "case sensitive key name", + input: `[Desktop Entry] +exec=ignored +EXEC=also ignored +Exec=correct +`, + wantExec: "correct", + }, + { + name: "value with equals sign", + input: `[Desktop Entry] +Exec=env VAR=value myapp +`, + wantExec: "env VAR=value myapp", + }, + { + name: "value with multiple equals signs", + input: `[Desktop Entry] +Exec=env A=1 B=2 C=3 myapp +`, + wantExec: "env A=1 B=2 C=3 myapp", + }, + { + name: "localized keys are separate", + input: `[Desktop Entry] +Name[en]=Files +Name=Default Files +Exec[en]=ignored +Exec=nautilus +`, + wantExec: "nautilus", + }, + { + name: "whitespace in section header", + input: `[Desktop Entry] +Exec=nautilus +`, + wantExec: "nautilus", + }, + { + name: "no exec key", + input: `[Desktop Entry] +Name=Files +Icon=nautilus +`, + wantExec: "", + }, + { + name: "empty file", + input: ``, + wantExec: "", + }, + { + name: "only comments", + input: `# Comment 1 +# Comment 2 +`, + wantExec: "", + }, + { + name: "no Desktop Entry section", + input: `[Other Section] +Exec=ignored +`, + wantExec: "", + }, + { + name: "real nautilus desktop file structure", + input: `[Desktop Entry] +Name[en_CA]=Files +Name[en_GB]=Files +Name=Files +Comment=Access and organize files +Keywords=folder;manager;explore;disk;filesystem;nautilus; +Exec=nautilus --new-window %U +Icon=org.gnome.Nautilus +Terminal=false +Type=Application +DBusActivatable=true +StartupNotify=true +Categories=GNOME;GTK;Utility;Core;FileManager; +MimeType=inode/directory;application/x-7z-compressed; +X-GNOME-UsesNotifications=true +Actions=new-window; + +[Desktop Action new-window] +Name=New Window +Exec=nautilus --new-window +`, + wantExec: "nautilus --new-window %U", + }, + { + name: "thunar style", + input: `[Desktop Entry] +Version=1.0 +Name=Thunar File Manager +Exec=thunar %F +Icon=Thunar +Type=Application +Categories=System;FileTools;FileManager; +`, + wantExec: "thunar %F", + }, + { + name: "dolphin style", + input: `[Desktop Entry] +Type=Application +Exec=dolphin %u +Icon=system-file-manager +Name=Dolphin +GenericName=File Manager +`, + wantExec: "dolphin %u", + }, + { + name: "pcmanfm style", + input: `[Desktop Entry] +Type=Application +Name=PCManFM +GenericName=File Manager +Exec=pcmanfm %U +Icon=system-file-manager +`, + wantExec: "pcmanfm %U", + }, + { + name: "exec with environment variable", + input: `[Desktop Entry] +Exec=env GDK_BACKEND=x11 nautilus %U +`, + wantExec: "env GDK_BACKEND=x11 nautilus %U", + }, + { + name: "trailing whitespace in value preserved", + input: `[Desktop Entry] +Exec=nautilus +`, + wantExec: "nautilus ", + }, + { + name: "leading whitespace in key", + input: `[Desktop Entry] + Exec=nautilus +`, + wantExec: "nautilus", + }, + { + name: "space around equals", + input: `[Desktop Entry] +Exec = nautilus +`, + wantExec: " nautilus", // We trim the key, value starts after = + }, + { + name: "line without equals is ignored", + input: `[Desktop Entry] +InvalidLine +Exec=nautilus +AnotherInvalidLine +`, + wantExec: "nautilus", + }, + { + name: "UTF-8 in exec path", + input: `[Desktop Entry] +Exec=/usr/bin/文件管理器 %U +`, + wantExec: "/usr/bin/文件管理器 %U", + }, + { + name: "special characters in exec", + input: `[Desktop Entry] +Exec=sh -c "echo 'hello world' && nautilus %U" +`, + wantExec: `sh -c "echo 'hello world' && nautilus %U"`, + }, + { + name: "multiple Desktop Entry sections (invalid file, last value wins)", + input: `[Desktop Entry] +Exec=first + +[Desktop Entry] +Exec=second +`, + wantExec: "second", // Invalid file, but we handle it gracefully + }, + { + name: "very long exec line", + input: `[Desktop Entry] +Exec=` + strings.Repeat("a", 1000) + ` +`, + wantExec: strings.Repeat("a", 1000), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + entry, err := ParseDesktopReader(strings.NewReader(tt.input)) + if (err != nil) != tt.wantErr { + t.Errorf("ParseDesktopReader() error = %v, wantErr %v", err, tt.wantErr) + return + } + if err != nil { + return + } + if entry.Exec != tt.wantExec { + t.Errorf("ParseDesktopReader() Exec = %q, want %q", entry.Exec, tt.wantExec) + } + }) + } +} + +func TestParseDesktopReader_LineScanning(t *testing.T) { + // Test that we handle lines at the scanner's limit (64KB default) + // bufio.Scanner returns an error for lines > 64KB, which is acceptable + // since real .desktop files never have lines that long + + t.Run("line at buffer limit returns error", func(t *testing.T) { + // Create a line that exceeds the buffer size (64KB) + longValue := strings.Repeat("x", 65536) + input := "[Desktop Entry]\nExec=" + longValue + "\n" + + _, err := ParseDesktopReader(strings.NewReader(input)) + if err == nil { + t.Error("Expected error for line exceeding buffer size, got nil") + } + }) + + t.Run("line under buffer limit works", func(t *testing.T) { + // Create a line that's under the limit (should work fine) + longValue := strings.Repeat("x", 60000) + input := "[Desktop Entry]\nExec=" + longValue + "\n" + + entry, err := ParseDesktopReader(strings.NewReader(input)) + if err != nil { + t.Errorf("Unexpected error for long but valid line: %v", err) + return + } + if entry.Exec != longValue { + t.Errorf("Long line not parsed correctly, got length %d, want %d", len(entry.Exec), len(longValue)) + } + }) +} + +func TestParseDesktopReader_RealWorldFiles(t *testing.T) { + // These are actual .desktop file contents from real systems + realWorldTests := []struct { + name string + content string + wantExec string + }{ + { + name: "GNOME Nautilus 43.x", + content: `[Desktop Entry] +Name=Files +Comment=Access and organize files +Keywords=folder;manager;explore;disk;filesystem;nautilus; +Exec=nautilus --new-window %U +Icon=org.gnome.Nautilus +Terminal=false +Type=Application +DBusActivatable=true +StartupNotify=true +Categories=GNOME;GTK;Utility;Core;FileManager; +MimeType=inode/directory;application/x-7z-compressed; +Actions=new-window; + +[Desktop Action new-window] +Name=New Window +Exec=nautilus --new-window`, + wantExec: "nautilus --new-window %U", + }, + { + name: "KDE Dolphin", + content: `[Desktop Entry] +Type=Application +Exec=dolphin %u +Icon=system-file-manager +Terminal=false +InitialPreference=9 +Name=Dolphin +GenericName=File Manager +MimeType=inode/directory; +Categories=Qt;KDE;System;FileTools;FileManager; +Actions=new-window; + +[Desktop Action new-window] +Name=Open a New Window +Exec=dolphin %u`, + wantExec: "dolphin %u", + }, + { + name: "Thunar", + content: `[Desktop Entry] +Version=1.0 +Name=Thunar File Manager +GenericName=File Manager +Comment=Browse the filesystem with the file manager +Exec=thunar %F +Icon=Thunar +Terminal=false +StartupNotify=true +Type=Application +Categories=System;FileTools;FileManager; +`, + wantExec: "thunar %F", + }, + { + name: "PCManFM", + content: `[Desktop Entry] +Type=Application +Name=PCManFM +GenericName=File Manager +Comment=Browse the file system +Exec=pcmanfm %U +Icon=system-file-manager +Terminal=false +StartupNotify=true +Categories=Utility;FileManager;`, + wantExec: "pcmanfm %U", + }, + { + name: "Caja (MATE)", + content: `[Desktop Entry] +Name=Files +Comment=Access and organize files +Exec=caja %U +Icon=system-file-manager +Terminal=false +Type=Application +Categories=MATE;System;FileManager; +StartupNotify=true`, + wantExec: "caja %U", + }, + { + name: "Nemo (Cinnamon)", + content: `[Desktop Entry] +Name=Files +Comment=Access and organize files +Exec=nemo %U +Icon=folder +Terminal=false +Type=Application +StartupNotify=true +Categories=GNOME;GTK;Utility;Core; +MimeType=inode/directory;`, + wantExec: "nemo %U", + }, + } + + for _, tt := range realWorldTests { + t.Run(tt.name, func(t *testing.T) { + entry, err := ParseDesktopReader(strings.NewReader(tt.content)) + if err != nil { + t.Fatalf("ParseDesktopReader() error = %v", err) + } + if entry.Exec != tt.wantExec { + t.Errorf("ParseDesktopReader() Exec = %q, want %q", entry.Exec, tt.wantExec) + } + }) + } +} + +// BenchmarkParseDesktopReader measures parsing performance +func BenchmarkParseDesktopReader(b *testing.B) { + // Real Nautilus .desktop file content + content := `[Desktop Entry] +Name=Files +Comment=Access and organize files +Keywords=folder;manager;explore;disk;filesystem;nautilus; +Exec=nautilus --new-window %U +Icon=org.gnome.Nautilus +Terminal=false +Type=Application +DBusActivatable=true +StartupNotify=true +Categories=GNOME;GTK;Utility;Core;FileManager; +MimeType=inode/directory;application/x-7z-compressed; +Actions=new-window; + +[Desktop Action new-window] +Name=New Window +Exec=nautilus --new-window +` + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ParseDesktopReader(strings.NewReader(content)) + if err != nil { + b.Fatal(err) + } + } +} + +// BenchmarkParseDesktopReader_Large tests parsing a file with many localized entries +func BenchmarkParseDesktopReader_Large(b *testing.B) { + // Simulate a desktop file with many localized Name entries (like Nautilus) + var sb strings.Builder + sb.WriteString("[Desktop Entry]\n") + for i := 0; i < 100; i++ { + sb.WriteString("Name[lang") + sb.WriteString(strings.Repeat("x", 5)) + sb.WriteString("]=Localized Name\n") + } + sb.WriteString("Exec=nautilus %U\n") + sb.WriteString("[Desktop Action new-window]\n") + sb.WriteString("Name=New Window\n") + sb.WriteString("Exec=nautilus\n") + + content := sb.String() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ParseDesktopReader(strings.NewReader(content)) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/v3/internal/fileexplorer/fileexplorer.go b/v3/internal/fileexplorer/fileexplorer.go new file mode 100644 index 000000000..bc0048f94 --- /dev/null +++ b/v3/internal/fileexplorer/fileexplorer.go @@ -0,0 +1,61 @@ +package fileexplorer + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "time" +) + +func OpenFileManager(path string, selectFile bool) error { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + path = os.ExpandEnv(path) + path = filepath.Clean(path) + absPath, err := filepath.Abs(path) + if err != nil { + return fmt.Errorf("failed to resolve the absolute path: %w", err) + } + path = absPath + if pathInfo, err := os.Stat(path); err != nil { + return fmt.Errorf("failed to access the specified path: %w", err) + } else { + selectFile = selectFile && !pathInfo.IsDir() + } + + var ( + ignoreExitCode bool = false + ) + + switch runtime.GOOS { + case "windows": + // NOTE: Disabling the exit code check on Windows system. Workaround for explorer.exe + // exit code handling (https://github.com/microsoft/WSL/issues/6565) + ignoreExitCode = true + case "darwin", "linux": + default: + return errors.New("unsupported platform: " + runtime.GOOS) + } + + explorerBin, explorerArgs, err := explorerBinArgs(path, selectFile) + if err != nil { + return fmt.Errorf("failed to determine the file explorer binary: %w", err) + } + + cmd := exec.CommandContext(ctx, explorerBin, explorerArgs...) + cmd.SysProcAttr = sysProcAttr(path, selectFile) + cmd.Stdout = nil + cmd.Stderr = nil + + if err := cmd.Run(); err != nil { + if !ignoreExitCode { + return fmt.Errorf("failed to open the file explorer: %w", err) + } + } + return nil +} diff --git a/v3/internal/fileexplorer/fileexplorer_darwin.go b/v3/internal/fileexplorer/fileexplorer_darwin.go new file mode 100644 index 000000000..ce16d1206 --- /dev/null +++ b/v3/internal/fileexplorer/fileexplorer_darwin.go @@ -0,0 +1,19 @@ +//go:build darwin + +package fileexplorer + +import "syscall" + +func explorerBinArgs(path string, selectFile bool) (string, []string, error) { + args := []string{} + if selectFile { + args = append(args, "-R") + } + + args = append(args, path) + return "open", args, nil +} + +func sysProcAttr(path string, selectFile bool) *syscall.SysProcAttr { + return &syscall.SysProcAttr{} +} diff --git a/v3/internal/fileexplorer/fileexplorer_linux.go b/v3/internal/fileexplorer/fileexplorer_linux.go new file mode 100644 index 000000000..bf7e070a7 --- /dev/null +++ b/v3/internal/fileexplorer/fileexplorer_linux.go @@ -0,0 +1,113 @@ +//go:build linux + +package fileexplorer + +import ( + "bytes" + "fmt" + "net/url" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" +) + +// when possible; the fallback method does not support selecting a file. +func explorerBinArgs(path string, selectFile bool) (string, []string, error) { + // Map of field codes to their replacements + var fieldCodes = map[string]string{ + "%d": "", + "%D": "", + "%n": "", + "%N": "", + "%v": "", + "%m": "", + "%f": path, + "%F": path, + "%u": pathToURI(path), + "%U": pathToURI(path), + } + fileManagerQuery := exec.Command("xdg-mime", "query", "default", "inode/directory") + buf := new(bytes.Buffer) + fileManagerQuery.Stdout = buf + fileManagerQuery.Stderr = nil + + if err := fileManagerQuery.Run(); err != nil { + return fallbackExplorerBinArgs(path, selectFile) + } + + desktopFilePath, err := findDesktopFile(strings.TrimSpace((buf.String()))) + if err != nil { + return fallbackExplorerBinArgs(path, selectFile) + } + + entry, err := ParseDesktopFile(desktopFilePath) + if err != nil { + // Opting to fallback rather than fail + return fallbackExplorerBinArgs(path, selectFile) + } + + execCmd := entry.Exec + for fieldCode, replacement := range fieldCodes { + execCmd = strings.ReplaceAll(execCmd, fieldCode, replacement) + } + args := strings.Fields(execCmd) + if !strings.Contains(strings.Join(args, " "), path) { + args = append(args, path) + } + + return args[0], args[1:], nil +} + +func sysProcAttr(path string, selectFile bool) *syscall.SysProcAttr { + return &syscall.SysProcAttr{} +} + +func fallbackExplorerBinArgs(path string, selectFile bool) (string, []string, error) { + // NOTE: The linux fallback explorer opening does not support file selection + + stat, err := os.Stat(path) + if err != nil { + return "", []string{}, fmt.Errorf("stat path: %w", err) + } + + // If the path is a file, we want to open the directory containing the file + if !stat.IsDir() { + path = filepath.Dir(path) + } + + return "xdg-open", []string{path}, nil +} + +func pathToURI(path string) string { + absPath, err := filepath.Abs(path) + if err != nil { + return path + } + // Use url.URL to properly construct file URIs. + // url.PathEscape incorrectly escapes forward slashes (/ -> %2F), + // which breaks file manager path parsing. + u := &url.URL{ + Scheme: "file", + Path: absPath, + } + return u.String() +} + +func findDesktopFile(xdgFileName string) (string, error) { + paths := []string{ + filepath.Join(os.Getenv("XDG_DATA_HOME"), "applications"), + filepath.Join(os.Getenv("HOME"), ".local", "share", "applications"), + "/usr/share/applications", + } + + for _, path := range paths { + desktopFile := filepath.Join(path, xdgFileName) + if _, err := os.Stat(desktopFile); err == nil { + return desktopFile, nil + } + } + err := fmt.Errorf("desktop file not found: %s", xdgFileName) + return "", err +} \ No newline at end of file diff --git a/v3/internal/fileexplorer/fileexplorer_test.go b/v3/internal/fileexplorer/fileexplorer_test.go new file mode 100644 index 000000000..8f7a9b39f --- /dev/null +++ b/v3/internal/fileexplorer/fileexplorer_test.go @@ -0,0 +1,83 @@ +package fileexplorer_test + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/wailsapp/wails/v3/internal/fileexplorer" +) + +// Credit: https://stackoverflow.com/a/50631395 +func skipCI(t *testing.T) { + if os.Getenv("CI") != "" { + t.Skip("Skipping testing in CI environment") + } +} + +func TestFileExplorer(t *testing.T) { + skipCI(t) + // TestFileExplorer verifies that the OpenFileManager function correctly handles: + // - Opening files in the native file manager across different platforms + // - Selecting files when the selectFile parameter is true + // - Various error conditions like non-existent paths + tempDir := t.TempDir() // Create a temporary directory for tests + + tests := []struct { + name string + path string + selectFile bool + expectedErr error + }{ + {"Open Existing File", tempDir, false, nil}, + {"Select Existing File", tempDir, true, nil}, + {"Non-Existent Path", "/path/does/not/exist", false, fmt.Errorf("failed to access the specified path: /path/does/not/exist")}, + {"Path with Special Characters", filepath.Join(tempDir, "test space.txt"), true, nil}, + {"No Permission Path", "/root/test.txt", false, fmt.Errorf("failed to open the file explorer: /root/test.txt")}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Run("Windows", func(t *testing.T) { + runPlatformTest(t, "windows") + }) + t.Run("Linux", func(t *testing.T) { + runPlatformTest(t, "linux") + }) + t.Run("Darwin", func(t *testing.T) { + runPlatformTest(t, "darwin") + }) + }) + } +} + +func runPlatformTest(t *testing.T, platform string) { + if runtime.GOOS != platform { + t.Skipf("Skipping test on non-%s platform", strings.ToTitle(platform)) + } + + testFile := filepath.Join(t.TempDir(), "test.txt") + if err := os.WriteFile(testFile, []byte("Test file contents"), 0644); err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + selectFile bool + }{ + {"OpenFile", false}, + {"SelectFile", true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := fileexplorer.OpenFileManager(testFile, test.selectFile) + if err != nil { + t.Errorf("OpenFileManager(%s, %v) error = %v", testFile, test.selectFile, err) + } + }) + } +} diff --git a/v3/internal/fileexplorer/fileexplorer_windows.go b/v3/internal/fileexplorer/fileexplorer_windows.go new file mode 100644 index 000000000..6498c5f26 --- /dev/null +++ b/v3/internal/fileexplorer/fileexplorer_windows.go @@ -0,0 +1,24 @@ +//go:build windows + +package fileexplorer + +import ( + "fmt" + "syscall" +) + +func explorerBinArgs(path string, selectFile bool) (string, []string, error) { + return "explorer.exe", []string{}, nil +} + +func sysProcAttr(path string, selectFile bool) *syscall.SysProcAttr { + if selectFile { + return &syscall.SysProcAttr{ + CmdLine: fmt.Sprintf("explorer.exe /select,\"%s\"", path), + } + } else { + return &syscall.SysProcAttr{ + CmdLine: fmt.Sprintf("explorer.exe \"%s\"", path), + } + } +} diff --git a/v3/internal/flags/bindings.go b/v3/internal/flags/bindings.go new file mode 100644 index 000000000..a95566af6 --- /dev/null +++ b/v3/internal/flags/bindings.go @@ -0,0 +1,97 @@ +package flags + +import ( + "errors" + "slices" + "strings" + "unicode/utf8" +) + +type GenerateBindingsOptions struct { + BuildFlagsString string `name:"f" description:"A list of additional space-separated Go build flags. Flags (or parts of them) can be wrapped in single or double quotes to include spaces"` + OutputDirectory string `name:"d" description:"The output directory" default:"frontend/bindings"` + ModelsFilename string `name:"models" description:"File name for exported JS/TS models (excluding the extension)" default:"models"` + IndexFilename string `name:"index" description:"File name for JS/TS package indexes (excluding the extension)" default:"index"` + TS bool `name:"ts" description:"Generate Typescript bindings"` + UseInterfaces bool `name:"i" description:"Generate Typescript interfaces instead of classes"` + UseBundledRuntime bool `name:"b" description:"Use the bundled runtime instead of importing the npm package"` + UseNames bool `name:"names" description:"Use names instead of IDs for the binding calls"` + NoEvents bool `name:"noevents" description:"Do not generate types for registered custom events"` + NoIndex bool `name:"noindex" description:"Do not generate JS/TS index files"` + DryRun bool `name:"dry" description:"Do not write output files"` + Silent bool `name:"silent" description:"Silent mode"` + Verbose bool `name:"v" description:"Enable debug output"` + Clean bool `name:"clean" description:"Clean output directory before generation" default:"true"` +} + +var ErrUnmatchedQuote = errors.New("build flags contain an unmatched quote") + +func isWhitespace(r rune) bool { + // We use Go's definition of whitespace instead of the Unicode ones + return r == ' ' || r == '\t' || r == '\r' || r == '\n' +} + +func isNonWhitespace(r rune) bool { + return !isWhitespace(r) +} + +func isQuote(r rune) bool { + return r == '\'' || r == '"' +} + +func isQuoteOrWhitespace(r rune) bool { + return isQuote(r) || isWhitespace(r) +} + +func (options *GenerateBindingsOptions) BuildFlags() (flags []string, err error) { + str := options.BuildFlagsString + + // temporary buffer for flag assembly + flag := make([]byte, 0, 32) + + for start := strings.IndexFunc(str, isNonWhitespace); start >= 0; start = strings.IndexFunc(str, isNonWhitespace) { + // each iteration starts at the beginning of a flag + // skip initial whitespace + str = str[start:] + + // iterate over all quoted and unquoted parts of the flag and join them + for { + breakpoint := strings.IndexFunc(str, isQuoteOrWhitespace) + if breakpoint < 0 { + breakpoint = len(str) + } + + // append everything up to the breakpoint + flag = append(flag, str[:breakpoint]...) + str = str[breakpoint:] + + quote, quoteSize := utf8.DecodeRuneInString(str) + if !isQuote(quote) { + // if the breakpoint is not a quote, we reached the end of the flag + break + } + + // otherwise, look for the closing quote + str = str[quoteSize:] + closingQuote := strings.IndexRune(str, quote) + + // closing quote not found, append everything to the last flag and raise an error + if closingQuote < 0 { + flag = append(flag, str...) + str = "" + err = ErrUnmatchedQuote + break + } + + // closing quote found, append quoted content to the flag and restart after the quote + flag = append(flag, str[:closingQuote]...) + str = str[closingQuote+quoteSize:] + } + + // append a clone of the flag to the result, then reuse buffer space + flags = append(flags, string(slices.Clone(flag))) + flag = flag[:0] + } + + return +} diff --git a/v3/internal/flags/bindings_test.go b/v3/internal/flags/bindings_test.go new file mode 100644 index 000000000..506ca1b00 --- /dev/null +++ b/v3/internal/flags/bindings_test.go @@ -0,0 +1,125 @@ +package flags + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" +) + +func TestBuildFlags(t *testing.T) { + tests := []struct { + name string + input string + wantFlags []string + wantErr bool + }{ + { + name: "empty string", + input: "", + wantFlags: nil, + }, + { + name: "single flag, multiple spaces", + input: " -v ", + wantFlags: []string{"-v"}, + }, + { + name: "multiple flags, complex spaces", + input: " \t-v\r\n-x", + wantFlags: []string{"-v", "-x"}, + }, + { + name: "empty flag (single quotes)", + input: `''`, + wantFlags: []string{""}, + }, + { + name: "empty flag (double quotes)", + input: `""`, + wantFlags: []string{""}, + }, + { + name: "flag with spaces (single quotes)", + input: `'a b'`, + wantFlags: []string{"a \tb"}, + }, + { + name: "flag with spaces (double quotes)", + input: `'a b'`, + wantFlags: []string{"a \tb"}, + }, + { + name: "mixed quoted and non-quoted flags (single quotes)", + input: `-v 'a b ' -x`, + wantFlags: []string{"-v", "a b ", "-x"}, + }, + { + name: "mixed quoted and non-quoted flags (double quotes)", + input: `-v "a b " -x`, + wantFlags: []string{"-v", "a b ", "-x"}, + }, + { + name: "mixed quoted and non-quoted flags (mixed quotes)", + input: `-v "a b " '-x'`, + wantFlags: []string{"-v", "a b ", "-x"}, + }, + { + name: "double quote within single quotes", + input: `' " '`, + wantFlags: []string{" \" "}, + }, + { + name: "single quote within double quotes", + input: `" ' "`, + wantFlags: []string{" ' "}, + }, + { + name: "unmatched single quote", + input: `-v "a b " '-x -y`, + wantFlags: []string{"-v", "a b ", "-x -y"}, + wantErr: true, + }, + { + name: "unmatched double quote", + input: `-v "a b " "-x -y`, + wantFlags: []string{"-v", "a b ", "-x -y"}, + wantErr: true, + }, + { + name: "mismatched single quote", + input: `-v "a b " '-x" -y`, + wantFlags: []string{"-v", "a b ", "-x\" -y"}, + wantErr: true, + }, + { + name: "mismatched double quote", + input: `-v "a b " "-x' -y`, + wantFlags: []string{"-v", "a b ", "-x' -y"}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + options := GenerateBindingsOptions{ + BuildFlagsString: tt.input, + } + + var wantErr error = nil + if tt.wantErr { + wantErr = ErrUnmatchedQuote + } + + gotFlags, gotErr := options.BuildFlags() + + if diff := cmp.Diff(tt.wantFlags, gotFlags); diff != "" { + t.Errorf("BuildFlags() unexpected result: %s\n", diff) + } + + if diff := cmp.Diff(wantErr, gotErr, cmpopts.EquateErrors()); diff != "" { + t.Errorf("BuildFlags() unexpected error: %s\n", diff) + } + }) + } +} diff --git a/v3/internal/flags/common.go b/v3/internal/flags/common.go new file mode 100644 index 000000000..e58eff411 --- /dev/null +++ b/v3/internal/flags/common.go @@ -0,0 +1,5 @@ +package flags + +type Common struct { + NoColour bool `description:"Disable colour in output"` +} diff --git a/v3/internal/flags/init.go b/v3/internal/flags/init.go new file mode 100644 index 000000000..a8b8e079d --- /dev/null +++ b/v3/internal/flags/init.go @@ -0,0 +1,24 @@ +package flags + +type Init struct { + Common + + PackageName string `name:"p" description:"Package name" default:"main"` + TemplateName string `name:"t" description:"Name of built-in template to use, path to template or template url" default:"vanilla"` + ProjectName string `name:"n" description:"Name of project" default:""` + ProjectDir string `name:"d" description:"Project directory" default:"."` + Quiet bool `name:"q" description:"Suppress output to console"` + List bool `name:"l" description:"List templates"` + Force bool `name:"f" description:"Force init in non-empty directory (use with caution)"` + SkipGoModTidy bool `name:"skipgomodtidy" description:"Skip running go mod tidy"` + Git string `name:"git" description:"Git repository URL to initialize (e.g. github.com/username/project)"` + ModulePath string `name:"mod" description:"The Go module path for the project. Will be computed from the Git URL if unspecified."` + ProductName string `description:"The name of the product" default:"My Product"` + ProductDescription string `description:"The description of the product" default:"My Product Description"` + ProductVersion string `description:"The version of the product" default:"0.1.0"` + ProductCompany string `description:"The company of the product" default:"My Company"` + ProductCopyright string `description:"The copyright notice" default:"\u00a9 now, My Company"` + ProductComments string `description:"Comments to add to the generated files" default:"This is a comment"` + ProductIdentifier string `description:"The product identifier, e.g com.mycompany.myproduct"` + SkipWarning bool `name:"s" description:"Skips the warning message when using remote templates"` +} diff --git a/v3/internal/flags/lipo.go b/v3/internal/flags/lipo.go new file mode 100644 index 000000000..90f2893fd --- /dev/null +++ b/v3/internal/flags/lipo.go @@ -0,0 +1,12 @@ +package flags + +// Lipo represents the options for creating macOS universal binaries +type Lipo struct { + Common + + // Output is the path for the universal binary + Output string `name:"output" short:"o" description:"Output path for the universal binary" default:""` + + // Inputs are the architecture-specific binaries to combine + Inputs []string `name:"input" short:"i" description:"Input binaries to combine (specify multiple times)" default:""` +} diff --git a/v3/internal/flags/msix.go b/v3/internal/flags/msix.go new file mode 100644 index 000000000..17bbbd446 --- /dev/null +++ b/v3/internal/flags/msix.go @@ -0,0 +1,26 @@ +package flags + +// ToolMSIX represents the options for the MSIX packaging command +type ToolMSIX struct { + Common + + // Project configuration + ConfigPath string `name:"config" description:"Path to the project configuration file" default:"wails.json"` + + // MSIX package information + Publisher string `name:"publisher" description:"Publisher name for the MSIX package (e.g., CN=CompanyName)" default:""` + + // Certificate for signing + CertificatePath string `name:"cert" description:"Path to the certificate file for signing the MSIX package" default:""` + CertificatePassword string `name:"cert-password" description:"Password for the certificate file" default:""` + + // Build options + Arch string `name:"arch" description:"Architecture of the package (x64, x86, arm64)" default:"x64"` + ExecutableName string `name:"name" description:"Name of the executable in the package" default:""` + ExecutablePath string `name:"executable" description:"Path to the executable file to package" default:""` + OutputPath string `name:"out" description:"Path where the MSIX package will be saved" default:""` + + // Tool selection + UseMsixPackagingTool bool `name:"use-msix-tool" description:"Use the Microsoft MSIX Packaging Tool for packaging" default:"false"` + UseMakeAppx bool `name:"use-makeappx" description:"Use MakeAppx.exe for packaging" default:"true"` +} diff --git a/v3/internal/flags/package.go b/v3/internal/flags/package.go new file mode 100644 index 000000000..bd56107c5 --- /dev/null +++ b/v3/internal/flags/package.go @@ -0,0 +1,13 @@ +package flags + +// ToolPackage represents the options for the package command +type ToolPackage struct { + Common + + Format string `name:"format" description:"Package format to generate (deb, rpm, archlinux, dmg)" default:"deb"` + ExecutableName string `name:"name" description:"Name of the executable to package" default:"myapp"` + ConfigPath string `name:"config" description:"Path to the package configuration file" default:""` + Out string `name:"out" description:"Path to the output dir" default:"."` + BackgroundImage string `name:"background" description:"Path to an optional background image for the DMG" default:""` + CreateDMG bool `name:"create-dmg" description:"Create a DMG file (macOS only)" default:"false"` +} diff --git a/v3/internal/flags/service.go b/v3/internal/flags/service.go new file mode 100644 index 000000000..f7fd8ec2e --- /dev/null +++ b/v3/internal/flags/service.go @@ -0,0 +1,14 @@ +package flags + +type ServiceInit struct { + Name string `name:"n" description:"Name of service" default:"example_service"` + Description string `name:"d" description:"Description of service" default:"Example service"` + PackageName string `name:"p" description:"Package name for service" default:""` + OutputDir string `name:"o" description:"Output directory" default:"."` + Quiet bool `name:"q" description:"Suppress output to console"` + Author string `name:"a" description:"Author of service" default:""` + Version string `name:"v" description:"Version of service" default:""` + Website string `name:"w" description:"Website of service" default:""` + Repository string `name:"r" description:"Repository of service" default:""` + License string `name:"l" description:"License of service" default:""` +} diff --git a/v3/internal/flags/sign.go b/v3/internal/flags/sign.go new file mode 100644 index 000000000..9e8442405 --- /dev/null +++ b/v3/internal/flags/sign.go @@ -0,0 +1,26 @@ +package flags + +// Sign contains flags for the sign command +type Sign struct { + Input string `name:"input" description:"Path to the file to sign"` + Output string `name:"output" description:"Output path (optional, defaults to in-place signing)"` + Verbose bool `name:"verbose" description:"Enable verbose output"` + + // Windows/macOS certificate signing + Certificate string `name:"certificate" description:"Path to PKCS#12 (.pfx/.p12) certificate file"` + Password string `name:"password" description:"Certificate password (reads from keychain if not provided)"` + Thumbprint string `name:"thumbprint" description:"Certificate thumbprint in Windows certificate store"` + Timestamp string `name:"timestamp" description:"Timestamp server URL"` + + // macOS specific + Identity string `name:"identity" description:"Signing identity (e.g., 'Developer ID Application: ...')"` + Entitlements string `name:"entitlements" description:"Path to entitlements plist file"` + HardenedRuntime bool `name:"hardened-runtime" description:"Enable hardened runtime (default: true for notarization)"` + Notarize bool `name:"notarize" description:"Submit for Apple notarization after signing"` + KeychainProfile string `name:"keychain-profile" description:"Keychain profile for notarization credentials"` + + // Linux PGP signing + PGPKey string `name:"pgp-key" description:"Path to PGP private key file"` + PGPPassword string `name:"pgp-password" description:"PGP key password (reads from keychain if not provided)"` + Role string `name:"role" description:"DEB signing role (origin, maint, archive, builder)"` +} diff --git a/v3/internal/flags/signing.go b/v3/internal/flags/signing.go new file mode 100644 index 000000000..72dd71f3c --- /dev/null +++ b/v3/internal/flags/signing.go @@ -0,0 +1,9 @@ +package flags + +type SigningSetup struct { + Platforms []string `name:"platform" description:"Platform(s) to configure (darwin, windows, linux). If not specified, auto-detects from build directory."` +} + +type EntitlementsSetup struct { + Output string `name:"output" description:"Output path for entitlements.plist (default: build/darwin/entitlements.plist)"` +} diff --git a/v3/internal/flags/task_wrapper.go b/v3/internal/flags/task_wrapper.go new file mode 100644 index 000000000..d77535f6c --- /dev/null +++ b/v3/internal/flags/task_wrapper.go @@ -0,0 +1,17 @@ +package flags + +type Build struct { + Common +} + +type Dev struct { + Common +} + +type Package struct { + Common +} + +type SignWrapper struct { + Common +} diff --git a/v3/internal/generator/.gitignore b/v3/internal/generator/.gitignore new file mode 100644 index 000000000..3ed83544e --- /dev/null +++ b/v3/internal/generator/.gitignore @@ -0,0 +1,4 @@ +.task +node_modules +testdata/output/**/*.got.[jt]s +testdata/output/**/*.got.log diff --git a/v3/internal/generator/README.md b/v3/internal/generator/README.md new file mode 100644 index 000000000..20b879245 --- /dev/null +++ b/v3/internal/generator/README.md @@ -0,0 +1,6 @@ +# Generator + +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 diff --git a/v3/internal/generator/Taskfile.yaml b/v3/internal/generator/Taskfile.yaml new file mode 100644 index 000000000..491e426b9 --- /dev/null +++ b/v3/internal/generator/Taskfile.yaml @@ -0,0 +1,56 @@ +# https://taskfile.dev + +version: "3" + +shopt: [globstar] + +tasks: + clean: + cmds: + - rm -rf ./testdata/output/**/*.got.[jt]s ./testdata/output/**/*.got.log + + test: + cmds: + - go test -count=1 -v . + - task: test:check + + test:analyse: + cmds: + - go test -count=1 -v -run ^TestAnalyser . + + test:constants: + cmds: + - go test -v -count=1 -run ^TestGenerateConstants . + + test:generate: + cmds: + - go test -v -count=1 -run ^TestGenerator . + - task: test:check + + test:regenerate: + cmds: + - cmd: rm -rf ./testdata/output/* + - cmd: go test -v -count=1 -run ^TestGenerator . + ignore_error: true + - task: test:generate + + test:check: + dir: ./testdata + deps: + - install-deps + cmds: + - npx tsc + - npx madge --circular output/ + + install-deps: + internal: true + dir: ./testdata + sources: + - package.json + cmds: + - npm install + + update: + dir: ./testdata + cmds: + - npx npm-check-updates -u diff --git a/v3/internal/generator/analyse.go b/v3/internal/generator/analyse.go new file mode 100644 index 000000000..e3f664496 --- /dev/null +++ b/v3/internal/generator/analyse.go @@ -0,0 +1,220 @@ +package generator + +import ( + "fmt" + "go/token" + "go/types" + "iter" + + "github.com/wailsapp/wails/v3/internal/generator/config" + "golang.org/x/tools/go/packages" +) + +// FindServices scans the given packages for invocations +// of the NewService function from the Wails application package. +// +// Whenever one is found and the type of its unique argument +// is a valid service type, the corresponding named type object +// is fed into the returned iterator. +// +// Results are deduplicated, i.e. the iterator yields any given object at most once. +func FindServices(pkgs []*packages.Package, systemPaths *config.SystemPaths, logger config.Logger) (iter.Seq[*types.TypeName], types.Object, error) { + type instanceInfo struct { + args *types.TypeList + pos token.Position + } + + type target struct { + obj types.Object + param int + } + + type targetInfo struct { + target + cause token.Position + } + + // instances maps objects (TypeName or Func) to their instance list. + instances := make(map[types.Object][]instanceInfo) + + // owner maps type parameter objects to their parent object (TypeName or Func) + owner := make(map[*types.TypeName]types.Object) + + // scheduled holds the set of type parameters + // that have been already scheduled for analysis, + // for deduplication. + scheduled := make(map[target]bool) + + // registerEvent holds the `application.RegisterEvent` function if found. + var registerEvent types.Object + + // next lists type parameter objects that have yet to be analysed. + var next []targetInfo + + // Initialise instance/owner maps and detect application.NewService. + for _, pkg := range pkgs { + for ident, instance := range pkg.TypesInfo.Instances { + obj := pkg.TypesInfo.Uses[ident] + + // Add to instance map. + objInstances, seen := instances[obj] + instances[obj] = append(objInstances, instanceInfo{ + instance.TypeArgs, + pkg.Fset.Position(ident.Pos()), + }) + + if seen { + continue + } + + // Object seen for the first time: + // add type params to owner map. + var tp *types.TypeParamList + + if t, ok := obj.Type().(interface{ TypeParams() *types.TypeParamList }); ok { + tp = t.TypeParams() + } else { + // Instantiated object has unexpected kind: + // the spec might have changed. + logger.Warningf( + "unexpected instantiation for %s: please report this to Wails maintainers", + types.ObjectString(obj, nil), + ) + continue + } + + // Add type params to owner map. + for i := range tp.Len() { + if param := tp.At(i).Obj(); param != nil { + owner[param] = obj + } + } + + // If this is a named type, process methods. + if recv, ok := obj.Type().(*types.Named); ok && recv.NumMethods() > 0 { + // Register receiver type params. + for i := range recv.NumMethods() { + tp := recv.Method(i).Type().(*types.Signature).RecvTypeParams() + for j := range tp.Len() { + if param := tp.At(j).Obj(); param != nil { + owner[param] = obj + } + } + } + } + + // Detect application.RegisterEvent + if registerEvent == nil && obj.Name() == "RegisterEvent" && obj.Pkg().Path() == systemPaths.ApplicationPackage { + fn, ok := obj.(*types.Func) + if !ok { + return nil, nil, ErrBadApplicationPackage + } + + signature := fn.Type().(*types.Signature) + if signature.Params().Len() != 1 || signature.Results().Len() != 0 || signature.TypeParams().Len() != 1 { + logger.Warningf("application.RegisterService params: %d, results: %d, typeparams: %d", signature.Params().Len(), signature.Results().Len(), signature.TypeParams().Len()) + return nil, nil, ErrBadApplicationPackage + } + + if !types.Identical(signature.Params().At(0).Type(), types.Universe.Lookup("string").Type()) { + logger.Warningf("application.RegisterService parameter type: %v", signature.Params().At(0).Type()) + return nil, nil, ErrBadApplicationPackage + } + + registerEvent = obj + continue + } + + // Detect application.NewService + if len(next) == 0 && obj.Name() == "NewService" && obj.Pkg().Path() == systemPaths.ApplicationPackage { + fn, ok := obj.(*types.Func) + if !ok { + return nil, nil, ErrBadApplicationPackage + } + + signature := fn.Type().(*types.Signature) + if signature.Params().Len() != 1 || signature.Results().Len() != 1 || tp.Len() != 1 { + logger.Warningf("application.NewService params: %d, results: %d, typeparams: %d", signature.Params().Len(), signature.Results().Len(), tp.Len()) + return nil, nil, ErrBadApplicationPackage + } + + // Schedule unique type param for analysis. + tgt := target{obj, 0} + scheduled[tgt] = true + next = append(next, targetInfo{target: tgt}) + continue + } + } + } + + // found tracks service types that have been found so far, for deduplication. + found := make(map[*types.TypeName]bool) + + return func(yield func(*types.TypeName) bool) { + // Process targets. + for len(next) > 0 { + // Pop one target off the next list. + tgt := next[len(next)-1] + next = next[:len(next)-1] + + // Prepare indirect binding message. + indirectMsg := "" + if tgt.cause.IsValid() { + indirectMsg = fmt.Sprintf(" (indirectly bound at %s)", tgt.cause) + } + + for _, instance := range instances[tgt.obj] { + // Retrieve type argument. + serviceType := types.Unalias(instance.args.At(tgt.param)) + + var named *types.Named + + switch t := serviceType.(type) { + case *types.Named: + // Process named type. + named = t.Origin() + + case *types.TypeParam: + // Schedule type parameter for analysis. + newtgt := target{owner[t.Obj()], t.Index()} + if !scheduled[newtgt] { + scheduled[newtgt] = true + + // Retrieve position of call to application.NewService + // that caused this target to be scheduled. + cause := tgt.cause + if !tgt.cause.IsValid() { + // This _is_ a call to application.NewService. + cause = instance.pos + } + + // Push on next list. + next = append(next, targetInfo{newtgt, cause}) + } + continue + + default: + logger.Warningf("%s: ignoring anonymous service type %s%s", instance.pos, serviceType, indirectMsg) + continue + } + + // Reject interfaces and generic types. + if types.IsInterface(named.Underlying()) { + logger.Warningf("%s: ignoring interface service type %s%s", instance.pos, named, indirectMsg) + continue + } else if named.TypeParams() != nil { + logger.Warningf("%s: ignoring generic service type %s%s", instance.pos, named, indirectMsg) + continue + } + + // Record and yield type object. + if !found[named.Obj()] { + found[named.Obj()] = true + if !yield(named.Obj()) { + return + } + } + } + } + }, registerEvent, nil +} diff --git a/v3/internal/generator/analyse_test.go b/v3/internal/generator/analyse_test.go new file mode 100644 index 000000000..06c593901 --- /dev/null +++ b/v3/internal/generator/analyse_test.go @@ -0,0 +1,135 @@ +package generator + +import ( + "encoding/json" + "errors" + "go/types" + "os" + "path" + "path/filepath" + "slices" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v3/internal/generator/config" +) + +func TestAnalyser(t *testing.T) { + type testParams struct { + name string + want []string + events bool + } + + // Gather tests from cases directory. + entries, err := os.ReadDir("testcases") + if err != nil { + t.Fatal(err) + } + + tests := make([]testParams, 0, len(entries)+1) + + for _, entry := range entries { + if !entry.IsDir() { + continue + } + + test := testParams{ + name: entry.Name(), + want: make([]string, 0), + } + + want, err := os.Open(filepath.Join("testcases", entry.Name(), "bound_types.json")) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + t.Fatal(err) + } + } else { + err = json.NewDecoder(want).Decode(&test.want) + want.Close() + if err != nil { + t.Fatal(err) + } + } + + for i := range test.want { + test.want[i] = path.Clean("github.com/wailsapp/wails/v3/internal/generator/testcases/" + test.name + test.want[i]) + } + slices.Sort(test.want) + + events, err := os.Open(filepath.Join("testcases", entry.Name(), "events.json")) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + t.Fatal(err) + } + } else { + err = json.NewDecoder(events).Decode(&test.events) + events.Close() + if err != nil { + t.Fatal(err) + } + } + + tests = append(tests, test) + } + + // Add global test. + { + all := testParams{ + name: "...", + } + + for _, test := range tests { + all.want = append(all.want, test.want...) + all.events = all.events || test.events + } + slices.Sort(all.want) + + tests = append(tests, all) + } + + // Resolve system package paths. + systemPaths, err := ResolveSystemPaths(nil) + if err != nil { + t.Fatal(err) + } + + for _, test := range tests { + pkgPattern := "github.com/wailsapp/wails/v3/internal/generator/testcases/" + test.name + + t.Run("pkg="+test.name, func(t *testing.T) { + pkgs, err := LoadPackages(nil, pkgPattern) + if err != nil { + t.Fatal(err) + } + + for _, pkg := range pkgs { + for _, err := range pkg.Errors { + pterm.Warning.Println(err) + } + } + + got := make([]string, 0) + + services, registerService, err := FindServices(pkgs, systemPaths, config.DefaultPtermLogger(nil)) + if err != nil { + t.Error(err) + } + + for obj := range services { + got = append(got, types.TypeString(obj.Type(), nil)) + } + + slices.Sort(got) + + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("Found services mismatch (-want +got):\n%s", diff) + } + + if test.events != (registerService != nil) { + t.Errorf("Found events mismatch: wanted=%t, got=%t", test.events, registerService != nil) + } + }) + } +} diff --git a/v3/internal/generator/collect/_reference/json_marshaler_behaviour.go b/v3/internal/generator/collect/_reference/json_marshaler_behaviour.go new file mode 100644 index 000000000..bc2c99524 --- /dev/null +++ b/v3/internal/generator/collect/_reference/json_marshaler_behaviour.go @@ -0,0 +1,166 @@ +// This example explores exhaustively the behaviour of encoding/json +// when handling types that implement marshaler interfaces. +// +// When encoding values, encoding/json makes no distinction +// between pointer and base types: +// if the base type implements a marshaler interface, +// be it with plain receiver or pointer receiver, +// json.Marshal picks it up. +// +// json.Marshaler is always preferred to encoding.TextMarshaler, +// without any consideration for the receiver type. +// +// When encoding map keys, on the other hand, +// the map key type must implement encoding.TextMarshaler strictly, +// i.e. if the interface is implemented only for plain receivers, +// pointer keys will not be accepted. +// +// json.Marshaler is ignored in this case, i.e. it makes no difference +// whether the key type implements it or not. +// +// Decoding behaviour w.r.t. unmarshaler types mirrors encoding behaviour. +package main + +import ( + "encoding/json" + "fmt" +) + +type A struct{} +type B struct{} +type C struct{} +type D struct{} +type E struct{} +type F struct{} +type G struct{} +type H struct{} + +type T struct { + A A + Ap *A + B B + Bp *B + C C + Cp *C + D D + Dp *D + E E + Ep *E + F F + Fp *F + G G + Gp *G + H H + Hp *H +} + +type MT struct { + //A map[A]bool // error + //Ap map[*A]bool // error + //B map[B]bool // error + //Bp map[*B]bool // error + C map[C]bool + Cp map[*C]bool + //D map[D]bool // error + Dp map[*D]bool + E map[E]bool + Ep map[*E]bool + //F map[F]bool // error + Fp map[*F]bool + G map[G]bool + Gp map[*G]bool + //H map[H]bool // error + Hp map[*H]bool +} + +func (A) MarshalJSON() ([]byte, error) { + return []byte(`"This is j A"`), nil +} + +func (*B) MarshalJSON() ([]byte, error) { + return []byte(`"This is j *B"`), nil +} + +func (C) MarshalText() ([]byte, error) { + return []byte(`This is t C`), nil +} + +func (*D) MarshalText() ([]byte, error) { + return []byte(`This is t *D`), nil +} + +func (E) MarshalJSON() ([]byte, error) { + return []byte(`"This is j E"`), nil +} + +func (E) MarshalText() ([]byte, error) { + return []byte(`This is t E`), nil +} + +func (F) MarshalJSON() ([]byte, error) { + return []byte(`"This is j F"`), nil +} + +func (*F) MarshalText() ([]byte, error) { + return []byte(`This is t *F`), nil +} + +func (*G) MarshalJSON() ([]byte, error) { + return []byte(`"This is j *G"`), nil +} + +func (G) MarshalText() ([]byte, error) { + return []byte(`This is t G`), nil +} + +func (*H) MarshalJSON() ([]byte, error) { + return []byte(`"This is j *H"`), nil +} + +func (*H) MarshalText() ([]byte, error) { + return []byte(`This is t *H`), nil +} + +func main() { + t := &T{ + A{}, + &A{}, + B{}, + &B{}, + C{}, + &C{}, + D{}, + &D{}, + E{}, + &E{}, + F{}, + &F{}, + G{}, + &G{}, + H{}, + &H{}, + } + enc, err := json.Marshal(t) + fmt.Println(string(enc), err) + + mt := &MT{ + //map[A]bool{A{}: true}, // error + //map[*A]bool{&A{}: true}, // error + //map[B]bool{B{}: true}, // error + //map[*B]bool{&B{}: true}, // error + map[C]bool{C{}: true}, + map[*C]bool{&C{}: true}, + //map[D]bool{D{}: true}, // error + map[*D]bool{&D{}: true}, + map[E]bool{E{}: true}, + map[*E]bool{&E{}: true}, + //map[F]bool{F{}: true}, // error + map[*F]bool{&F{}: true}, + map[G]bool{G{}: true}, + map[*G]bool{&G{}: true}, + //map[H]bool{H{}: true}, // error + map[*H]bool{&H{}: true}, + } + enc, err = json.Marshal(mt) + fmt.Println(string(enc), err) +} diff --git a/v3/internal/generator/collect/collector.go b/v3/internal/generator/collect/collector.go new file mode 100644 index 000000000..2485c1162 --- /dev/null +++ b/v3/internal/generator/collect/collector.go @@ -0,0 +1,119 @@ +package collect + +import ( + "go/ast" + "go/types" + "sync" + "sync/atomic" + + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/generator/config" + "golang.org/x/tools/go/packages" +) + +// Scheduler instances provide task scheduling +// for collection activities. +type Scheduler interface { + // Schedule should run the given function according + // to the implementation's preferred strategy. + // + // Scheduled tasks may call Schedule again; + // therefore, if tasks run concurrently, + // the implementation must support concurrent calls. + Schedule(task func()) +} + +// Info instances provide information about either +// a type-checker object, a struct type or a group of declarations. +type Info interface { + Object() types.Object + Type() types.Type + Node() ast.Node +} + +// Collector wraps all bookkeeping data structures that are needed +// to collect data about a set of packages, bindings and models. +type Collector struct { + // pkgs caches packages that have been registered for collection. + pkgs map[*types.Package]*PackageInfo + + // cache caches collected information about type-checker objects + // and declaration groups. Elements are [Info] instances. + cache sync.Map + + // events holds collected information about registered custom events. + events *EventMap + // appVoidType caches the application.Void named type that stands in for the void TS type. + appVoidType atomic.Value + + systemPaths *config.SystemPaths + options *flags.GenerateBindingsOptions + scheduler Scheduler + logger config.Logger +} + +// NewCollector initialises a new Collector instance for the given package set. +func NewCollector(pkgs []*packages.Package, registerEvent types.Object, systemPaths *config.SystemPaths, options *flags.GenerateBindingsOptions, scheduler Scheduler, logger config.Logger) *Collector { + collector := &Collector{ + pkgs: make(map[*types.Package]*PackageInfo, len(pkgs)), + + systemPaths: systemPaths, + options: options, + scheduler: scheduler, + logger: logger, + } + + // Register packages. + for _, pkg := range pkgs { + collector.pkgs[pkg.Types] = newPackageInfo(pkg, collector) + } + + // Initialise event map. + if !options.NoEvents { + collector.events = newEventMap(collector, registerEvent) + } + + return collector +} + +// fromCache returns the cached Info instance associated +// to the given type-checker object or declaration group. +// If none exists, a new one is created. +func (collector *Collector) fromCache(objectOrGroup any) Info { + entry, ok := collector.cache.Load(objectOrGroup) + info, _ := entry.(Info) + + if !ok { + switch x := objectOrGroup.(type) { + case *ast.GenDecl, *ast.ValueSpec, *ast.Field: + info = newGroupInfo(x.(ast.Node)) + + case *types.Const: + info = newConstInfo(collector, x) + + case *types.Func: + info = newMethodInfo(collector, x.Origin()) + + case *types.TypeName: + info = newTypeInfo(collector, x) + + case *types.Var: + if !x.IsField() { + panic("cache lookup for invalid object kind") + } + + info = newFieldInfo(collector, x.Origin()) + + case *types.Struct: + info = newStructInfo(collector, x) + + default: + panic("cache lookup for invalid object kind") + } + + entry, _ = collector.cache.LoadOrStore(objectOrGroup, info) + info, _ = entry.(Info) + } + + return info +} diff --git a/v3/internal/generator/collect/const.go b/v3/internal/generator/collect/const.go new file mode 100644 index 000000000..ad624bdad --- /dev/null +++ b/v3/internal/generator/collect/const.go @@ -0,0 +1,104 @@ +package collect + +import ( + "go/ast" + "go/constant" + "go/token" + "go/types" + "sync" +) + +// ConstInfo records information about a constant declaration. +// +// Read accesses to any public field are only safe +// if a call to [ConstInfo.Collect] has completed before the access, +// for example by calling it in the accessing goroutine +// or before spawning the accessing goroutine. +type ConstInfo struct { + Name string + Value any + + Pos token.Pos + Spec *GroupInfo + Decl *GroupInfo + + obj *types.Const + node ast.Node + + collector *Collector + once sync.Once +} + +func newConstInfo(collector *Collector, obj *types.Const) *ConstInfo { + return &ConstInfo{ + obj: obj, + collector: collector, + } +} + +// Const returns the unique ConstInfo instance +// associated to the given object within a collector. +// +// Const is safe for concurrent use. +func (collector *Collector) Const(obj *types.Const) *ConstInfo { + return collector.fromCache(obj).(*ConstInfo) +} + +func (info *ConstInfo) Object() types.Object { + return info.obj +} + +func (info *ConstInfo) Type() types.Type { + return info.obj.Type() +} + +func (info *ConstInfo) Node() ast.Node { + return info.Collect().node +} + +// Collect gathers information about the constant described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *ConstInfo) Collect() *ConstInfo { + if info == nil { + return nil + } + + info.once.Do(func() { + collector := info.collector + + info.Name = info.obj.Name() + info.Value = constant.Val(info.obj.Val()) + + info.Pos = info.obj.Pos() + + path := collector.findDeclaration(info.obj) + if path == nil { + collector.logger.Warningf( + "package %s: const %s: could not find declaration for constant object", + info.obj.Pkg().Path(), + info.Name, + ) + + // Provide dummy groups. + dummyGroup := newGroupInfo(nil).Collect() + info.Spec = dummyGroup + info.Decl = dummyGroup + return + } + + // path shape: *ast.ValueSpec, *ast.GenDecl, *ast.File + info.Spec = collector.fromCache(path[0]).(*GroupInfo).Collect() + info.Decl = collector.fromCache(path[1]).(*GroupInfo).Collect() + info.node = path[0] + }) + + return info +} diff --git a/v3/internal/generator/collect/declaration.go b/v3/internal/generator/collect/declaration.go new file mode 100644 index 000000000..7f2ccc989 --- /dev/null +++ b/v3/internal/generator/collect/declaration.go @@ -0,0 +1,246 @@ +package collect + +import ( + "cmp" + "go/ast" + "go/token" + "go/types" + "slices" +) + +// findDeclaration returns the AST spec or declaration +// that defines the given _global_ type-checker object. +// +// Specifically, the first element in the returned slice +// is the relevant spec or declaration, followed by its chain +// of parent nodes up to the declaring [ast.File]. +// +// If no corresponding declaration can be found within +// the set of registered packages, the returned slice is nil. +// +// Resulting node types are as follows: +// - global functions and concrete methods (*types.Func) +// map to *ast.FuncDecl nodes; +// - interface methods from global interfaces (*types.Func) +// map to *ast.Field nodes within their interface expression; +// - struct fields from global structs (*types.Var) +// map to *ast.Field nodes within their struct expression; +// - global constants and variables map to *ast.ValueSpec nodes; +// - global named types map to *ast.TypeSpec nodes; +// - for type parameters, the result is always nil; +// - for local objects defined within functions, +// field types, variable types or field values, +// the result is always nil; +// +// findDeclaration supports unsynchronised concurrent calls. +func (collector *Collector) findDeclaration(obj types.Object) (path []ast.Node) { + pkg := collector.Package(obj.Pkg()).Collect() + if pkg == nil { + return nil + } + + file := findEnclosingFile(pkg.Files, obj.Pos()) + if file == nil { + return nil + } + + // Find enclosing declaration. + decl := findEnclosingNode(file.Decls, obj.Pos()) + if decl == nil { + // Invalid position. + return nil + } + + var gen *ast.GenDecl + + switch d := decl.(type) { + case *ast.FuncDecl: + if obj.Pos() == d.Name.Pos() { + // Object is function. + return []ast.Node{decl, file} + } + + // Ignore local objects defined within function bodies. + return nil + + case *ast.BadDecl: + // What's up?? + return nil + + case *ast.GenDecl: + gen = d + } + + // Handle *ast.GenDecl + + // Find enclosing ast.Spec + spec := findEnclosingNode(gen.Specs, obj.Pos()) + if spec == nil { + // Invalid position. + return nil + } + + var def ast.Expr + + switch s := spec.(type) { + case *ast.ValueSpec: + if s.Names[0].Pos() <= obj.Pos() && obj.Pos() < s.Names[len(s.Names)-1].End() { + // Object is variable or constant. + return []ast.Node{spec, decl, file} + } + + // Ignore local objects defined within variable types/values. + return nil + + case *ast.TypeSpec: + if obj.Pos() == s.Name.Pos() { + // Object is named type. + return []ast.Node{spec, decl, file} + } + + if obj.Pos() < s.Type.Pos() || s.Type.End() <= obj.Pos() { + // Type param or invalid position. + return nil + } + + // Struct or interface field? + def = s.Type + } + + // Handle struct or interface field. + + var iface *ast.InterfaceType + + switch d := def.(type) { + case *ast.StructType: + // Find enclosing field + field := findEnclosingNode(d.Fields.List, obj.Pos()) + if field == nil { + // Invalid position. + return nil + } + + if len(field.Names) == 0 { + // Handle embedded field. + ftype := ast.Unparen(field.Type) + + // Unwrap pointer. + if ptr, ok := ftype.(*ast.StarExpr); ok { + ftype = ast.Unparen(ptr.X) + } + + // Unwrap generic instantiation. + switch t := field.Type.(type) { + case *ast.IndexExpr: + ftype = ast.Unparen(t.X) + case *ast.IndexListExpr: + ftype = ast.Unparen(t.X) + } + + // Unwrap selector. + if sel, ok := ftype.(*ast.SelectorExpr); ok { + ftype = sel.Sel + } + + // ftype must now be an identifier. + if obj.Pos() == ftype.Pos() { + // Object is this embedded field. + return []ast.Node{field, d.Fields, def, spec, decl, file} + } + } else if field.Names[0].Pos() <= obj.Pos() && obj.Pos() < field.Names[len(field.Names)-1].End() { + // Object is one of these fields. + return []ast.Node{field, d.Fields, def, spec, decl, file} + } + + // Ignore local objects defined within field types. + return nil + + case *ast.InterfaceType: + iface = d + + default: + // Other local object or invalid position. + return nil + } + + path = []ast.Node{file, decl, spec, def, iface.Methods} + + // Handle interface method. + for { + field := findEnclosingNode(iface.Methods.List, obj.Pos()) + if field == nil { + // Invalid position. + return nil + } + + path = append(path, field) + + if len(field.Names) == 0 { + // Handle embedded interface. + var ok bool + iface, ok = ast.Unparen(field.Type).(*ast.InterfaceType) + if !ok { + // Not embedded interface, ignore. + return nil + } + + path = append(path, iface, iface.Methods) + // Explore embedded interface. + + } else if field.Names[0].Pos() <= obj.Pos() && obj.Pos() < field.Names[len(field.Names)-1].End() { + // Object is one of these fields. + slices.Reverse(path) + return path + } else { + // Ignore local objects defined within interface method signatures. + return nil + } + } +} + +// findEnclosingFile finds the unique file in files, if any, that encloses the given position. +func findEnclosingFile(files []*ast.File, pos token.Pos) *ast.File { + // Perform a binary search to find the file enclosing the node. + // We can't use findEnclosingNode here because it is less accurate and less efficient with files. + fileIndex, exact := slices.BinarySearchFunc(files, pos, func(f *ast.File, p token.Pos) int { + return cmp.Compare(f.FileStart, p) + }) + + // If exact is true, pkg.Files[fileIndex] is the file we are looking for; + // otherwise, it is the first file whose start position is _after_ obj.Pos(). + if !exact { + fileIndex-- + } + + // When exact is false, the position might lie within an empty segment in between two files. + if fileIndex < 0 || files[fileIndex].FileEnd <= pos { + return nil + } + + return files[fileIndex] +} + +// findEnclosingNode finds the unique node in nodes, if any, +// that encloses the given position. +// +// It uses binary search and therefore expects +// the nodes slice to be sorted in source order. +func findEnclosingNode[S ~[]E, E ast.Node](nodes S, pos token.Pos) (node E) { + // Perform a binary search to find the nearest node. + index, exact := slices.BinarySearchFunc(nodes, pos, func(n E, p token.Pos) int { + return cmp.Compare(n.Pos(), p) + }) + + // If exact is true, nodes[index] is the node we are looking for; + // otherwise, it is the first node whose start position is _after_ pos. + if !exact { + index-- + } + + // When exact is false, the position might lie within an empty segment in between two nodes. + if index < 0 || nodes[index].End() <= pos { + return // zero value, nil in practice. + } + + return nodes[index] +} diff --git a/v3/internal/generator/collect/directive.go b/v3/internal/generator/collect/directive.go new file mode 100644 index 000000000..05293a640 --- /dev/null +++ b/v3/internal/generator/collect/directive.go @@ -0,0 +1,101 @@ +package collect + +import ( + "fmt" + "strings" + "unicode" + "unicode/utf8" + + "github.com/wailsapp/wails/v3/internal/flags" +) + +// IsDirective returns true if the given comment +// is a directive of the form //wails: + directive. +func IsDirective(comment string, directive string) bool { + if strings.HasPrefix(comment, "//wails:"+directive) { + length := len("//wails:") + len(directive) + if len(comment) == length { + return true + } + + next, _ := utf8.DecodeRuneInString(comment[length:]) + return unicode.IsSpace(next) + } + + return false +} + +// ParseDirective extracts the argument portion of a //wails: + directive comment. +func ParseDirective(comment string, directive string) string { + rawArg := comment[len("//wails:")+len(directive):] + + if directive != "inject" { + return strings.TrimSpace(rawArg) + } + + // wails:inject requires special parsing: + // do not trim all surrounding space, just the one space + // immediately after the directive name. + _, wsize := utf8.DecodeRuneInString(rawArg) + return rawArg[wsize:] +} + +// ParseCondition parses an optional two-character condition prefix +// for include or inject directives. +// It returns the argument stripped of the prefix and the resulting condition. +// If the condition is malformed, ParseCondition returns a non-nil error. +func ParseCondition(argument string) (string, Condition, error) { + cond, arg, present := strings.Cut(argument, ":") + if !present { + return cond, Condition{true, true, true, true}, nil + } + + if len(cond) != 2 || !strings.ContainsRune("*jt", rune(cond[0])) || !strings.ContainsRune("*ci", rune(cond[1])) { + return argument, + Condition{true, true, true, true}, + fmt.Errorf("invalid condition code '%s': expected format is '(*|j|t)(*|c|i)'", cond) + } + + condition := Condition{true, true, true, true} + + switch cond[0] { + case 'j': + condition.TS = false + case 't': + condition.JS = false + } + + switch cond[1] { + case 'c': + condition.Interfaces = false + case 'i': + condition.Classes = false + } + + return arg, condition, nil +} + +type Condition struct { + JS bool + TS bool + Classes bool + Interfaces bool +} + +// Satisfied returns true when the condition described by the receiver +// is satisfied by the given configuration. +func (cond Condition) Satisfied(options *flags.GenerateBindingsOptions) bool { + if options.TS { + if options.UseInterfaces { + return cond.TS && cond.Interfaces + } else { + return cond.TS && cond.Classes + } + } else { + if options.UseInterfaces { + return cond.JS && cond.Interfaces + } else { + return cond.JS && cond.Classes + } + } +} diff --git a/v3/internal/generator/collect/events.go b/v3/internal/generator/collect/events.go new file mode 100644 index 000000000..21e7cd7a9 --- /dev/null +++ b/v3/internal/generator/collect/events.go @@ -0,0 +1,283 @@ +package collect + +import ( + _ "embed" + "go/ast" + "go/constant" + "go/token" + "go/types" + "slices" + "strings" + "sync" + + "golang.org/x/tools/go/ast/astutil" +) + +type ( + // EventMap holds information about a set of custom events + // and their associated data types. + // + // Read accesses to any public field are only safe + // if a call to [EventMap.Collect] has completed before the access, + // for example by calling it in the accessing goroutine + // or before spawning the accessing goroutine. + EventMap struct { + Imports *ImportMap + Defs []*EventInfo + + registerEvent types.Object + collector *Collector + once sync.Once + } + + // EventInfo holds information about a single event definition. + EventInfo struct { + // Name is the name of the event. + Name string + + // Data is the data type the event has been registered with. + // It may be nil in case of conflicting definitions. + Data types.Type + + // Pos records the position + // of the first discovered definition for this event. + Pos token.Position + } +) + +func newEventMap(collector *Collector, registerEvent types.Object) *EventMap { + return &EventMap{ + registerEvent: registerEvent, + collector: collector, + } +} + +// EventMap returns the unique event map associated with the given collector, +// or nil if event collection is disabled. +func (collector *Collector) EventMap() *EventMap { + return collector.events +} + +// Stats returns statistics for this event map. +// It is an error to call stats before a call to [EventMap.Collect] has completed. +func (em *EventMap) Stats() *Stats { + return &Stats{ + NumEvents: len(em.Defs), + } +} + +// Collect gathers information for the event map described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (em *EventMap) Collect() *EventMap { + if em == nil { + return nil + } + + em.once.Do(func() { + // XXX: initialise the import map with a fake package. + // At present this works fine; let's hope it doesn't come back and haunt us later. + em.Imports = NewImportMap(&PackageInfo{ + Path: em.collector.systemPaths.InternalPackage, + collector: em.collector, + }) + + if em.registerEvent == nil { + return + } + + var ( + wg sync.WaitGroup + defs sync.Map + ) + + for pkg := range em.collector.Iterate { + if !pkg.IsOrImportsApp { + // Packages that are not, and do not import, the Wails application package + // cannot define any events. + continue + } + + wg.Add(1) + + // Process packages in parallel. + em.collector.scheduler.Schedule(func() { + em.collectEventsInPackage(pkg, &defs) + wg.Done() + }) + } + + wg.Wait() + + // Collect valid events. + em.Defs = slices.Collect(func(yield func(*EventInfo) bool) { + for _, v := range defs.Range { + event := v.(*EventInfo) + if event.Data != nil && !IsParametric(event.Data) { + if !yield(event) { + break + } + } + } + }) + + // Sort by name, ascending. + slices.SortFunc(em.Defs, func(a, b *EventInfo) int { + return strings.Compare(a.Name, b.Name) + }) + + // Record required types. + // This must be done at the end because: + // - [ImportMap.AddType] does not support concurrent calls, and + // - we only know the set of valid events after inspecting all definitions. + for _, def := range em.Defs { + em.Imports.AddType(def.Data) + } + }) + + return em +} + +func (em *EventMap) collectEventsInPackage(pkg *PackageInfo, defs *sync.Map) { + for ident, inst := range pkg.TypesInfo.Instances { + if pkg.TypesInfo.Uses[ident] != em.registerEvent { + continue + } + + file := findEnclosingFile(pkg.Collect().Files, ident.Pos()) + if file == nil { + em.collector.logger.Warningf( + "package %s: found event declaration with no associated source file", + pkg.Path, + ) + continue + } + + path, _ := astutil.PathEnclosingInterval(file, ident.Pos(), ident.End()) + if path[0] != ident { + em.collector.logger.Warningf( + "%v: event declaration not found in source file", + pkg.Fset.Position(ident.Pos()), + ) + continue + } + + // Walk up the path: *ast.Ident -> *ast.SelectorExpr? -> (*ast.IndexExpr | *ast.IndexListExpr)? -> *ast.CallExpr? + path = path[1:] + + if _, ok := path[0].(*ast.SelectorExpr); ok { + path = path[1:] + } + + if _, ok := path[0].(*ast.IndexExpr); ok { + path = path[1:] + } else if _, ok := path[0].(*ast.IndexListExpr); ok { + path = path[1:] + } + + call, ok := path[0].(*ast.CallExpr) + if !ok { + em.collector.logger.Warningf( + "%v: `application.RegisterEvent` is instantiated here but not called", + pkg.Fset.Position(path[0].Pos()), + ) + em.collector.logger.Warningf("events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly") + continue + } + + if len(call.Args) == 0 { + // Invalid calls result in compile-time failures and can be ignored safely. + continue + } + + eventName, ok := pkg.TypesInfo.Types[call.Args[0]] + if !ok || !types.AssignableTo(eventName.Type, types.Universe.Lookup("string").Type()) { + // Mistyped calls result in compile-time failures and can be ignored safely. + continue + } + + if eventName.Value == nil { + em.collector.logger.Warningf( + "%v: `application.RegisterEvent` called here with non-constant event name", + pkg.Fset.Position(call.Pos()), + ) + em.collector.logger.Warningf("dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only") + continue + } + + event := &EventInfo{ + Data: inst.TypeArgs.At(0), + Pos: pkg.Fset.Position(call.Pos()), + } + if eventName.Value.Kind() == constant.String { + event.Name = constant.StringVal(eventName.Value) + } else { + event.Name = eventName.Value.ExactString() + } + + if IsKnownEvent(event.Name) { + em.collector.logger.Warningf( + "%v: event '%s' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic", + event.Pos, + event.Name, + ) + continue + } + + if v, ok := defs.LoadOrStore(event.Name, event); ok { + prev := v.(*EventInfo) + if prev.Data != nil && !types.Identical(event.Data, prev.Data) { + next := &EventInfo{ + Name: prev.Name, + Pos: prev.Pos, + } + + if defs.CompareAndSwap(prev.Name, prev, next) { + em.collector.logger.Warningf("event '%s' has multiple conflicting definitions and will be ignored", event.Name) + em.collector.logger.Warningf( + "%v: event '%s' has one of multiple definitions here with data type %s", + prev.Pos, + prev.Name, + prev.Data, + ) + } + + prev = next + } + if prev.Data == nil { + em.collector.logger.Warningf( + "%v: event '%s' has one of multiple definitions here with data type %s", + event.Pos, + event.Name, + event.Data, + ) + } + continue + } + + // Emit unsupported type warnings only for first definition + if IsParametric(event.Data) { + em.collector.logger.Warningf( + "%v: data type %s for event '%s' contains unresolved type parameters and will be ignored`", + event.Pos, + event.Data, + event.Name, + ) + em.collector.logger.Warningf("generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only") + } else if types.IsInterface(event.Data) && !types.Identical(event.Data.Underlying(), typeAny) && !em.collector.IsVoidAlias(event.Data) { + em.collector.logger.Warningf( + "%v: data type %s for event '%s' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors", + event.Pos, + event.Data, + event.Name, + ) + } + } +} diff --git a/v3/internal/generator/collect/field.go b/v3/internal/generator/collect/field.go new file mode 100644 index 000000000..eed808114 --- /dev/null +++ b/v3/internal/generator/collect/field.go @@ -0,0 +1,102 @@ +package collect + +import ( + "go/ast" + "go/token" + "go/types" + "sync" +) + +// FieldInfo records information about a struct field declaration. +// +// Read accesses to any public field are only safe +// if a call to [FieldInfo.Collect] has completed before the access, +// for example by calling it in the accessing goroutine +// or before spawning the accessing goroutine. +type FieldInfo struct { + Name string + Blank bool + Embedded bool + + Pos token.Pos + Decl *GroupInfo + + obj *types.Var + node ast.Node + + collector *Collector + once sync.Once +} + +// newFieldInfo initialises a descriptor for the given field object. +func newFieldInfo(collector *Collector, obj *types.Var) *FieldInfo { + return &FieldInfo{ + obj: obj, + collector: collector, + } +} + +// Field returns the unique FieldInfo instance +// associated to the given object within a collector. +// +// Field is safe for concurrent use. +func (collector *Collector) Field(obj *types.Var) *FieldInfo { + if !obj.IsField() { + return nil + } + + return collector.fromCache(obj).(*FieldInfo) +} + +func (info *FieldInfo) Object() types.Object { + return info.obj +} + +func (info *FieldInfo) Type() types.Type { + return info.obj.Type() +} + +func (info *FieldInfo) Node() ast.Node { + return info.Collect().node +} + +// Collect gathers information about the struct field +// described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *FieldInfo) Collect() *FieldInfo { + if info == nil { + return nil + } + + info.once.Do(func() { + collector := info.collector + + info.Name = info.obj.Name() + info.Blank = (info.Name == "" || info.Name == "_") + info.Embedded = info.obj.Embedded() + + info.Pos = info.obj.Pos() + + path := collector.findDeclaration(info.obj) + if path == nil { + // Do not report failure: it is expected for anonymous struct fields. + // Provide dummy group. + info.Decl = newGroupInfo(nil).Collect() + return + } + + // path shape: *ast.Field, *ast.FieldList, ... + info.Decl = collector.fromCache(path[0]).(*GroupInfo).Collect() + info.node = path[0] + }) + + return info +} diff --git a/v3/internal/generator/collect/group.go b/v3/internal/generator/collect/group.go new file mode 100644 index 000000000..62d83304c --- /dev/null +++ b/v3/internal/generator/collect/group.go @@ -0,0 +1,95 @@ +package collect + +import ( + "go/ast" + "go/token" + "go/types" + "slices" + "sync" +) + +// GroupInfo records information about a group +// of type, field or constant declarations. +// This may be either a list of distinct specifications +// wrapped in parentheses, or a single specification +// declaring multiple fields or constants. +// +// Read accesses to any public field are only safe +// if a call to [GroupInfo.Collect] has completed before the access, +// for example by calling it in the accessing goroutine +// or before spawning the accessing goroutine. +type GroupInfo struct { + Pos token.Pos + Doc *ast.CommentGroup + + node ast.Node + + once sync.Once +} + +func newGroupInfo(node ast.Node) *GroupInfo { + return &GroupInfo{ + node: node, + } +} + +func (*GroupInfo) Object() types.Object { + return nil +} + +func (*GroupInfo) Type() types.Type { + return nil +} + +func (info *GroupInfo) Node() ast.Node { + return info.node +} + +// Collect gathers information about the declaration group +// described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *GroupInfo) Collect() *GroupInfo { + if info == nil { + return nil + } + + info.once.Do(func() { + switch n := info.node.(type) { + case *ast.GenDecl: + info.Pos = n.Pos() + info.Doc = n.Doc + + case *ast.ValueSpec: + info.Pos = n.Pos() + info.Doc = n.Doc + if info.Doc == nil { + info.Doc = n.Comment + } else if n.Comment != nil { + info.Doc = &ast.CommentGroup{ + List: slices.Concat(n.Doc.List, n.Comment.List), + } + } + + case *ast.Field: + info.Pos = n.Pos() + info.Doc = n.Doc + if info.Doc == nil { + info.Doc = n.Comment + } else if n.Comment != nil { + info.Doc = &ast.CommentGroup{ + List: slices.Concat(n.Doc.List, n.Comment.List), + } + } + } + }) + + return info +} diff --git a/v3/internal/generator/collect/imports.go b/v3/internal/generator/collect/imports.go new file mode 100644 index 000000000..349ef68af --- /dev/null +++ b/v3/internal/generator/collect/imports.go @@ -0,0 +1,288 @@ +package collect + +import ( + "go/types" + "path/filepath" + + "golang.org/x/tools/go/types/typeutil" +) + +type ( + // ImportMap records deduplicated imports by a binding or models module. + // It computes relative import paths and assigns import names, + // taking care to avoid collisions. + ImportMap struct { + // Self records the path of the importing package. + Self string + + // ImportModels records whether models from the current package may be needed. + ImportModels bool + + // External records information about each imported package, + // keyed by package path. + External map[string]ImportInfo + + // counters holds the occurence count for each package name in External. + counters map[string]int + collector *Collector + } + + // ImportInfo records information about a single import. + ImportInfo struct { + Name string + Index int // Progressive number for identically named imports, starting from 0 for each distinct name. + RelPath string + } +) + +// NewImportMap initialises an import map for the given importer package. +// The argument may be nil, in which case import paths will be relative +// to the root output directory. +func NewImportMap(importer *PackageInfo) *ImportMap { + var ( + self string + collector *Collector + ) + if importer != nil { + self = importer.Path + collector = importer.collector + } + + return &ImportMap{ + Self: self, + + External: make(map[string]ImportInfo), + + counters: make(map[string]int), + collector: collector, + } +} + +// Merge merges the given import map into the receiver. +// The importing package must be the same. +func (imports *ImportMap) Merge(other *ImportMap) { + if other.Self != imports.Self { + panic("cannot merge import maps with different importing package") + } + + if other.ImportModels { + imports.ImportModels = true + } + + for path, info := range other.External { + if _, ok := imports.External[path]; ok { + continue + } + + counter := imports.counters[info.Name] + imports.counters[info.Name] = counter + 1 + + imports.External[path] = ImportInfo{ + Name: info.Name, + Index: counter, + RelPath: info.RelPath, + } + } +} + +// Add adds the given package to the import map if not already present, +// choosing import names so as to avoid collisions. +// +// Add does not support unsynchronised concurrent calls +// on the same receiver. +func (imports *ImportMap) Add(pkg *PackageInfo) { + if pkg.Path == imports.Self { + // Do not import self. + return + } + + if imports.External[pkg.Path].Name != "" { + // Package already imported. + return + } + + // Fetch and update counter for name. + counter := imports.counters[pkg.Name] + imports.counters[pkg.Name] = counter + 1 + + // Always add counters to + imports.External[pkg.Path] = ImportInfo{ + Name: pkg.Name, + Index: counter, + RelPath: computeImportPath(imports.Self, pkg.Path), + } +} + +// AddType adds all dependencies of the given type to the import map +// and marks all referenced named types as models. +// +// It is a runtime error to call AddType on an ImportMap +// created with nil importing package. +// +// AddType does not support unsynchronised concurrent calls +// on the same receiver. +func (imports *ImportMap) AddType(typ types.Type) { + imports.addTypeImpl(typ, new(typeutil.Map)) +} + +// addTypeImpl provides the actual implementation of AddType. +// The visited parameter is used to break cycles. +func (imports *ImportMap) addTypeImpl(typ types.Type, visited *typeutil.Map) { + collector := imports.collector + if collector == nil { + panic("AddType called on ImportMap with nil collector") + } + + for { // Avoid recursion where possible. + switch t := typ.(type) { + case *types.Alias, *types.Named: + if visited.Set(typ, true) != nil { + // Break type cycles. + return + } + + obj := typ.(interface{ Obj() *types.TypeName }).Obj() + if obj.Pkg() == nil { + // Ignore universe type. + return + } + + // Special case: application.Void will render as TS void hence no dependencies and no model + if collector.IsVoidAlias(obj) { + return + } + + if obj.Pkg().Path() == imports.Self { + imports.ImportModels = true + } + + // Record model. + imports.collector.Model(obj) + + // Import parent package. + imports.Add(collector.Package(obj.Pkg())) + + instance, _ := typ.(interface{ TypeArgs() *types.TypeList }) + if instance != nil { + // Record type argument dependencies. + if targs := instance.TypeArgs(); targs != nil { + for i := range targs.Len() { + imports.addTypeImpl(targs.At(i), visited) + } + } + } + + if collector.options.UseInterfaces { + // No creation/initialisation code required. + return + } + + if _, isAlias := typ.(*types.Alias); isAlias { + // Aliased type might be needed during + // JS value creation and initialisation. + typ = types.Unalias(typ) + break + } + + if IsClass(typ) || IsAny(typ) || IsStringAlias(typ) { + return + } + + // If named type does not map to a class, unknown type or string, + // its underlying type may be needed during JS value creation. + typ = typ.Underlying() + + case *types.Basic: + switch { + case t.Info()&(types.IsBoolean|types.IsInteger|types.IsUnsigned|types.IsFloat|types.IsString) != 0: + break + case t.Info()&types.IsComplex != 0: + collector.logger.Warningf("package %s: complex types are not supported by encoding/json", imports.Self) + default: + collector.logger.Warningf("package %s: unknown basic type %s: please report this to Wails maintainers", imports.Self, typ) + } + return + + case *types.Array, *types.Pointer, *types.Slice: + typ = typ.(interface{ Elem() types.Type }).Elem() + + case *types.Chan: + collector.logger.Warningf("package %s: channel types are not supported by encoding/json", imports.Self) + return + + case *types.Map: + if IsMapKey(t.Key()) { + if IsStringAlias(t.Key()) { + // This model type is always rendered as a string alias, + // hence we can generate it and use it as a type for JS object keys. + imports.addTypeImpl(t.Key(), visited) + } + } else if IsTypeParam(t.Key()) { + // In some cases, type params or pointers to type params + // may be valid as map keys, but not for all instantiations. + // When that happens, emit a softer warning. + collector.logger.Warningf( + "package %s: type %s is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors", + imports.Self, types.TypeString(t.Key(), nil), + ) + } else { + collector.logger.Warningf( + "package %s: type %s is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors", + imports.Self, types.TypeString(t.Key(), nil), + ) + } + + typ = t.Elem() + + case *types.Signature: + collector.logger.Warningf("package %s: function types are not supported by encoding/json", imports.Self) + return + + case *types.Struct: + if t.NumFields() == 0 || MaybeJSONMarshaler(typ) != NonMarshaler || MaybeTextMarshaler(typ) != NonMarshaler { + // Struct is empty, or marshals to custom JSON (any) or string. + return + } + + // Retrieve struct info and ensure it is complete. + info := collector.Struct(t).Collect() + + if len(info.Fields) == 0 { + // No visible fields. + return + } + + // Add field dependencies. + for i := range len(info.Fields) - 1 { + imports.addTypeImpl(info.Fields[i].Type, visited) + } + + // Process last field without recursion. + typ = info.Fields[len(info.Fields)-1].Type + + case *types.Interface, *types.TypeParam: + // No dependencies. + return + + default: + collector.logger.Warningf("package %s: unknown or unexpected type %s: please report this to Wails maintainers", imports.Self, typ) + return + } + } +} + +// computeImportPath returns the shortest relative import path +// through which the importer package can reference the imported one. +func computeImportPath(importer string, imported string) string { + rel, err := filepath.Rel(importer, imported) + if err != nil { + panic(err) + } + + rel = filepath.ToSlash(rel) + if rel[0] == '.' { + return rel + } else { + return "./" + rel + } +} diff --git a/v3/internal/generator/collect/index.go b/v3/internal/generator/collect/index.go new file mode 100644 index 000000000..ae4d31240 --- /dev/null +++ b/v3/internal/generator/collect/index.go @@ -0,0 +1,121 @@ +package collect + +import ( + "slices" + "strings" +) + +// PackageIndex lists all services, models and unexported models +// to be generated from a single package. +// +// When obtained through a call to [PackageInfo.Index], +// each service and model appears at most once; +// services and models are sorted +// by internal property (all exported first), then by name. +type PackageIndex struct { + Package *PackageInfo + + Services []*ServiceInfo + HasExportedServices bool // If true, there is at least one exported service. + + Models []*ModelInfo + HasExportedModels bool // If true, there is at least one exported model. +} + +// Index computes a [PackageIndex] for the selected language from the list +// of generated services and models and regenerates cached stats. +// +// Services and models appear at most once in the returned slices; +// services are sorted by name; +// exported models precede all unexported ones +// and both ranges are sorted by name. +// +// Index calls info.Collect, and therefore provides the same guarantees. +// It is safe for concurrent use. +func (info *PackageInfo) Index(TS bool) (index *PackageIndex) { + // Init index. + index = &PackageIndex{ + Package: info.Collect(), + } + + // Init stats + stats := &Stats{ + NumPackages: 1, + } + + // Gather services. + for _, value := range info.services.Range { + service := value.(*ServiceInfo) + if !service.IsEmpty() { + index.Services = append(index.Services, service) + // Mark presence of exported services + if !service.Internal { + index.HasExportedServices = true + } + // Update service stats. + stats.NumServices++ + stats.NumMethods += len(service.Methods) + } + } + + // Sort services by internal property (exported first), then by name. + slices.SortFunc(index.Services, func(s1 *ServiceInfo, s2 *ServiceInfo) int { + if s1 == s2 { + return 0 + } + + if s1.Internal != s2.Internal { + if s1.Internal { + return 1 + } else { + return -1 + } + } + + return strings.Compare(s1.Name, s2.Name) + }) + + // Gather models. + for _, value := range info.models.Range { + model := value.(*ModelInfo) + index.Models = append(index.Models, model) + // Mark presence of exported models + if !model.Internal { + index.HasExportedModels = true + } + // Update model stats. + if len(model.Values) > 0 { + stats.NumEnums++ + } else { + stats.NumModels++ + } + } + + // Sort models by internal property (exported first), then by name. + slices.SortFunc(index.Models, func(m1 *ModelInfo, m2 *ModelInfo) int { + if m1 == m2 { + return 0 + } + + if m1.Internal != m2.Internal { + if m1.Internal { + return 1 + } else { + return -1 + } + } + + return strings.Compare(m1.Name, m2.Name) + }) + + // Cache stats + info.stats.Store(stats) + + return +} + +// IsEmpty returns true if the given index +// contains no data for the selected language. +func (index *PackageIndex) IsEmpty() bool { + return !index.HasExportedServices && !index.HasExportedModels && len(index.Package.Injections) == 0 +} diff --git a/v3/internal/generator/collect/known_events.go b/v3/internal/generator/collect/known_events.go new file mode 100644 index 000000000..857071585 --- /dev/null +++ b/v3/internal/generator/collect/known_events.go @@ -0,0 +1,222 @@ +package collect + +func IsKnownEvent(name string) bool { + _, ok := knownEvents[name] + return ok +} + +var knownEvents = map[string]struct{}{ + "common:ApplicationOpenedWithFile": {}, + "common:ApplicationStarted": {}, + "common:ApplicationLaunchedWithUrl": {}, + "common:ThemeChanged": {}, + "common:WindowClosing": {}, + "common:WindowDidMove": {}, + "common:WindowDidResize": {}, + "common:WindowDPIChanged": {}, + "common:WindowFilesDropped": {}, + "common:WindowFocus": {}, + "common:WindowFullscreen": {}, + "common:WindowHide": {}, + "common:WindowLostFocus": {}, + "common:WindowMaximise": {}, + "common:WindowMinimise": {}, + "common:WindowToggleFrameless": {}, + "common:WindowRestore": {}, + "common:WindowRuntimeReady": {}, + "common:WindowShow": {}, + "common:WindowUnFullscreen": {}, + "common:WindowUnMaximise": {}, + "common:WindowUnMinimise": {}, + "common:WindowZoom": {}, + "common:WindowZoomIn": {}, + "common:WindowZoomOut": {}, + "common:WindowZoomReset": {}, + "linux:ApplicationStartup": {}, + "linux:SystemThemeChanged": {}, + "linux:WindowDeleteEvent": {}, + "linux:WindowDidMove": {}, + "linux:WindowDidResize": {}, + "linux:WindowFocusIn": {}, + "linux:WindowFocusOut": {}, + "linux:WindowLoadStarted": {}, + "linux:WindowLoadRedirected": {}, + "linux:WindowLoadCommitted": {}, + "linux:WindowLoadFinished": {}, + "mac:ApplicationDidBecomeActive": {}, + "mac:ApplicationDidChangeBackingProperties": {}, + "mac:ApplicationDidChangeEffectiveAppearance": {}, + "mac:ApplicationDidChangeIcon": {}, + "mac:ApplicationDidChangeOcclusionState": {}, + "mac:ApplicationDidChangeScreenParameters": {}, + "mac:ApplicationDidChangeStatusBarFrame": {}, + "mac:ApplicationDidChangeStatusBarOrientation": {}, + "mac:ApplicationDidChangeTheme": {}, + "mac:ApplicationDidFinishLaunching": {}, + "mac:ApplicationDidHide": {}, + "mac:ApplicationDidResignActive": {}, + "mac:ApplicationDidUnhide": {}, + "mac:ApplicationDidUpdate": {}, + "mac:ApplicationShouldHandleReopen": {}, + "mac:ApplicationWillBecomeActive": {}, + "mac:ApplicationWillFinishLaunching": {}, + "mac:ApplicationWillHide": {}, + "mac:ApplicationWillResignActive": {}, + "mac:ApplicationWillTerminate": {}, + "mac:ApplicationWillUnhide": {}, + "mac:ApplicationWillUpdate": {}, + "mac:MenuDidAddItem": {}, + "mac:MenuDidBeginTracking": {}, + "mac:MenuDidClose": {}, + "mac:MenuDidDisplayItem": {}, + "mac:MenuDidEndTracking": {}, + "mac:MenuDidHighlightItem": {}, + "mac:MenuDidOpen": {}, + "mac:MenuDidPopUp": {}, + "mac:MenuDidRemoveItem": {}, + "mac:MenuDidSendAction": {}, + "mac:MenuDidSendActionToItem": {}, + "mac:MenuDidUpdate": {}, + "mac:MenuWillAddItem": {}, + "mac:MenuWillBeginTracking": {}, + "mac:MenuWillDisplayItem": {}, + "mac:MenuWillEndTracking": {}, + "mac:MenuWillHighlightItem": {}, + "mac:MenuWillOpen": {}, + "mac:MenuWillPopUp": {}, + "mac:MenuWillRemoveItem": {}, + "mac:MenuWillSendAction": {}, + "mac:MenuWillSendActionToItem": {}, + "mac:MenuWillUpdate": {}, + "mac:WebViewDidCommitNavigation": {}, + "mac:WebViewDidFinishNavigation": {}, + "mac:WebViewDidReceiveServerRedirectForProvisionalNavigation": {}, + "mac:WebViewDidStartProvisionalNavigation": {}, + "mac:WindowDidBecomeKey": {}, + "mac:WindowDidBecomeMain": {}, + "mac:WindowDidBeginSheet": {}, + "mac:WindowDidChangeAlpha": {}, + "mac:WindowDidChangeBackingLocation": {}, + "mac:WindowDidChangeBackingProperties": {}, + "mac:WindowDidChangeCollectionBehavior": {}, + "mac:WindowDidChangeEffectiveAppearance": {}, + "mac:WindowDidChangeOcclusionState": {}, + "mac:WindowDidChangeOrderingMode": {}, + "mac:WindowDidChangeScreen": {}, + "mac:WindowDidChangeScreenParameters": {}, + "mac:WindowDidChangeScreenProfile": {}, + "mac:WindowDidChangeScreenSpace": {}, + "mac:WindowDidChangeScreenSpaceProperties": {}, + "mac:WindowDidChangeSharingType": {}, + "mac:WindowDidChangeSpace": {}, + "mac:WindowDidChangeSpaceOrderingMode": {}, + "mac:WindowDidChangeTitle": {}, + "mac:WindowDidChangeToolbar": {}, + "mac:WindowDidDeminiaturize": {}, + "mac:WindowDidEndSheet": {}, + "mac:WindowDidEnterFullScreen": {}, + "mac:WindowDidEnterVersionBrowser": {}, + "mac:WindowDidExitFullScreen": {}, + "mac:WindowDidExitVersionBrowser": {}, + "mac:WindowDidExpose": {}, + "mac:WindowDidFocus": {}, + "mac:WindowDidMiniaturize": {}, + "mac:WindowDidMove": {}, + "mac:WindowDidOrderOffScreen": {}, + "mac:WindowDidOrderOnScreen": {}, + "mac:WindowDidResignKey": {}, + "mac:WindowDidResignMain": {}, + "mac:WindowDidResize": {}, + "mac:WindowDidUpdate": {}, + "mac:WindowDidUpdateAlpha": {}, + "mac:WindowDidUpdateCollectionBehavior": {}, + "mac:WindowDidUpdateCollectionProperties": {}, + "mac:WindowDidUpdateShadow": {}, + "mac:WindowDidUpdateTitle": {}, + "mac:WindowDidUpdateToolbar": {}, + "mac:WindowDidZoom": {}, + "mac:WindowFileDraggingEntered": {}, + "mac:WindowFileDraggingExited": {}, + "mac:WindowFileDraggingPerformed": {}, + "mac:WindowHide": {}, + "mac:WindowMaximise": {}, + "mac:WindowUnMaximise": {}, + "mac:WindowMinimise": {}, + "mac:WindowUnMinimise": {}, + "mac:WindowShouldClose": {}, + "mac:WindowShow": {}, + "mac:WindowWillBecomeKey": {}, + "mac:WindowWillBecomeMain": {}, + "mac:WindowWillBeginSheet": {}, + "mac:WindowWillChangeOrderingMode": {}, + "mac:WindowWillClose": {}, + "mac:WindowWillDeminiaturize": {}, + "mac:WindowWillEnterFullScreen": {}, + "mac:WindowWillEnterVersionBrowser": {}, + "mac:WindowWillExitFullScreen": {}, + "mac:WindowWillExitVersionBrowser": {}, + "mac:WindowWillFocus": {}, + "mac:WindowWillMiniaturize": {}, + "mac:WindowWillMove": {}, + "mac:WindowWillOrderOffScreen": {}, + "mac:WindowWillOrderOnScreen": {}, + "mac:WindowWillResignMain": {}, + "mac:WindowWillResize": {}, + "mac:WindowWillUnfocus": {}, + "mac:WindowWillUpdate": {}, + "mac:WindowWillUpdateAlpha": {}, + "mac:WindowWillUpdateCollectionBehavior": {}, + "mac:WindowWillUpdateCollectionProperties": {}, + "mac:WindowWillUpdateShadow": {}, + "mac:WindowWillUpdateTitle": {}, + "mac:WindowWillUpdateToolbar": {}, + "mac:WindowWillUpdateVisibility": {}, + "mac:WindowWillUseStandardFrame": {}, + "mac:WindowZoomIn": {}, + "mac:WindowZoomOut": {}, + "mac:WindowZoomReset": {}, + "windows:APMPowerSettingChange": {}, + "windows:APMPowerStatusChange": {}, + "windows:APMResumeAutomatic": {}, + "windows:APMResumeSuspend": {}, + "windows:APMSuspend": {}, + "windows:ApplicationStarted": {}, + "windows:SystemThemeChanged": {}, + "windows:WebViewNavigationCompleted": {}, + "windows:WindowActive": {}, + "windows:WindowBackgroundErase": {}, + "windows:WindowClickActive": {}, + "windows:WindowClosing": {}, + "windows:WindowDidMove": {}, + "windows:WindowDidResize": {}, + "windows:WindowDPIChanged": {}, + "windows:WindowDragDrop": {}, + "windows:WindowDragEnter": {}, + "windows:WindowDragLeave": {}, + "windows:WindowDragOver": {}, + "windows:WindowEndMove": {}, + "windows:WindowEndResize": {}, + "windows:WindowFullscreen": {}, + "windows:WindowHide": {}, + "windows:WindowInactive": {}, + "windows:WindowKeyDown": {}, + "windows:WindowKeyUp": {}, + "windows:WindowKillFocus": {}, + "windows:WindowNonClientHit": {}, + "windows:WindowNonClientMouseDown": {}, + "windows:WindowNonClientMouseLeave": {}, + "windows:WindowNonClientMouseMove": {}, + "windows:WindowNonClientMouseUp": {}, + "windows:WindowPaint": {}, + "windows:WindowRestore": {}, + "windows:WindowSetFocus": {}, + "windows:WindowShow": {}, + "windows:WindowStartMove": {}, + "windows:WindowStartResize": {}, + "windows:WindowUnFullscreen": {}, + "windows:WindowZOrderChanged": {}, + "windows:WindowMinimise": {}, + "windows:WindowUnMinimise": {}, + "windows:WindowMaximise": {}, + "windows:WindowUnMaximise": {}, +} diff --git a/v3/internal/generator/collect/method.go b/v3/internal/generator/collect/method.go new file mode 100644 index 000000000..adff7aaaa --- /dev/null +++ b/v3/internal/generator/collect/method.go @@ -0,0 +1,115 @@ +package collect + +import ( + "go/ast" + "go/types" + "sync" +) + +// MethodInfo records information about a method declaration. +// +// Read accesses to any public field are only safe +// if a call to [MethodInfo.Collect] has completed before the access, +// for example by calling it in the accessing goroutine +// or before spawning the accessing goroutine. +type MethodInfo struct { + Name string + + // Abstract is true when the described method belongs to an interface. + Abstract bool + + Doc *ast.CommentGroup + Decl *GroupInfo + + obj *types.Func + node ast.Node + + collector *Collector + once sync.Once +} + +func newMethodInfo(collector *Collector, obj *types.Func) *MethodInfo { + return &MethodInfo{ + obj: obj, + collector: collector, + } +} + +// Method returns the unique MethodInfo instance +// associated to the given object within a collector. +// +// Method is safe for concurrent use. +func (collector *Collector) Method(obj *types.Func) *MethodInfo { + return collector.fromCache(obj).(*MethodInfo) +} + +func (info *MethodInfo) Object() types.Object { + return info.obj +} + +func (info *MethodInfo) Type() types.Type { + return info.obj.Type() +} + +func (info *MethodInfo) Node() ast.Node { + return info.Collect().node +} + +// Collect gathers information about the method described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *MethodInfo) Collect() *MethodInfo { + if info == nil { + return nil + } + + info.once.Do(func() { + collector := info.collector + + info.Name = info.obj.Name() + + path := collector.findDeclaration(info.obj) + if path == nil { + recv := "" + if info.obj.Type() != nil { + recv = info.obj.Type().(*types.Signature).Recv().Type().String() + "." + } + + collector.logger.Warningf( + "package %s: method %s%s: could not find declaration for method object", + info.obj.Pkg().Path(), + recv, + info.obj.Name(), + ) + + // Provide dummy group. + info.Decl = newGroupInfo(nil).Collect() + return + } + + // path shape: *ast.FuncDecl/*ast.Field, ... + info.node = path[0] + + // Retrieve doc comments. + switch n := info.node.(type) { + case *ast.FuncDecl: + // Concrete method. + info.Doc = n.Doc + info.Decl = newGroupInfo(nil).Collect() // Provide dummy group. + + case *ast.Field: + // Abstract method. + info.Abstract = true + info.Decl = newGroupInfo(path[0]).Collect() + } + }) + + return info +} diff --git a/v3/internal/generator/collect/model.go b/v3/internal/generator/collect/model.go new file mode 100644 index 000000000..4e5b32cb5 --- /dev/null +++ b/v3/internal/generator/collect/model.go @@ -0,0 +1,381 @@ +package collect + +import ( + "cmp" + "go/ast" + "go/constant" + "go/types" + "slices" + "strings" + "sync" +) + +type ( + // ModelInfo records all information that is required + // to render JS/TS code for a model type. + // + // Read accesses to exported fields are only safe + // if a call to [ModelInfo.Collect] has completed before the access, + // for example by calling it in the accessing goroutine + // or before spawning the accessing goroutine. + ModelInfo struct { + *TypeInfo + + // Internal records whether the model + // should be exported by the index file. + Internal bool + + // Imports records dependencies for this model. + Imports *ImportMap + + // Type records the target type for an alias or derived model, + // the underlying type for an enum. + Type types.Type + + // Fields records the property list for a class or struct alias model, + // in order of declaration and grouped by their declaring [ast.Field]. + Fields [][]*ModelFieldInfo + + // Values records the value list for an enum model, + // in order of declaration and grouped + // by their declaring [ast.GenDecl] and [ast.ValueSpec]. + Values [][][]*ConstInfo + + // TypeParams records type parameter names for generic models. + TypeParams []string + + // Predicates caches the value of all type predicates for this model. + // + // WARN: whenever working with a generic uninstantiated model type, + // use these instead of invoking predicate functions, + // which may incur a large performance penalty. + Predicates Predicates + + collector *Collector + once sync.Once + } + + // ModelFieldInfo holds extended information + // about a struct field in a model type. + ModelFieldInfo struct { + *StructField + *FieldInfo + } + + // Predicates caches the value of all type predicates. + Predicates struct { + IsJSONMarshaler MarshalerKind + MaybeJSONMarshaler MarshalerKind + IsTextMarshaler MarshalerKind + MaybeTextMarshaler MarshalerKind + IsMapKey bool + IsTypeParam bool + IsStringAlias bool + IsClass bool + IsAny bool + } +) + +func newModelInfo(collector *Collector, obj *types.TypeName) *ModelInfo { + return &ModelInfo{ + TypeInfo: collector.Type(obj), + collector: collector, + } +} + +// Model retrieves the the unique [ModelInfo] instance +// associated to the given type object within a Collector. +// If none is present, Model initialises a new one +// registers it for code generation +// and schedules background collection activity. +// +// Model is safe for concurrent use. +func (collector *Collector) Model(obj *types.TypeName) *ModelInfo { + pkg := collector.Package(obj.Pkg()) + if pkg == nil { + return nil + } + + model, present := pkg.recordModel(obj) + if !present { + collector.scheduler.Schedule(func() { model.Collect() }) + } + + return model +} + +// Collect gathers information for the model described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *ModelInfo) Collect() *ModelInfo { + if info == nil { + return nil + } + + // Changes in the following logic must be reflected adequately + // by the predicates in properties.go, by ImportMap.AddType + // and by all render.Module methods. + + info.once.Do(func() { + collector := info.collector + obj := info.Object().(*types.TypeName) + + typ := obj.Type() + + // Collect type information. + info.TypeInfo.Collect() + + // Initialise import map. + info.Imports = NewImportMap(collector.Package(obj.Pkg())) + + // Setup fallback type. + info.Type = types.Universe.Lookup("any").Type() + + // Record whether the model should be exported. + info.Internal = !obj.Exported() + + // Parse directives. + for _, doc := range []*ast.CommentGroup{info.Doc, info.Decl.Doc} { + if doc == nil { + continue + } + for _, comment := range doc.List { + if IsDirective(comment.Text, "internal") { + info.Internal = true + } + } + } + + // Record type parameter names. + var isGeneric bool + if generic, ok := typ.(interface{ TypeParams() *types.TypeParamList }); ok { + tparams := generic.TypeParams() + isGeneric = tparams != nil + + if isGeneric && tparams.Len() > 0 { + info.TypeParams = make([]string, tparams.Len()) + for i := range tparams.Len() { + info.TypeParams[i] = tparams.At(i).Obj().Name() + } + } + } + + // Precompute predicates. + // Preinstantiate typ to avoid repeated instantiations in predicate code. + ityp := instantiate(typ) + info.Predicates = Predicates{ + IsJSONMarshaler: IsJSONMarshaler(ityp), + MaybeJSONMarshaler: MaybeJSONMarshaler(ityp), + IsTextMarshaler: IsTextMarshaler(ityp), + MaybeTextMarshaler: MaybeTextMarshaler(ityp), + IsMapKey: IsMapKey(ityp), + IsTypeParam: IsTypeParam(ityp), + IsStringAlias: IsStringAlias(ityp), + IsClass: IsClass(ityp), + IsAny: IsAny(ityp), + } + + var def types.Type + var constants []*types.Const + + switch t := typ.(type) { + case *types.Alias: + // Model is an alias: take rhs as definition. + // It is important not to skip alias chains with [types.Unalias] + // because in doing so we could end up with a private type from another package. + def = t.Rhs() + + // Test for constants with alias type, + // but only when non-generic alias resolves to a basic type + // (hence not to e.g. a named type). + if basic, ok := types.Unalias(def).(*types.Basic); ok { + if !isGeneric && basic.Info()&types.IsConstType != 0 && basic.Info()&types.IsComplex == 0 { + // Non-generic alias resolves to a representable constant type: + // look for defined constants whose type is exactly the alias typ. + for _, name := range obj.Pkg().Scope().Names() { + if cnst, ok := obj.Pkg().Scope().Lookup(name).(*types.Const); ok { + alias, isAlias := cnst.Type().(*types.Alias) + if isAlias && cnst.Val().Kind() != constant.Unknown && alias.Obj() == t.Obj() { + constants = append(constants, cnst) + } + } + } + } + } + + case *types.Named: + // Model is a named type: + // jump directly to underlying type to match go semantics, + // i.e. do not render named types as aliases for other named types. + def = typ.Underlying() + + // Check whether it implements marshaler interfaces or has defined constants. + if info.Predicates.MaybeJSONMarshaler != NonMarshaler { + // Type marshals to a custom value of unknown shape. + // If it has explicit custom marshaling logic, render it as any; + // otherwise, delegate to the underlying type that must be the actual [json.Marshaler]. + if info.Predicates.MaybeJSONMarshaler == ExplicitMarshaler { + return + } + } else if info.Predicates.MaybeTextMarshaler != NonMarshaler { + // Type marshals to a custom string of unknown shape. + // If it has explicit custom marshaling logic, render it as string; + // otherwise, delegate to the underlying type that must be the actual [encoding.TextMarshaler]. + // + // One exception must be made for situations + // where the underlying type is a [json.Marshaler] but the model is not: + // in that case, we cannot delegate to the underlying type either. + // Note that in such a case the underlying type is never a pointer or interface, + // because those cannot have explicitly defined methods, + // hence it would not possible for the model not to be a [json.Marshaler] + // while the underlying type is. + if info.Predicates.MaybeTextMarshaler == ExplicitMarshaler || MaybeJSONMarshaler(def) != NonMarshaler { + info.Type = types.Typ[types.String] + return + } + } else if basic, ok := def.Underlying().(*types.Basic); ok { + // Test for enums (excluding marshalers and generic types). + if !isGeneric && basic.Info()&types.IsConstType != 0 && basic.Info()&types.IsComplex == 0 { + // Named type is defined as a representable constant type: + // look for defined constants of that named type. + for _, name := range obj.Pkg().Scope().Names() { + if cnst, ok := obj.Pkg().Scope().Lookup(name).(*types.Const); ok { + if cnst.Val().Kind() != constant.Unknown && types.Identical(cnst.Type(), typ) { + constants = append(constants, cnst) + } + } + } + } + } + + default: + panic("model has unknown object kind (neither alias nor named type)") + } + + // Handle struct types. + strct, isStruct := def.(*types.Struct) + if isStruct && info.Predicates.MaybeJSONMarshaler == NonMarshaler && info.Predicates.MaybeTextMarshaler == NonMarshaler { + // Def is struct and model is not a marshaler: + // collect information about struct fields. + info.collectStruct(strct) + info.Type = nil + return + } + + // Record required imports. + info.Imports.AddType(def) + + // Handle enum types. + // constants slice is always empty for structs, marshalers. + if len(constants) > 0 { + // Collect information about enum values. + info.collectEnum(constants) + } + + // That's all, folks. Render as a TS alias. + info.Type = def + }) + + return info +} + +// collectEnum collects information about enum values and their declarations. +func (info *ModelInfo) collectEnum(constants []*types.Const) { + // Collect information about each constant object. + values := make([]*ConstInfo, len(constants)) + for i, cnst := range constants { + values[i] = info.collector.Const(cnst).Collect() + } + + // Sort values by grouping and source order. + slices.SortFunc(values, func(v1 *ConstInfo, v2 *ConstInfo) int { + // Skip comparisons for identical pointers. + if v1 == v2 { + return 0 + } + + // Sort first by source order of declaration group. + if v1.Decl != v2.Decl { + return cmp.Compare(v1.Decl.Pos, v2.Decl.Pos) + } + + // Then by source order of spec. + if v1.Spec != v2.Spec { + return cmp.Compare(v1.Spec.Pos, v2.Spec.Pos) + } + + // Then by source order of identifiers. + if v1.Pos != v2.Pos { + return cmp.Compare(v1.Pos, v2.Pos) + } + + // Finally by name (for constants whose source position is unknown). + return strings.Compare(v1.Name, v2.Name) + }) + + // Split value list into groups and subgroups. + var decl, spec *GroupInfo + decli, speci := -1, -1 + + for _, value := range values { + if value.Spec != spec { + spec = value.Spec + + if value.Decl == decl { + speci++ + } else { + decl = value.Decl + decli++ + speci = 0 + info.Values = append(info.Values, nil) + } + + info.Values[decli] = append(info.Values[decli], nil) + } + + info.Values[decli][speci] = append(info.Values[decli][speci], value) + } +} + +// collectStruct collects information about struct fields and their declarations. +func (info *ModelInfo) collectStruct(strct *types.Struct) { + collector := info.collector + + // Retrieve struct info. + structInfo := collector.Struct(strct).Collect() + + // Allocate result slice. + fields := make([]*ModelFieldInfo, len(structInfo.Fields)) + + // Collect fields. + for i, field := range structInfo.Fields { + // Record required imports. + info.Imports.AddType(field.Type) + + fields[i] = &ModelFieldInfo{ + StructField: field, + FieldInfo: collector.Field(field.Object).Collect(), + } + } + + // Split field list into groups, preserving the original order. + var decl *GroupInfo + decli := -1 + + for _, field := range fields { + if field.Decl != decl { + decl = field.Decl + decli++ + info.Fields = append(info.Fields, nil) + } + + info.Fields[decli] = append(info.Fields[decli], field) + } +} diff --git a/v3/internal/generator/collect/package.go b/v3/internal/generator/collect/package.go new file mode 100644 index 000000000..1d4a9414b --- /dev/null +++ b/v3/internal/generator/collect/package.go @@ -0,0 +1,300 @@ +package collect + +import ( + "cmp" + "go/ast" + "go/token" + "go/types" + "path/filepath" + "slices" + "strings" + "sync" + "sync/atomic" + + "golang.org/x/tools/go/packages" +) + +// PackageInfo records information about a package. +// +// Read accesses to fields Path, Name, IsOrImportsApp, Types, TypesInfo, Fset, +// are safe at any time without any synchronisation. +// +// Read accesses to all other fields are only safe +// if a call to [PackageInfo.Collect] has completed before the access, +// for example by calling it in the accessing goroutine +// or before spawning the accessing goroutine. +// +// Concurrent write accesses are only allowed through the provided methods. +type PackageInfo struct { + // Path holds the canonical path of the described package. + Path string + + // Name holds the import name of the described package. + Name string + + // IsOrImportsApp is true if this package is, or depends upon, the Wails application package. + IsOrImportsApp bool + + // Types and TypesInfo hold type information for this package. + Types *types.Package + TypesInfo *types.Info + + // Fset holds the FileSet that was used to parse this package. + Fset *token.FileSet + + // Files holds parsed files for this package, + // ordered by start position to support binary search. + Files []*ast.File + + // Docs holds package doc comments. + Docs []*ast.CommentGroup + + // Includes holds a list of additional files to include + // with the generated bindings. + // It maps file names to their paths on disk. + Includes map[string]string + + // Injections holds a list of code lines to be injected + // into the package index file. + Injections []string + + // services records service types that have to be generated for this package. + // We rely upon [sync.Map] for atomic swapping support. + // Keys are *types.TypeName, values are *ServiceInfo. + services sync.Map + + // models records model types that have to be generated for this package. + // We rely upon [sync.Map] for atomic swapping support. + // Keys are *types.TypeName, values are *ModelInfo. + models sync.Map + + // stats caches statistics about this package. + stats atomic.Pointer[Stats] + + collector *Collector + once sync.Once +} + +func newPackageInfo(pkg *packages.Package, collector *Collector) *PackageInfo { + _, importsApp := pkg.Imports[collector.systemPaths.ApplicationPackage] + + return &PackageInfo{ + Path: pkg.PkgPath, + Name: pkg.Name, + + IsOrImportsApp: importsApp || pkg.PkgPath == collector.systemPaths.ApplicationPackage, + + Types: pkg.Types, + TypesInfo: pkg.TypesInfo, + + Fset: pkg.Fset, + Files: pkg.Syntax, + + collector: collector, + } +} + +// Package retrieves the unique [PackageInfo] instance, if any, +// associated to the given package object within a Collector. +// +// Package is safe for concurrent use. +func (collector *Collector) Package(pkg *types.Package) *PackageInfo { + return collector.pkgs[pkg] +} + +// Iterate calls yield sequentially for each [PackageInfo] instance +// registered with the collector. If yield returns false, +// Iterate stops the iteration. +// +// Iterate is safe for concurrent use. +func (collector *Collector) Iterate(yield func(pkg *PackageInfo) bool) { + for _, pkg := range collector.pkgs { + if !yield(pkg) { + return + } + } +} + +// Stats returns cached statistics for this package. +// If [PackageInfo.Index] has not been called yet, it returns nil. +// +// Stats is safe for unsynchronised concurrent calls. +func (info *PackageInfo) Stats() *Stats { + return info.stats.Load() +} + +// Collect gathers information about the package described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *PackageInfo) Collect() *PackageInfo { + if info == nil { + return nil + } + + info.once.Do(func() { + collector := info.collector + + // Sort files by source position. + if !slices.IsSortedFunc(info.Files, compareAstFiles) { + info.Files = slices.Clone(info.Files) + slices.SortFunc(info.Files, compareAstFiles) + } + + // Collect docs and parse directives. + for _, file := range info.Files { + if file.Doc == nil { + continue + } + + info.Docs = append(info.Docs, file.Doc) + + // Retrieve file directory. + pos := info.Fset.Position(file.Pos()) + if !pos.IsValid() { + collector.logger.Errorf( + "package %s: found AST file with unknown path: `wails:include` directives from that file will be ignored", + info.Path, + ) + } + dir := filepath.Dir(pos.Filename) + + // Parse directives. + if info.Includes == nil { + info.Includes = make(map[string]string) + } + for _, comment := range file.Doc.List { + switch { + case IsDirective(comment.Text, "inject"): + // Check condition. + line, cond, err := ParseCondition(ParseDirective(comment.Text, "inject")) + if err != nil { + collector.logger.Errorf( + "%s: in `wails:inject` directive: %v", + info.Fset.Position(comment.Pos()), + err, + ) + continue + } + + if !cond.Satisfied(collector.options) { + continue + } + + // Record injected line. + info.Injections = append(info.Injections, line) + + case pos.IsValid() && IsDirective(comment.Text, "include"): + // Check condition. + pattern, cond, err := ParseCondition(ParseDirective(comment.Text, "include")) + if err != nil { + collector.logger.Errorf( + "%s: in `wails:include` directive: %v", + info.Fset.Position(comment.Pos()), + err, + ) + continue + } + + if !cond.Satisfied(collector.options) { + continue + } + + // Collect matching files. + paths, err := filepath.Glob(filepath.Join(dir, pattern)) + if err != nil { + collector.logger.Errorf( + "%s: invalid pattern '%s' in `wails:include` directive: %v", + info.Fset.Position(comment.Pos()), + pattern, + err, + ) + continue + } else if len(paths) == 0 { + collector.logger.Warningf( + "%s: pattern '%s' in `wails:include` directive matched no files", + info.Fset.Position(comment.Pos()), + pattern, + ) + continue + } + + // Announce and record matching files. + for _, path := range paths { + name := strings.ToLower(filepath.Base(path)) + if old, ok := info.Includes[name]; ok { + collector.logger.Errorf( + "%s: duplicate included file name '%s' in package %s; old path: '%s'; new path: '%s'", + info.Fset.Position(comment.Pos()), + name, + info.Path, + old, + path, + ) + continue + } + + collector.logger.Debugf( + "including file '%s' as '%s' in package %s", + path, + name, + info.Path, + ) + + info.Includes[name] = path + } + } + } + } + }) + + return info +} + +// recordService adds the given service type object +// to the set of bindings generated for this package. +// It returns the unique [ServiceInfo] instance associated +// with the given type object. +// +// It is an error to pass in here a type whose parent package +// is not the one described by the receiver. +// +// recordService is safe for unsynchronised concurrent calls. +func (info *PackageInfo) recordService(obj *types.TypeName) *ServiceInfo { + // Fetch current value, then add if not already present. + service, _ := info.services.Load(obj) + if service == nil { + service, _ = info.services.LoadOrStore(obj, newServiceInfo(info.collector, obj)) + } + return service.(*ServiceInfo) +} + +// recordModel adds the given model type object +// to the set of models generated for this package. +// It returns the unique [ModelInfo] instance associated +// with the given type object. The present result is true +// if the model was already registered. +// +// It is an error to pass in here a type whose parent package +// is not the one described by the receiver. +// +// recordModel is safe for unsynchronised concurrent calls. +func (info *PackageInfo) recordModel(obj *types.TypeName) (model *ModelInfo, present bool) { + // Fetch current value, then add if not already present. + imodel, present := info.models.Load(obj) + if imodel == nil { + imodel, present = info.models.LoadOrStore(obj, newModelInfo(info.collector, obj)) + } + return imodel.(*ModelInfo), present +} + +// compareAstFiles compares two AST files by starting position. +func compareAstFiles(f1 *ast.File, f2 *ast.File) int { + return cmp.Compare(f1.FileStart, f2.FileStart) +} diff --git a/v3/internal/generator/collect/predicates.go b/v3/internal/generator/collect/predicates.go new file mode 100644 index 000000000..8a4b3197c --- /dev/null +++ b/v3/internal/generator/collect/predicates.go @@ -0,0 +1,598 @@ +package collect + +// This file gathers functions that test useful properties of model types. +// The rationale for the way things are handled here +// is given in the example file found at ./_reference/json_marshaler_behaviour.go + +import ( + "go/token" + "go/types" + "iter" + + "golang.org/x/exp/typeparams" +) + +// Cached interface types. +var ( + ifaceTextMarshaler = types.NewInterfaceType([]*types.Func{ + types.NewFunc(token.NoPos, nil, "MarshalText", + types.NewSignatureType(nil, nil, nil, types.NewTuple(), types.NewTuple( + types.NewParam(token.NoPos, nil, "", types.NewSlice(types.Universe.Lookup("byte").Type())), + types.NewParam(token.NoPos, nil, "", types.Universe.Lookup("error").Type()), + ), false)), + }, nil).Complete() + + ifaceJSONMarshaler = types.NewInterfaceType([]*types.Func{ + types.NewFunc(token.NoPos, nil, "MarshalJSON", + types.NewSignatureType(nil, nil, nil, types.NewTuple(), types.NewTuple( + types.NewParam(token.NoPos, nil, "", types.NewSlice(types.Universe.Lookup("byte").Type())), + types.NewParam(token.NoPos, nil, "", types.Universe.Lookup("error").Type()), + ), false)), + }, nil).Complete() +) + +// MarshalerKind values describe +// whether and how a type implements a marshaler interface. +// For any one of the two marshaler interfaces, a type is +// - a NonMarshaler if it does not implement it; +// - an ImplicitMarshaler if it inherits the implementation from its underlying type; +// - an ExplicitMarshaler if it defines the relevant method explicitly. +type MarshalerKind byte + +const ( + NonMarshaler MarshalerKind = iota + ImplicitMarshaler + ExplicitMarshaler +) + +// termlist returns an iterator over the normalised term list of the given type. +// If typ is invalid or has an empty type set, termlist returns the empty sequence. +// If typ has an empty term list +// then termlist returns a sequence with just one element: the type itself. +// +// TODO: replace with new term set API once Go 1.25 is out. +// See go.dev/issue/61013 +func termlist(typ types.Type) iter.Seq[*typeparams.Term] { + terms, err := typeparams.NormalTerms(types.Unalias(typ)) + return func(yield func(*typeparams.Term) bool) { + if err == nil && len(terms) == 0 { + yield(typeparams.NewTerm(false, typ)) + } else { + for _, term := range terms { + if !yield(term) { + break + } + } + } + } +} + +// instantiate instantiates typ if it is an uninstantiated generic type +// using its own type parameters as arguments in order to preserve genericity. +// +// If typ is not generic or already instantiated, it is returned as is. +// If typ is not an alias, then the returned type is not an alias either. +func instantiate(typ types.Type) types.Type { + if t, ok := typ.(interface { + TypeParams() *types.TypeParamList + TypeArgs() *types.TypeList + }); ok && t.TypeParams() != nil && t.TypeArgs() == nil { + args := make([]types.Type, t.TypeParams().Len()) + for i := range args { + args[i] = t.TypeParams().At(i) + } + + typ, _ = types.Instantiate(nil, typ, args, false) + } + + return typ +} + +// isMarshaler checks whether the given type +// implements one of the two marshaler interfaces, +// and whether it implements it explicitly, +// i.e. by defining the relevant method directly +// instead of inheriting it from the underlying type. +// +// If addressable is true, it checks both pointer and non-pointer receivers. +// +// The behaviour of isMarshaler is unspecified +// if marshaler is not one of [json.Marshaler] or [encoding.TextMarshaler]. +func isMarshaler(typ types.Type, marshaler *types.Interface, addressable bool, visited map[*types.TypeName]MarshalerKind) MarshalerKind { + // Follow alias chain and instantiate if necessary. + // + // types.Implements does not handle generics, + // hence when typ is generic it must be instantiated. + // + // Instantiation operations may incur a large performance penalty and are usually cached, + // but doing so here would entail some complex global state and a potential memory leak. + // Because typ should be generic only during model collection, + // it should be enough to cache the result of marshaler queries for models. + typ = instantiate(types.Unalias(typ)) + + // Invariant: at this point, typ is not an alias. + + if typ == types.Typ[types.Invalid] { + // Do not pass invalid types to [types.Implements]. + return NonMarshaler + } + + result := types.Implements(typ, marshaler) + + ptr, isPtr := typ.Underlying().(*types.Pointer) + + if !result && addressable && !isPtr { + result = types.Implements(types.NewPointer(typ), marshaler) + } + + named, isNamed := typ.(*types.Named) + + if result { + // Check whether marshaler method is implemented explicitly on a named type. + if isNamed { + method := marshaler.Method(0).Name() + for i := range named.NumMethods() { + if named.Method(i).Name() == method { + return ExplicitMarshaler + } + } + } + + return ImplicitMarshaler + } + + // Fast path: named types that fail the [types.Implements] test cannot be marshalers. + // + // WARN: currently typeparams cannot be used on the rhs of a named type declaration. + // If that changes in the future, + // this guard will become essential for correctness, + // not just a shortcut. + if isNamed { + return NonMarshaler + } + + // Unwrap at most one pointer and follow alias chain. + if isPtr { + typ = types.Unalias(ptr.Elem()) + } + + // Invariant: at this point, typ is not an alias. + + // Type parameters require special handling: + // iterate over their term list and treat them as marshalers + // if so are all their potential instantiations. + + tp, ok := typ.(*types.TypeParam) + if !ok { + return NonMarshaler + } + + // Init cycle detection/deduplication map. + if visited == nil { + visited = make(map[*types.TypeName]MarshalerKind) + } + + // Type params cannot be embedded in constraints directly, + // but they can be embedded as pointer terms. + // + // When we hit that kind of cycle, + // we can err towards it being a marshaler: + // such a constraint is meaningless anyways, + // as no type can be simultaneously a pointer to itself. + // + // Therefore, we iterate the type set + // only for unvisited pointers-to-typeparams, + // and return the current best guess + // for those we have already visited. + // + // WARN: there has been some talk + // of allowing type parameters as embedded fields/terms. + // That might make our lives miserable here. + // The spec must be monitored for changes in that regard. + if isPtr { + if kind, ok := visited[tp.Obj()]; ok { + return kind + } + } + + // Initialise kind to explicit marshaler, then decrease as needed. + kind := ExplicitMarshaler + + if isPtr { + // Pointers are never explicit marshalers. + kind = ImplicitMarshaler + // Mark pointer-to-typeparam as visited and init current best guess. + visited[tp.Obj()] = kind + } + + // Iterate term list. + for term := range termlist(tp) { + ttyp := types.Unalias(term.Type()) + + // Reject if tp has a tilde or invalid element in its term list + // or has a method-only constraint. + // + // Valid tilde terms + // can always be satisfied by named types that hide their methods + // hence fail in general to implement the required interface. + if term.Tilde() || ttyp == types.Typ[types.Invalid] || ttyp == tp { + kind = NonMarshaler + break + } + + // Propagate the presence of a wrapping pointer. + if isPtr { + ttyp = types.NewPointer(ttyp) + } + + kind = min(kind, isMarshaler(ttyp, marshaler, addressable && !isPtr, visited)) + if kind == NonMarshaler { + // We can stop here as we've reached the minimum [MarshalerKind]. + break + } + } + + // Store final response for pointer-to-typeparam. + if isPtr { + visited[tp.Obj()] = kind + } + + return kind +} + +// IsTextMarshaler queries whether and how the given type +// implements the [encoding.TextMarshaler] interface. +func IsTextMarshaler(typ types.Type) MarshalerKind { + return isMarshaler(typ, ifaceTextMarshaler, false, nil) +} + +// MaybeTextMarshaler queries whether and how the given type +// implements the [encoding.TextMarshaler] interface for at least one receiver form. +func MaybeTextMarshaler(typ types.Type) MarshalerKind { + return isMarshaler(typ, ifaceTextMarshaler, true, nil) +} + +// IsJSONMarshaler queries whether and how the given type +// implements the [json.Marshaler] interface. +func IsJSONMarshaler(typ types.Type) MarshalerKind { + return isMarshaler(typ, ifaceJSONMarshaler, false, nil) +} + +// MaybeJSONMarshaler queries whether and how the given type +// implements the [json.Marshaler] interface for at least one receiver form. +func MaybeJSONMarshaler(typ types.Type) MarshalerKind { + return isMarshaler(typ, ifaceJSONMarshaler, true, nil) +} + +// IsMapKey returns true if the given type +// is accepted as a map key by encoding/json. +func IsMapKey(typ types.Type) bool { + // Iterate over type set and return true if all elements are valid. + // + // We cannot simply delegate to [IsTextMarshaler] here + // because a union of some basic terms and some TextMarshalers + // might still be acceptable. + // + // NOTE: If typ is not a typeparam or constraint, termlist returns just typ itself. + // If typ has an empty type set, it's safe to return true + // because the map cannot be instantiated anyways. + for term := range termlist(typ) { + ttyp := types.Unalias(term.Type()) + + // Types whose underlying type is a signed/unsigned integer or a string + // are always acceptable, whether they are marshalers or not. + if basic, ok := ttyp.Underlying().(*types.Basic); ok { + if basic.Info()&(types.IsInteger|types.IsUnsigned|types.IsString) != 0 { + continue + } + } + + // Valid tilde terms + // can always be satisfied by named types that hide their methods + // hence fail in general to implement the required interface. + // For example one could have: + // + // type NotAKey struct{ encoding.TextMarshaler } + // func (NotAKey) MarshalText() int { ... } + // + // which satisfies ~struct{ encoding.TextMarshaler } + // but is not itself a TextMarshaler. + // + // It might still be the case that the constraint + // requires explicitly a marshaling method, + // hence we perform one last check on typ. + // + // For example, we reject interface{ ~struct{ ... } } + // but still accept interface{ ~struct{ ... }; MarshalText() ([]byte, error) } + // + // All other cases are only acceptable + // if the type implements [encoding.TextMarshaler] in non-addressable mode. + if term.Tilde() || IsTextMarshaler(ttyp) == NonMarshaler { + // When some term fails, test the input typ itself, + // but only if it has not been tested already. + // + // Note that when term.Tilde() is true + // then it is always the case that typ != term.Type(), + // because cyclic constraints are not allowed + // and naked type parameters cannot occur in type unions. + return typ != term.Type() && IsTextMarshaler(typ) != NonMarshaler + } + } + + return true +} + +// IsTypeParam returns true when the given type +// is either a TypeParam or a pointer to a TypeParam. +func IsTypeParam(typ types.Type) bool { + switch t := types.Unalias(typ).(type) { + case *types.TypeParam: + return true + case *types.Pointer: + _, ok := types.Unalias(t.Elem()).(*types.TypeParam) + return ok + default: + return false + } +} + +// IsStringAlias returns true when +// either typ will be rendered to JS/TS as an alias for the TS type `string`, +// or typ itself (not its underlying type) is a pointer +// whose element type satisfies the property described above. +// +// This predicate is only safe to use either with map keys, +// where pointers are treated in an ad-hoc way by [json.Marshal], +// or when typ IS ALREADY KNOWN to be either [types.Alias] or [types.Named]. +// +// Otherwise, the result might be incorrect: +// IsStringAlias MUST NOT be used to check +// whether an arbitrary instance of [types.Type] +// renders as a JS/TS string type. +// +// Notice that IsStringAlias returns false for all type parameters: +// detecting those that must be always instantiated as string aliases +// is technically possible, but very difficult. +func IsStringAlias(typ types.Type) bool { + // Unwrap at most one pointer. + // NOTE: do not unalias typ before testing: + // aliases whose underlying type is a pointer + // are never rendered as strings. + if ptr, ok := typ.(*types.Pointer); ok { + typ = ptr.Elem() + } + + switch typ.(type) { + case *types.Alias, *types.Named: + // Aliases and named types might be rendered as string aliases. + default: + // Not a model type, hence not an alias. + return false + } + + // Skip pointer and interface types: they are always nullable + // and cannot have any explicitly defined methods. + // This takes care of rejecting type params as well, + // since their underlying type is guaranteed to be an interface. + switch typ.Underlying().(type) { + case *types.Pointer, *types.Interface: + return false + } + + // Follow alias chain. + typ = types.Unalias(typ) + + // Aliases of the basic string type are rendered as strings. + if basic, ok := typ.(*types.Basic); ok { + return basic.Info()&types.IsString != 0 + } + + // json.Marshalers can only be rendered as any. + // TextMarshalers that aren't json.Marshalers render as strings. + if MaybeJSONMarshaler(typ) != NonMarshaler { + return false + } else if MaybeTextMarshaler(typ) != NonMarshaler { + return true + } + + // Named types whose underlying type is a string are rendered as strings. + basic, ok := typ.Underlying().(*types.Basic) + return ok && basic.Info()&types.IsString != 0 +} + +// IsClass returns true if the given type will be rendered +// as a JS/TS model class (or interface). +func IsClass(typ types.Type) bool { + // Follow alias chain. + typ = types.Unalias(typ) + + if _, isNamed := typ.(*types.Named); !isNamed { + // Unnamed types are never rendered as classes. + return false + } + + // Struct named types without custom marshaling are rendered as classes. + _, isStruct := typ.Underlying().(*types.Struct) + return isStruct && MaybeJSONMarshaler(typ) == NonMarshaler && MaybeTextMarshaler(typ) == NonMarshaler +} + +// IsAny returns true if the given type +// is guaranteed to render as the TS any type or equivalent. +// +// It might return false negatives for generic aliases, +// hence should only be used with instantiated types +// or in contexts where false negatives are acceptable. +func IsAny(typ types.Type) bool { + // Follow alias chain. + typ = types.Unalias(typ) + + if MaybeJSONMarshaler(typ) != NonMarshaler { + // If typ is either a named type, an interface, a pointer or a struct, + // it will be rendered as (possibly an alias for) the TS any type. + // + // If it is a type parameter that implements json.Marshal, + // every possible concrete instantiation will implement json.Marshal, + // hence will be rendered as the TS any type. + return true + } + + if MaybeTextMarshaler(typ) != NonMarshaler { + // If type is either a named type, an interface, a pointer or a struct, + // it will be rendered as (possibly an alias for) + // the (possibly nullable) TS string type. + // + // If typ is a type parameter, we know at this point + // that it does not necessarily implement json.Marshaler, + // hence it will be possible to instantiate it in a way + // that renders as the (possibly nullable) TS string type. + return false + } + + if ptr, ok := typ.Underlying().(*types.Pointer); ok { + // Pointers render as the union of their element type with null. + // This is equivalent to the TS any type + // if and only if so is the element type. + return IsAny(ptr.Elem()) + } + + // All types listed below have rich TS equivalents, + // hence won't be equivalent to the TS any type. + // + // WARN: it is important to keep these lists explicit and up to date + // instead of listing the unsupported types (which would be much easier). + // + // By doing so, IsAny will keep working correctly + // in case future updates to the Go spec introduce new type families, + // thus buying the maintainers some time to patch the binding generator. + + // Retrieve underlying type. + switch t := typ.Underlying().(type) { + case *types.Basic: + // Complex types are not supported. + return t.Info()&(types.IsBoolean|types.IsInteger|types.IsUnsigned|types.IsFloat|types.IsString) == 0 + case *types.Array, *types.Slice, *types.Map, *types.Struct, *types.TypeParam: + return false + } + + return true +} + +// IsParametric returns true if the given type +// contains unresolved type parameters. +func IsParametric(typ types.Type) bool { + for { // Avoid recursion where possible + switch t := typ.(type) { + case *types.Alias, *types.Named: + tp := t.(interface{ TypeParams() *types.TypeParamList }).TypeParams() + ta := t.(interface{ TypeArgs() *types.TypeList }).TypeArgs() + + if tp.Len() == 0 { + // Not a generic alias/named type. + return false + } + + if ta.Len() == 0 { + // Uninstantiated generic. + return true + } + + for i := range ta.Len() - 1 { + if IsParametric(ta.At(i)) { + return true + } + } + + typ = ta.At(ta.Len() - 1) + + case *types.Basic: + return false + + case *types.Array, *types.Pointer, *types.Slice, *types.Chan: + typ = typ.(interface{ Elem() types.Type }).Elem() + + case *types.Map: + if IsParametric(t.Key()) { + return true + } + + typ = t.Elem() + + case *types.Signature: + if IsParametric(t.Params()) { + return true + } + + typ = t.Results() + + case *types.Struct: + if t.NumFields() == 0 { + // No more subtypes to check. + return false + } + + for i := range t.NumFields() - 1 { + if IsParametric(t.Field(i).Type()) { + return true + } + } + + typ = t.Field(t.NumFields() - 1).Type() + + case *types.Interface: + for m := range t.ExplicitMethods() { + if IsParametric(m.Type()) { + return true + } + } + + if t.NumEmbeddeds() == 0 { + // No more subtypes to check. + return false + } + + for i := range t.NumEmbeddeds() - 1 { + if IsParametric(t.EmbeddedType(i)) { + return true + } + } + + typ = t.EmbeddedType(t.NumEmbeddeds() - 1) + + case *types.Tuple: + if t.Len() == 0 { + // No more subtypes to check. + return false + } + + for i := range t.Len() - 1 { + if IsParametric(t.At(i).Type()) { + return true + } + } + + typ = t.At(t.Len() - 1).Type() + + case *types.TypeParam: + return true + + case *types.Union: + if t.Len() == 0 { + // No more subtypes to check. + return false + } + + for i := range t.Len() - 1 { + if IsParametric(t.Term(i).Type()) { + return true + } + } + + typ = t.Term(t.Len() - 1).Type() + + default: + // Unknown new type. + // This is wrong but [ImportMap.AddType] will take care of reporting it eventually. + return false + } + } +} diff --git a/v3/internal/generator/collect/service.go b/v3/internal/generator/collect/service.go new file mode 100644 index 000000000..56f500f79 --- /dev/null +++ b/v3/internal/generator/collect/service.go @@ -0,0 +1,332 @@ +package collect + +import ( + "fmt" + "go/ast" + "go/types" + "strconv" + "sync" + + "github.com/wailsapp/wails/v3/internal/hash" + "golang.org/x/tools/go/types/typeutil" +) + +type ( + // ServiceInfo records all information that is required + // to render JS/TS code for a service type. + // + // Read accesses to any public field are only safe + // if a call to [ServiceInfo.Collect] has completed before the access, + // for example by calling it in the accessing goroutine + // or before spawning the accessing goroutine. + ServiceInfo struct { + *TypeInfo + + // Internal records whether the service + // should be exported by the index file. + Internal bool + + Imports *ImportMap + Methods []*ServiceMethodInfo + + // HasInternalMethods records whether the service + // defines lifecycle or http server methods. + HasInternalMethods bool + + // Injections stores a list of JS code lines + // that should be injected into the generated file. + Injections []string + + collector *Collector + once sync.Once + } + + // ServiceMethodInfo records all information that is required + // to render JS/TS code for a service method. + ServiceMethodInfo struct { + *MethodInfo + FQN string + ID string + Internal bool + Params []*ParamInfo + Results []types.Type + } + + // ParamInfo records all information that is required + // to render JS/TS code for a service method parameter. + ParamInfo struct { + Name string + Type types.Type + Blank bool + Variadic bool + } +) + +func newServiceInfo(collector *Collector, obj *types.TypeName) *ServiceInfo { + return &ServiceInfo{ + TypeInfo: collector.Type(obj), + collector: collector, + } +} + +// Service returns the unique ServiceInfo instance +// associated to the given object within a collector +// and registers it for code generation. +// +// Service is safe for concurrent use. +func (collector *Collector) Service(obj *types.TypeName) *ServiceInfo { + pkg := collector.Package(obj.Pkg()) + if pkg == nil { + return nil + } + + return pkg.recordService(obj) +} + +// IsEmpty returns true if no methods or code injections +// are present for this service, for the selected language. +func (info *ServiceInfo) IsEmpty() bool { + // Ensure information has been collected. + info.Collect() + return len(info.Methods) == 0 && len(info.Injections) == 0 +} + +// Collect gathers information about the service described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *ServiceInfo) Collect() *ServiceInfo { + if info == nil { + return nil + } + + info.once.Do(func() { + collector := info.collector + obj := info.Object().(*types.TypeName) + + // Collect type information. + info.TypeInfo.Collect() + + // Initialise import map. + info.Imports = NewImportMap(collector.Package(obj.Pkg())) + + // Compute intuitive method set (i.e. both pointer and non-pointer receiver). + // Do not use a method set cache because + // - it would hurt concurrency (requires mutual exclusion), + // - it is only useful when the same type is queried many times; + // this may only happen here if some embedded types appear frequently, + // which should be far from average. + mset := typeutil.IntuitiveMethodSet(obj.Type(), nil) + + // Collect method information. + info.Methods = make([]*ServiceMethodInfo, 0, len(mset)) + for _, sel := range mset { + switch { + case internalServiceMethods[sel.Obj().Name()]: + info.HasInternalMethods = true + continue + case !sel.Obj().Exported(): + // Ignore unexported and internal methods. + continue + } + + methodInfo := info.collectMethod(sel.Obj().(*types.Func)) + if methodInfo != nil { + info.Methods = append(info.Methods, methodInfo) + } + } + + // Record whether the service should be exported. + info.Internal = !obj.Exported() + + // Parse directives. + for _, doc := range []*ast.CommentGroup{info.Doc, info.Decl.Doc} { + if doc == nil { + continue + } + for _, comment := range doc.List { + switch { + case IsDirective(comment.Text, "internal"): + info.Internal = true + + case IsDirective(comment.Text, "inject"): + // Check condition. + line, cond, err := ParseCondition(ParseDirective(comment.Text, "inject")) + if err != nil { + collector.logger.Errorf( + "%s: in `wails:inject` directive: %v", + collector.Package(obj.Pkg()).Fset.Position(comment.Pos()), + err, + ) + continue + } + + if !cond.Satisfied(collector.options) { + continue + } + + // Record injected line. + info.Injections = append(info.Injections, line) + } + } + } + }) + + return info +} + +// internalServiceMethod is a set of methods +// that are handled specially by the binding engine +// and must not be exposed to the frontend. +var internalServiceMethods = map[string]bool{ + "ServiceName": true, + "ServiceStartup": true, + "ServiceShutdown": true, + "ServeHTTP": true, +} + +// typeError caches the type-checker type for the Go error interface. +var typeError = types.Universe.Lookup("error").Type() + +// typeAny caches the empty interface type. +var typeAny = types.Universe.Lookup("any").Type().Underlying() + +// collectMethod collects and returns information about a service method. +// It is intended to be called only by ServiceInfo.Collect. +func (info *ServiceInfo) collectMethod(method *types.Func) *ServiceMethodInfo { + collector := info.collector + obj := info.Object().(*types.TypeName) + + signature, _ := method.Type().(*types.Signature) + if signature == nil { + // Skip invalid interface method. + // TODO: is this actually necessary? + return nil + } + + // Compute fully qualified name. + path := obj.Pkg().Path() + if obj.Pkg().Name() == "main" { + // reflect.Method.PkgPath is always "main" for the main package. + // This should not cause collisions because + // other main packages are not importable. + path = "main" + } + + fqn := path + "." + obj.Name() + "." + method.Name() + id := hash.Fnv(fqn) + + methodInfo := &ServiceMethodInfo{ + MethodInfo: collector.Method(method).Collect(), + FQN: fqn, + ID: strconv.FormatUint(uint64(id), 10), + Params: make([]*ParamInfo, 0, signature.Params().Len()), + Results: make([]types.Type, 0, signature.Results().Len()), + } + + // Parse directives. + if methodInfo.Doc != nil { + var methodIdFound bool + + for _, comment := range methodInfo.Doc.List { + switch { + case IsDirective(comment.Text, "ignore"): + return nil + + case IsDirective(comment.Text, "internal"): + methodInfo.Internal = true + + case !methodIdFound && IsDirective(comment.Text, "id"): + idString := ParseDirective(comment.Text, "id") + idValue, err := strconv.ParseUint(idString, 10, 32) + + if err != nil { + collector.logger.Errorf( + "%s: invalid value '%s' in `wails:id` directive: expected a valid uint32 value", + collector.Package(method.Pkg()).Fset.Position(comment.Pos()), + idString, + ) + continue + } + + // Announce and record alias. + collector.logger.Infof( + "package %s: method %s.%s: default ID %s replaced by %d", + path, + obj.Name(), + method.Name(), + methodInfo.ID, + idValue, + ) + methodInfo.ID = strconv.FormatUint(idValue, 10) + methodIdFound = true + } + } + } + + // Collect parameters. + for i := range signature.Params().Len() { + param := signature.Params().At(i) + + if i == 0 { + // Skip first parameter if it has context type. + named, ok := types.Unalias(param.Type()).(*types.Named) + if ok && named.Obj().Pkg().Path() == collector.systemPaths.ContextPackage && named.Obj().Name() == "Context" { + continue + } + } + + if types.IsInterface(param.Type()) && !types.Identical(param.Type().Underlying(), typeAny) { + paramName := param.Name() + if paramName == "" || paramName == "_" { + paramName = fmt.Sprintf("#%d", i+1) + } + + collector.logger.Warningf( + "%s: parameter %s has non-empty interface type %s: passing values other than `null` is not supported by encoding/json and will likely result in runtime errors", + collector.Package(method.Pkg()).Fset.Position(param.Pos()), + paramName, + param.Type(), + ) + } + + // Record type dependencies. + info.Imports.AddType(param.Type()) + + // Record parameter. + methodInfo.Params = append(methodInfo.Params, &ParamInfo{ + Name: param.Name(), + Type: param.Type(), + Blank: param.Name() == "" || param.Name() == "_", + }) + } + + if signature.Variadic() { + methodInfo.Params[len(methodInfo.Params)-1].Type = methodInfo.Params[len(methodInfo.Params)-1].Type.(*types.Slice).Elem() + methodInfo.Params[len(methodInfo.Params)-1].Variadic = true + } + + // Collect results. + for i := range signature.Results().Len() { + result := signature.Results().At(i) + + if types.Identical(result.Type(), typeError) { + // Skip error results, they are thrown as exceptions + continue + } + + // Record type dependencies. + info.Imports.AddType(result.Type()) + + // Record result. + methodInfo.Results = append(methodInfo.Results, result.Type()) + } + + return methodInfo +} diff --git a/v3/internal/generator/collect/stats.go b/v3/internal/generator/collect/stats.go new file mode 100644 index 000000000..9b5045565 --- /dev/null +++ b/v3/internal/generator/collect/stats.go @@ -0,0 +1,36 @@ +package collect + +import "time" + +type Stats struct { + NumPackages int + NumServices int + NumMethods int + NumEnums int + NumModels int + NumEvents int + StartTime time.Time + EndTime time.Time +} + +func (stats *Stats) Start() { + stats.StartTime = time.Now() + stats.EndTime = stats.StartTime +} + +func (stats *Stats) Stop() { + stats.EndTime = time.Now() +} + +func (stats *Stats) Elapsed() time.Duration { + return stats.EndTime.Sub(stats.StartTime) +} + +func (stats *Stats) Add(other *Stats) { + stats.NumPackages += other.NumPackages + stats.NumServices += other.NumServices + stats.NumMethods += other.NumMethods + stats.NumEnums += other.NumEnums + stats.NumModels += other.NumModels + stats.NumEvents += other.NumEvents +} diff --git a/v3/internal/generator/collect/struct.go b/v3/internal/generator/collect/struct.go new file mode 100644 index 000000000..b9a1b99ca --- /dev/null +++ b/v3/internal/generator/collect/struct.go @@ -0,0 +1,352 @@ +package collect + +import ( + "cmp" + "go/ast" + "go/types" + "reflect" + "slices" + "strings" + "sync" + "unicode" +) + +type ( + // StructInfo records the flattened field list for a struct type, + // taking into account JSON tags. + // + // The field list is initially empty. It will be populated + // upon calling [StructInfo.Collect] for the first time. + // + // Read accesses to the field list are only safe + // if a call to [StructInfo.Collect] has been completed before the access, + // for example by calling it in the accessing goroutine + // or before spawning the accessing goroutine. + StructInfo struct { + Fields []*StructField + + typ *types.Struct + + collector *Collector + once sync.Once + } + + // FieldInfo represents a single field in a struct. + StructField struct { + JsonName string // Avoid collisions with [FieldInfo.Name]. + Type types.Type + Optional bool + Quoted bool + + // Object holds the described type-checker object. + Object *types.Var + } +) + +func newStructInfo(collector *Collector, typ *types.Struct) *StructInfo { + return &StructInfo{ + typ: typ, + collector: collector, + } +} + +// Struct retrieves the unique [StructInfo] instance +// associated to the given type within a Collector. +// If none is present, a new one is initialised. +// +// Struct is safe for concurrent use. +func (collector *Collector) Struct(typ *types.Struct) *StructInfo { + // Cache by type pointer, do not use a typeutil.Map: + // - for models, it may result in incorrect comments; + // - for anonymous structs, it would probably bring little benefit + // because the probability of repetitions is much lower. + + return collector.fromCache(typ).(*StructInfo) +} + +func (*StructInfo) Object() types.Object { + return nil +} + +func (info *StructInfo) Type() types.Type { + return info.typ +} + +func (*StructInfo) Node() ast.Node { + return nil +} + +// Collect gathers information for the structure described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// The field list of the receiver is populated +// by the same flattening algorithm employed by encoding/json. +// JSON struct tags are accounted for. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *StructInfo) Collect() *StructInfo { + if info == nil { + return nil + } + + type fieldData struct { + *StructField + + // Data for the encoding/json flattening algorithm. + nameFromTag bool + index []int + } + + info.once.Do(func() { + // Flattened list of fields with additional information. + fields := make([]fieldData, 0, info.typ.NumFields()) + + // Queued embedded types for current and next level. + current := make([]fieldData, 0, info.typ.NumFields()) + next := make([]fieldData, 1, max(1, info.typ.NumFields())) + + // Count of queued embedded types for current and next level. + count := make(map[*types.Struct]int) + nextCount := make(map[*types.Struct]int) + + // Set of visited types to avoid duplicating work. + visited := make(map[*types.Struct]bool) + + next[0] = fieldData{ + StructField: &StructField{ + Type: info.typ, + }, + } + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, count + clear(nextCount) + + for _, embedded := range current { + // Scan embedded type for fields to include. + estruct := embedded.Type.Underlying().(*types.Struct) + + // Skip previously visited structs + if visited[estruct] { + continue + } + visited[estruct] = true + + // WARNING: do not reuse cached info for embedded structs. + // It may lead to incorrect results for subtle reasons. + + for i := range estruct.NumFields() { + field := estruct.Field(i) + + // Retrieve type of field, following aliases conservatively + // and unwrapping exactly one pointer. + ftype := field.Type() + if ptr, ok := types.Unalias(ftype).(*types.Pointer); ok { + ftype = ptr.Elem() + } + + // Detect struct alias and keep it. + fstruct, _ := types.Unalias(ftype).(*types.Struct) + if fstruct == nil { + // Not a struct alias, follow alias chain. + ftype = types.Unalias(ftype) + fstruct, _ = ftype.Underlying().(*types.Struct) + } + + if field.Embedded() { + if !field.Exported() && fstruct == nil { + // Ignore embedded fields of unexported non-struct types. + continue + } + } else if !field.Exported() { + // Ignore unexported non-embedded fields. + continue + } + + // Retrieve and parse json tag. + tag := reflect.StructTag(estruct.Tag(i)).Get("json") + name, optional, quoted, visible := parseTag(tag) + if !visible { + // Ignored by encoding/json. + continue + } + + if !isValidFieldName(name) { + // Ignore alternative name if invalid. + name = "" + } + + index := make([]int, len(embedded.index)+1) + copy(index, embedded.index) + index[len(embedded.index)] = i + + if name != "" || !field.Embedded() || fstruct == nil { + // Tag name is non-empty, + // or field is not embedded, + // or field is not structure: + // add to field list. + + if !info.collector.options.UseInterfaces { + // In class mode, mark parametric fields as optional + // because there is no way to know their default JS value in advance. + if _, isTypeParam := types.Unalias(field.Type()).(*types.TypeParam); isTypeParam { + optional = true + } + } + + finfo := fieldData{ + StructField: &StructField{ + JsonName: name, + Type: field.Type(), + Optional: optional, + Quoted: quoted, + + Object: field, + }, + nameFromTag: name != "", + index: index, + } + + if name == "" { + finfo.JsonName = field.Name() + } + + fields = append(fields, finfo) + if count[estruct] > 1 { + // The struct we are scanning + // appears multiple times at the current level. + // This means that all its fields are ambiguous + // and must disappear. + // Duplicate them so that the field selection phase + // below will erase them. + fields = append(fields, finfo) + } + + continue + } + + // Queue embedded field for next level. + // If it has been queued already, do not duplicate it. + nextCount[fstruct]++ + if nextCount[fstruct] == 1 { + next = append(next, fieldData{ + StructField: &StructField{ + Type: ftype, + }, + index: index, + }) + } + } + } + } + + // Prepare for field selection phase. + slices.SortFunc(fields, func(f1 fieldData, f2 fieldData) int { + // Sort by name first. + if diff := strings.Compare(f1.JsonName, f2.JsonName); diff != 0 { + return diff + } + + // Break ties by depth of occurrence. + if diff := cmp.Compare(len(f1.index), len(f2.index)); diff != 0 { + return diff + } + + // Break ties by presence of json tag (prioritize presence). + if f1.nameFromTag != f2.nameFromTag { + if f1.nameFromTag { + return -1 + } else { + return 1 + } + } + + // Break ties by order of occurrence. + return slices.Compare(f1.index, f2.index) + }) + + fieldCount := 0 + + // Keep for each name the dominant field, drop those for which ties + // still exist (ignoring order of occurrence). + for i, j := 0, 1; j <= len(fields); j++ { + if j < len(fields) && fields[i].JsonName == fields[j].JsonName { + continue + } + + // If there is only one field with the current name, + // or there is a dominant one, keep it. + if i+1 == j || len(fields[i].index) != len(fields[i+1].index) || fields[i].nameFromTag != fields[i+1].nameFromTag { + fields[fieldCount] = fields[i] + fieldCount++ + } + + i = j + } + + fields = fields[:fieldCount] + + // Sort by order of occurrence. + slices.SortFunc(fields, func(f1 fieldData, f2 fieldData) int { + return slices.Compare(f1.index, f2.index) + }) + + // Copy selected fields to receiver. + info.Fields = make([]*StructField, len(fields)) + for i, field := range fields { + info.Fields[i] = field.StructField + } + + info.typ = nil + }) + + return info +} + +// parseTag parses a json field tag and extracts +// all options recognised by encoding/json. +func parseTag(tag string) (name string, optional bool, quoted bool, visible bool) { + if tag == "-" { + return "", false, false, false + } else { + visible = true + } + + parts := strings.Split(tag, ",") + + name = parts[0] + + for _, option := range parts[1:] { + switch option { + case "omitempty", "omitzero": + optional = true + case "string": + quoted = true + } + } + + return +} + +// isValidFieldName determines whether a field name is valid +// according to encoding/json. +func isValidFieldName(name string) bool { + if name == "" { + return false + } + + for _, c := range name { + if !strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c) && !unicode.IsLetter(c) && !unicode.IsDigit(c) { + return false + } + } + + return true +} diff --git a/v3/internal/generator/collect/type.go b/v3/internal/generator/collect/type.go new file mode 100644 index 000000000..24e5adefc --- /dev/null +++ b/v3/internal/generator/collect/type.go @@ -0,0 +1,113 @@ +package collect + +import ( + "go/ast" + "go/types" + "slices" + "sync" +) + +// TypeInfo records information about a type declaration. +// +// Read accesses to any public field are only safe +// if a call to [TypeInfo.Collect] has completed before the access, +// for example by calling it in the accessing goroutine +// or before spawning the accessing goroutine. +type TypeInfo struct { + Name string + + // Alias is true for type aliases. + Alias bool + + Doc *ast.CommentGroup + Decl *GroupInfo + + obj *types.TypeName + node ast.Node + + collector *Collector + once sync.Once +} + +// newTypeInfo initialises a descriptor for the given named type object. +func newTypeInfo(collector *Collector, obj *types.TypeName) *TypeInfo { + return &TypeInfo{ + obj: obj, + collector: collector, + } +} + +// Type returns the unique TypeInfo instance +// associated to the given object within a collector. +// +// Type is safe for concurrent use. +func (collector *Collector) Type(obj *types.TypeName) *TypeInfo { + return collector.fromCache(obj).(*TypeInfo) +} + +func (info *TypeInfo) Object() types.Object { + return info.obj +} + +func (info *TypeInfo) Type() types.Type { + return info.obj.Type() +} + +func (info *TypeInfo) Node() ast.Node { + return info.Collect().node +} + +// Collect gathers information about the type described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *TypeInfo) Collect() *TypeInfo { + if info == nil { + return nil + } + + info.once.Do(func() { + collector := info.collector + + info.Name = info.obj.Name() + info.Alias = info.obj.IsAlias() + + path := collector.findDeclaration(info.obj) + if path == nil { + collector.logger.Warningf( + "package %s: type %s: could not find declaration for type object", + info.obj.Pkg().Path(), + info.Name, + ) + + // Provide dummy group. + info.Decl = newGroupInfo(nil).Collect() + return + } + + // path shape: *ast.TypeSpec, *ast.GenDecl, *ast.File + tspec := path[0].(*ast.TypeSpec) + + // Retrieve doc comments. + info.Doc = tspec.Doc + if info.Doc == nil { + info.Doc = tspec.Comment + } else if tspec.Comment != nil { + info.Doc = &ast.CommentGroup{ + List: slices.Concat(tspec.Doc.List, tspec.Comment.List), + } + } + + info.Decl = collector.fromCache(path[1]).(*GroupInfo).Collect() + + info.node = path[0] + }) + + return info +} diff --git a/v3/internal/generator/collect/void.go b/v3/internal/generator/collect/void.go new file mode 100644 index 000000000..b2a377ec8 --- /dev/null +++ b/v3/internal/generator/collect/void.go @@ -0,0 +1,28 @@ +package collect + +import ( + "go/types" +) + +// IsVoidAlias returns true when the given type or object is the application.Void named type that stands in for the void TS type. +func (collector *Collector) IsVoidAlias(typOrObj any) bool { + var obj types.Object + switch to := typOrObj.(type) { + case types.Object: + obj = to + case interface{ Obj() *types.TypeName }: + obj = to.Obj() + default: + return false + } + + if vt := collector.appVoidType.Load(); vt != nil && obj == vt { + return true + } else if vt == nil && obj.Name() == "Void" && obj.Pkg() != nil && obj.Pkg().Path() == collector.systemPaths.ApplicationPackage { // Check name before package to fail fast + // Cache void alias for faster checking + collector.appVoidType.Store(obj) + return true + } + + return false +} diff --git a/v3/internal/generator/config/file.go b/v3/internal/generator/config/file.go new file mode 100644 index 000000000..1e3bc7477 --- /dev/null +++ b/v3/internal/generator/config/file.go @@ -0,0 +1,64 @@ +package config + +import ( + "io" + "os" + "path/filepath" +) + +// FileCreator abstracts away file and directory creation. +// We use this to implement tests cleanly. +// +// Paths are always relative to the output directory. +// +// A FileCreator must allow concurrent calls to Create transparently. +// Each [io.WriteCloser] instance returned by a call to Create +// will be used by one goroutine at a time; but distinct instances +// must support concurrent use by distinct goroutines. +type FileCreator interface { + Create(path string) (io.WriteCloser, error) +} + +// FileCreatorFunc is an adapter to allow +// the use of ordinary functions as file creators. +type FileCreatorFunc func(path string) (io.WriteCloser, error) + +// Create calls f(path). +func (f FileCreatorFunc) Create(path string) (io.WriteCloser, error) { + return f(path) +} + +// NullCreator is a dummy file creator implementation. +// Calls to Create never fail and return +// a writer that discards all incoming data. +var NullCreator FileCreator = FileCreatorFunc(func(path string) (io.WriteCloser, error) { + return nullWriteCloser{}, nil +}) + +// DirCreator returns a file creator that creates files +// relative to the given output directory. +// +// It joins the output directory and the file path, +// calls [os.MkdirAll] on the directory part of the result, +// then [os.Create] on the full file path. +func DirCreator(outputDir string) FileCreator { + return FileCreatorFunc(func(path string) (io.WriteCloser, error) { + path = filepath.Join(outputDir, path) + + if err := os.MkdirAll(filepath.Dir(path), 0o777); err != nil { + return nil, err + } + + return os.Create(path) + }) +} + +type nullWriteCloser struct{} + +func (nullWriteCloser) Write(data []byte) (int, error) { + return len(data), nil +} + +func (nullWriteCloser) Close() error { + return nil +} diff --git a/v3/internal/generator/config/log.go b/v3/internal/generator/config/log.go new file mode 100644 index 000000000..cdfceb3db --- /dev/null +++ b/v3/internal/generator/config/log.go @@ -0,0 +1,102 @@ +package config + +import ( + "fmt" + + "github.com/pterm/pterm" +) + +// A Logger instance provides methods to format and report messages +// intended for the end user. +// +// All Logger methods may be called concurrently by its consumers. +type Logger interface { + // Errorf should process its arguments as if they were passed to fmt.Sprintf + // and report the resulting string to the user as an error message. + Errorf(format string, a ...any) + + // Warningf should process its arguments as if they were passed to fmt.Sprintf + // and report the resulting string to the user as a warning message. + Warningf(format string, a ...any) + + // Infof should process its arguments as if they were passed to fmt.Sprintf + // and report the resulting string to the user as an informational message. + Infof(format string, a ...any) + + // Debugf should process its arguments as if they were passed to fmt.Sprintf + // and report the resulting string to the user as a debug message. + Debugf(format string, a ...any) + + // Statusf should process its arguments as if they were passed to fmt.Sprintf + // and report the resulting string to the user as a status message. + Statusf(format string, a ...any) +} + +// NullLogger is a dummy Logger implementation +// that discards all incoming messages. +var NullLogger Logger = nullLogger{} + +type nullLogger struct{} + +func (nullLogger) Errorf(format string, a ...any) {} +func (nullLogger) Warningf(format string, a ...any) {} +func (nullLogger) Infof(format string, a ...any) {} +func (nullLogger) Debugf(format string, a ...any) {} +func (nullLogger) Statusf(format string, a ...any) {} + +// DefaultPtermLogger returns a Logger implementation that writes +// to the default pterm printers for each logging level. +// +// If spinner is not nil, it is used to log status updates. +// The spinner must have been started already. +func DefaultPtermLogger(spinner *pterm.SpinnerPrinter) Logger { + return &PtermLogger{ + &pterm.Error, + &pterm.Warning, + &pterm.Info, + &pterm.Debug, + spinner, + } +} + +// PtermLogger is a Logger implementation that writes to pterm printers. +// If any field is nil, PtermLogger discards all messages of that level. +type PtermLogger struct { + Error pterm.TextPrinter + Warning pterm.TextPrinter + Info pterm.TextPrinter + Debug pterm.TextPrinter + Spinner *pterm.SpinnerPrinter +} + +func (logger *PtermLogger) Errorf(format string, a ...any) { + if logger.Error != nil { + logger.Error.Printfln(format, a...) + } +} + +func (logger *PtermLogger) Warningf(format string, a ...any) { + if logger.Warning != nil { + // Prefix with [warn] to prevent GitHub Actions Go problem matcher + // from treating warnings as errors (it matches file.go:line:col: patterns) + logger.Warning.Printfln("[warn] "+format, a...) + } +} + +func (logger *PtermLogger) Infof(format string, a ...any) { + if logger.Info != nil { + logger.Info.Printfln(format, a...) + } +} + +func (logger *PtermLogger) Debugf(format string, a ...any) { + if logger.Debug != nil { + logger.Debug.Printfln(format, a...) + } +} + +func (logger *PtermLogger) Statusf(format string, a ...any) { + if logger.Spinner != nil { + logger.Spinner.UpdateText(fmt.Sprintf(format, a...)) + } +} diff --git a/v3/internal/generator/config/paths.go b/v3/internal/generator/config/paths.go new file mode 100644 index 000000000..26e1acb51 --- /dev/null +++ b/v3/internal/generator/config/paths.go @@ -0,0 +1,14 @@ +package config + +// WailsAppPkgPath is the official import path of Wails v3's application package. +const WailsAppPkgPath = "github.com/wailsapp/wails/v3/pkg/application" + +// WailsInternalPkgPath is the official import path of Wails v3's internal package. +const WailsInternalPkgPath = "github.com/wailsapp/wails/v3/internal" + +// SystemPaths holds resolved paths of required system packages. +type SystemPaths struct { + ContextPackage string + ApplicationPackage string + InternalPackage string +} diff --git a/v3/internal/generator/constants.go b/v3/internal/generator/constants.go new file mode 100644 index 000000000..3c0c85873 --- /dev/null +++ b/v3/internal/generator/constants.go @@ -0,0 +1,54 @@ +package generator + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "strings" +) + +func GenerateConstants(goData []byte) (string, error) { + + // Create a new token file set and parser + fs := token.NewFileSet() + f, err := parser.ParseFile(fs, "", goData, parser.AllErrors) + if err != nil { + return "", err + } + + // Extract constant declarations and generate JavaScript constants + var jsConstants []string + for _, decl := range f.Decls { + if gd, ok := decl.(*ast.GenDecl); ok && gd.Tok == token.CONST { + for _, spec := range gd.Specs { + if vs, ok := spec.(*ast.ValueSpec); ok { + for i, name := range vs.Names { + value := vs.Values[i] + if value != nil { + jsConstants = append(jsConstants, fmt.Sprintf("export const %s = %s;", name.Name, jsValue(value))) + } + } + } + } + } + } + + // Join the JavaScript constants into a single string + jsCode := strings.Join(jsConstants, "\n") + + return jsCode, nil +} + +func jsValue(expr ast.Expr) string { + // Implement conversion from Go constant value to JavaScript value here. + // You can add more cases for different types if needed. + switch e := expr.(type) { + case *ast.BasicLit: + return e.Value + case *ast.Ident: + return e.Name + default: + return "" + } +} diff --git a/v3/internal/generator/constants_test.go b/v3/internal/generator/constants_test.go new file mode 100644 index 000000000..9e056dcac --- /dev/null +++ b/v3/internal/generator/constants_test.go @@ -0,0 +1,55 @@ +package generator + +import "testing" + +func TestGenerateConstants(t *testing.T) { + tests := []struct { + name string + goData []byte + want string + wantErr bool + }{ + { + name: "int", + goData: []byte(`package test +const one = 1`), + want: "export const one = 1;", + wantErr: false, + }, + { + name: "float", + goData: []byte(`package test +const one_point_five = 1.5`), + want: "export const one_point_five = 1.5;", + wantErr: false, + }, + { + name: "string", + goData: []byte(`package test +const one_as_a_string = "1"`), + want: `export const one_as_a_string = "1";`, + wantErr: false, + }, + { + name: "nested", + goData: []byte(`package test +const ( + one_as_a_string = "1" +)`), + want: `export const one_as_a_string = "1";`, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GenerateConstants(tt.goData) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateConstants() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GenerateConstants() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/v3/internal/generator/errors.go b/v3/internal/generator/errors.go new file mode 100644 index 000000000..1536ea9be --- /dev/null +++ b/v3/internal/generator/errors.go @@ -0,0 +1,196 @@ +package generator + +import ( + "errors" + "fmt" + "maps" + "slices" + "sync" + + "github.com/wailsapp/wails/v3/internal/generator/config" +) + +// ErrNoContextPackage indicates that +// the canonical path for the standard context package +// did not match any actual package. +var ErrNoContextPackage = errors.New("standard context package not found at canonical import path ('context'): is the Wails v3 module properly installed? ") + +// ErrNoApplicationPackage indicates that +// the canonical path for the Wails application package +// did not match any actual package. +var ErrNoApplicationPackage = errors.New("Wails application package not found at canonical import path ('" + config.WailsAppPkgPath + "'): is the Wails v3 module properly installed? ") + +// ErrNoInternalPackage indicates that +// the canonical path for the Wails internal package +// did not match any actual package. +var ErrNoInternalPackage = errors.New("Wails internal package not found at canonical import path ('" + config.WailsInternalPkgPath + "'): is the Wails v3 module properly installed? ") + +// ErrBadApplicationPackage indicates that +// the Wails application package has invalid content. +var ErrBadApplicationPackage = errors.New("package " + config.WailsAppPkgPath + ": function NewService has wrong signature: is the Wails v3 module properly installed? ") + +// ErrNoPackages is returned by [Generator.Generate] +// when [LoadPackages] returns no error and no packages. +var ErrNoPackages = errors.New("the given patterns matched no packages") + +// ErrorReport accumulates and logs error +// and warning messages, with deduplication. +// +// It implements the error interface; the Error method +// returns a report counting messages emitted so far. +// +// It also implements the interface [config.Logger] for convenience. +type ErrorReport struct { + logger config.Logger + + mu sync.Mutex + warnings map[string]bool + errors map[string]bool +} + +// NewErrorReport report initialises an ErrorReport instance +// with the provided Logger implementation. +// +// If logger is nil, messages will be accumulated but not logged. +func NewErrorReport(logger config.Logger) *ErrorReport { + if logger == nil { + logger = config.NullLogger + } + + return &ErrorReport{ + logger: logger, + warnings: make(map[string]bool), + errors: make(map[string]bool), + } +} + +// Error returns a string reporting the number +// of errors and warnings emitted so far. +func (report *ErrorReport) Error() string { + report.mu.Lock() + defer report.mu.Unlock() + + if len(report.errors) > 0 && len(report.warnings) == 0 { + var plural string + if len(report.errors) > 1 { + plural = "s" + } + return fmt.Sprintf("%d error%s emitted", len(report.errors), plural) + + } else if len(report.errors) == 0 && len(report.warnings) > 0 { + var plural string + if len(report.warnings) > 1 { + plural = "s" + } + + return fmt.Sprintf("%d warning%s emitted", len(report.warnings), plural) + + } else if len(report.errors) > 0 && len(report.warnings) > 0 { + var eplural, wplural string + if len(report.errors) > 1 { + eplural = "s" + } + if len(report.warnings) > 1 { + wplural = "s" + } + + return fmt.Sprintf("%d error%s and %d warning%s emitted", len(report.errors), eplural, len(report.warnings), wplural) + + } else { + return "no errors or warnings emitted" + } +} + +// HasErrors returns true if at least one error has been added to the report. +func (report *ErrorReport) HasErrors() bool { + report.mu.Lock() + result := len(report.errors) > 0 + report.mu.Unlock() + return result +} + +// HasWarnings returns true if at least one warning has been added to the report. +func (report *ErrorReport) HasWarnings() bool { + report.mu.Lock() + result := len(report.warnings) > 0 + report.mu.Unlock() + return result +} + +// Errors returns the list of error messages +// that have been added to the report. +// The order is randomised. +func (report *ErrorReport) Errors() []string { + report.mu.Lock() + defer report.mu.Unlock() + + return slices.Collect(maps.Keys(report.errors)) +} + +// Warnings returns the list of warning messages +// that have been added to the report. +// The order is randomised. +func (report *ErrorReport) Warnings() []string { + report.mu.Lock() + defer report.mu.Unlock() + + return slices.Collect(maps.Keys(report.warnings)) +} + +// Errorf formats an error message and adds it to the report. +// If not already present, the message is forwarded +// to the logger instance provided during initialisation. +func (report *ErrorReport) Errorf(format string, a ...any) { + msg := fmt.Sprintf(format, a...) + + report.mu.Lock() + defer report.mu.Unlock() + + present := report.errors[msg] + report.errors[msg] = true + + if !present { + report.logger.Errorf(format, a...) + } +} + +// Warningf formats an error message and adds it to the report. +// If not already present, the message is forwarded +// to the logger instance provided during initialisation. +func (report *ErrorReport) Warningf(format string, a ...any) { + msg := fmt.Sprintf(format, a...) + + report.mu.Lock() + defer report.mu.Unlock() + + present := report.warnings[msg] + report.warnings[msg] = true + + if !present { + report.logger.Warningf(format, a...) + } +} + +// Infof forwards the given informational message +// to the logger instance provided during initialisation. +// +// This method is here just for convenience and performs no deduplication. +func (report *ErrorReport) Infof(format string, a ...any) { + report.logger.Infof(format, a...) +} + +// Debugf forwards the given informational message +// to the logger instance provided during initialisation. +// +// This method is here just for convenience and performs no deduplication. +func (report *ErrorReport) Debugf(format string, a ...any) { + report.logger.Debugf(format, a...) +} + +// Statusf forwards the given status message +// to the logger instance provided during initialisation. +// +// This method is here just for convenience and performs no deduplication. +func (report *ErrorReport) Statusf(format string, a ...any) { + report.logger.Statusf(format, a...) +} diff --git a/v3/internal/generator/events.go b/v3/internal/generator/events.go new file mode 100644 index 000000000..ef1ebd380 --- /dev/null +++ b/v3/internal/generator/events.go @@ -0,0 +1,53 @@ +package generator + +import ( + "path/filepath" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +func (generator *Generator) generateEvents(events *collect.EventMap) { + // Generate event data table. + generator.scheduler.Schedule(func() { + file, err := generator.creator.Create(filepath.Join(events.Imports.Self, generator.renderer.EventDataFile())) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("event data table generation failed") + return + } + defer func() { + if err := file.Close(); err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("event data table generation failed") + } + }() + + err = generator.renderer.EventData(file, events) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("event data table generation failed") + } + }) + + // Generate event creation code. + generator.scheduler.Schedule(func() { + file, err := generator.creator.Create(filepath.Join(events.Imports.Self, generator.renderer.EventCreateFile())) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("event creation code generation failed") + return + } + defer func() { + if err := file.Close(); err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("event creation code generation failed") + } + }() + + err = generator.renderer.EventCreate(file, events) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("event creation code generation failed") + } + }) +} diff --git a/v3/internal/generator/generate.go b/v3/internal/generator/generate.go new file mode 100644 index 000000000..2885cdd3c --- /dev/null +++ b/v3/internal/generator/generate.go @@ -0,0 +1,280 @@ +package generator + +import ( + "fmt" + "io" + "strings" + "sync" + "time" + + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/generator/collect" + "github.com/wailsapp/wails/v3/internal/generator/config" + "github.com/wailsapp/wails/v3/internal/generator/render" +) + +// Generator wraps all bookkeeping data structures that are needed +// to generate bindings for a set of packages. +type Generator struct { + options *flags.GenerateBindingsOptions + creator config.FileCreator + + // serviceFiles maps service file paths to their type object. + // It is used for lower/upper-case collision detection. + // Keys are strings, values are *types.TypeName. + serviceFiles sync.Map + + collector *collect.Collector + renderer *render.Renderer + + logger *ErrorReport + scheduler scheduler +} + +// NewGenerator configures a new generator instance. +// The options argument must not be nil. +// If creator is nil, no output file will be created. +// If logger is not nil, it is used to report messages interactively. +func NewGenerator(options *flags.GenerateBindingsOptions, creator config.FileCreator, logger config.Logger) *Generator { + if creator == nil { + creator = config.NullCreator + } + + report := NewErrorReport(logger) + + return &Generator{ + options: options, + creator: config.FileCreatorFunc(func(path string) (io.WriteCloser, error) { + report.Debugf("writing output file %s", path) + return creator.Create(path) + }), + + logger: report, + } +} + +// Generate runs the binding generation process +// for the packages specified by the given patterns. +// +// Concurrent or repeated calls to Generate with the same receiver +// are not allowed. +// +// The stats result field is never nil. +// +// The error result field is nil in case of complete success (no warning). +// Otherwise, it may either report errors that occured while loading +// the initial set of packages, or errors returned by the static analyser, +// or be an [ErrorReport] instance. +// +// If error is an ErrorReport, it may have accumulated no errors, just warnings. +// When this is the case, all bindings have been generated successfully. +// +// Parsing/type-checking errors or errors encountered while writing +// individual files will be printed directly to the [config.Logger] instance +// provided during initialisation. +func (generator *Generator) Generate(patterns ...string) (stats *collect.Stats, err error) { + stats = &collect.Stats{} + stats.Start() + defer stats.Stop() + + // Validate file names. + err = generator.validateFileNames() + if err != nil { + return + } + + // Parse build flags. + buildFlags, err := generator.options.BuildFlags() + if err != nil { + return + } + + // Start package loading feedback. + var lpkgMutex sync.Mutex + generator.logger.Statusf("Loading packages...") + go func() { + time.Sleep(5 * time.Second) + if lpkgMutex.TryLock() { + generator.logger.Statusf("Loading packages... (this may take a long time)") + lpkgMutex.Unlock() + } + }() + + systemPaths, err := ResolveSystemPaths(buildFlags) + if err != nil { + return + } + + // Load initial packages. + pkgs, err := LoadPackages(buildFlags, patterns...) + + // Suppress package loading feedback. + lpkgMutex.Lock() + + // Check for loading errors. + if err != nil { + return + } + if len(patterns) > 0 && len(pkgs) == 0 { + err = ErrNoPackages + return + } + + // Report parsing/type-checking errors. + for _, pkg := range pkgs { + for _, err := range pkg.Errors { + generator.logger.Warningf("%v", err) + } + } + + // Panic on repeated calls. + if generator.collector != nil { + panic("Generate() must not be called more than once on the same receiver") + } + + // Update status. + if generator.options.NoEvents { + generator.logger.Statusf("Looking for services...") + } else { + generator.logger.Statusf("Looking for services and events...") + } + serviceOrEventFound := sync.OnceFunc(func() { generator.logger.Statusf("Generating bindings...") }) + + // Run static analysis. + services, registerEvent, err := FindServices(pkgs, systemPaths, generator.logger) + + // Initialise subcomponents. + generator.collector = collect.NewCollector(pkgs, registerEvent, systemPaths, generator.options, &generator.scheduler, generator.logger) + generator.renderer = render.NewRenderer(generator.collector, generator.options) + + // Check for analyser errors. + if err != nil { + return + } + + // Discard unneeded data. + pkgs = nil + + // Schedule collection and code generation for event data types. + if !generator.options.NoEvents { + generator.scheduler.Schedule(func() { + events := generator.collector.EventMap().Collect() + if len(events.Defs) > 0 { + serviceOrEventFound() + // Not a data race because we wait for this scheduled task + // to complete before accessing stats again. + stats.Add(events.Stats()) + } + generator.generateEvents(events) + }) + } + + // Schedule code generation for each found service. + for obj := range services { + serviceOrEventFound() + generator.scheduler.Schedule(func() { + generator.generateService(obj) + }) + } + + // Wait until all services have been generated and all models collected. + generator.scheduler.Wait() + + // Invariants: + // - Service files have been generated for all discovered services; + // - ModelInfo.Collect has been called on all discovered models, and therefore + // - all required models have been discovered. + + // Update status. + if generator.options.NoIndex { + generator.logger.Statusf("Generating models...") + } else { + generator.logger.Statusf("Generating models and index files...") + } + + // Schedule models, index and included files generation for each package. + for pkg := range generator.collector.Iterate { + generator.scheduler.Schedule(func() { + generator.generateModelsIndexIncludes(pkg) + }) + } + + // Wait until all models and indices have been generated. + generator.scheduler.Wait() + + // Populate stats. + generator.logger.Statusf("Collecting stats...") + for info := range generator.collector.Iterate { + stats.Add(info.Stats()) + } + + // Return non-empty error report. + if generator.logger.HasErrors() || generator.logger.HasWarnings() { + err = generator.logger + } + + return +} + +// generateModelsIndexIncludes schedules generation of public/private model files, +// included files and, if allowed by the options, +// of an index file for the given package. +func (generator *Generator) generateModelsIndexIncludes(pkg *collect.PackageInfo) { + index := pkg.Index(generator.options.TS) + + // info.Index implies info.Collect: goroutines spawned below + // can access package information freely. + + if len(index.Models) > 0 { + generator.scheduler.Schedule(func() { + generator.generateModels(pkg, index.Models) + }) + } + + if len(index.Package.Includes) > 0 { + generator.scheduler.Schedule(func() { + generator.generateIncludes(index) + }) + } + + if !generator.options.NoIndex && !index.IsEmpty() { + generator.generateIndex(index) + } +} + +// validateFileNames validates user-provided filenames. +func (generator *Generator) validateFileNames() error { + switch { + case generator.options.ModelsFilename == "": + return fmt.Errorf("models filename must not be empty") + + case !generator.options.NoIndex && generator.options.IndexFilename == "": + return fmt.Errorf("package index filename must not be empty") + + case generator.options.ModelsFilename != strings.ToLower(generator.options.ModelsFilename): + return fmt.Errorf("models filename must not contain uppercase characters") + + case generator.options.IndexFilename != strings.ToLower(generator.options.IndexFilename): + return fmt.Errorf("package index filename must not contain uppercase characters") + + case !generator.options.NoIndex && generator.options.ModelsFilename == generator.options.IndexFilename: + return fmt.Errorf("models and package indexes cannot share the same filename") + } + + return nil +} + +// scheduler provides an implementation of the [collect.Scheduler] interface. +type scheduler struct { + sync.WaitGroup +} + +// Schedule runs the given function concurrently, +// registering it on the scheduler's wait group. +func (sched *scheduler) Schedule(task func()) { + sched.Add(1) + go func() { + defer sched.Done() + task() + }() +} diff --git a/v3/internal/generator/generate_test.go b/v3/internal/generator/generate_test.go new file mode 100644 index 000000000..04c01bef6 --- /dev/null +++ b/v3/internal/generator/generate_test.go @@ -0,0 +1,273 @@ +package generator + +import ( + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "slices" + "strings" + "sync" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/generator/config" + "github.com/wailsapp/wails/v3/internal/generator/render" +) + +const testcases = "github.com/wailsapp/wails/v3/internal/generator/testcases/..." + +type testParams struct { + name string + options *flags.GenerateBindingsOptions + outputDir string + want map[string]bool +} + +func TestGenerator(t *testing.T) { + const ( + useNamesBit = 1 << iota + useInterfacesBit + tsBit + ) + + // Generate configuration matrix. + tests := make([]*testParams, 1<<3) + for i := range tests { + options := &flags.GenerateBindingsOptions{ + ModelsFilename: "models", + IndexFilename: "index", + + UseBundledRuntime: true, + + TS: i&tsBit != 0, + UseInterfaces: i&useInterfacesBit != 0, + UseNames: i&useNamesBit != 0, + } + + name := configString(options) + + tests[i] = &testParams{ + name: name, + options: options, + outputDir: filepath.Join("testdata/output", name), + want: make(map[string]bool), + } + } + + for _, test := range tests { + // Create output dir. + if err := os.MkdirAll(test.outputDir, 0777); err != nil { + t.Fatal(err) + } + + // Walk output dir. + err := filepath.WalkDir(test.outputDir, func(path string, d fs.DirEntry, err error) error { + // Skip directories. + if d.IsDir() { + return nil + } + + // Skip got files. + if strings.HasSuffix(d.Name(), ".got.js") || strings.HasSuffix(d.Name(), ".got.ts") || strings.HasSuffix(d.Name(), ".got.log") { + return nil + } + + // Record file. + test.want[filepath.Clean("."+path[len(test.outputDir):])] = false + return nil + }) + + if err != nil { + t.Fatal(err) + } + } + + // Run tests. + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + creator := outputCreator(t, test) + + generator := NewGenerator( + test.options, + creator, + // Use NullLogger to suppress console output during tests. + // Warnings are written to warnings.log for comparison instead. + // This prevents GitHub Actions Go problem matcher from treating + // warning output as errors. + config.NullLogger, + ) + + _, err := generator.Generate(testcases) + if report := (*ErrorReport)(nil); errors.As(err, &report) { + if report.HasErrors() { + t.Error(report) + } + + // Log warnings and compare with reference output. + if log, err := creator.Create("warnings.log"); err != nil { + t.Error(err) + } else { + func() { + defer log.Close() + + warnings := report.Warnings() + slices.Sort(warnings) + + // Normalize paths in warnings to be relative to the testcases directory + // This ensures consistent output across different development environments and CI + for i, msg := range warnings { + // Handle both Unix and Windows path separators + msg = strings.ReplaceAll(msg, "\\", "/") + + // Check if this is a file path (contains line:column position) + // File paths look like: /path/to/file.go:123:45: message + // Package paths look like: package github.com/...: message + if strings.HasPrefix(msg, "package ") { + // Keep package warnings as-is + warnings[i] = msg + } else if idx := strings.Index(msg, "testcases/"); idx >= 0 { + // Check if it's a file path by looking for :line:column pattern after testcases/ + testcasesEnd := idx + len("testcases/") + colonIdx := strings.Index(msg[testcasesEnd:], ":") + if colonIdx > 0 { + // This looks like a file path, normalize it + warnings[i] = "/testcases/" + msg[testcasesEnd:] + } else { + // Not a file path, keep as-is + warnings[i] = msg + } + } else { + // Keep other warnings as-is + warnings[i] = msg + } + } + + for _, msg := range warnings { + // Prefix with [warn] to prevent GitHub Actions Go problem matcher + // from treating these as errors when diff output is shown + fmt.Fprint(log, "[warn] "+msg, render.Newline) + } + }() + } + } else if err != nil { + t.Error(err) + } + + for path, present := range test.want { + if !present { + t.Errorf("Missing output file '%s'", path) + } + } + }) + } +} + +// configString computes a subtest name from the given configuration. +func configString(options *flags.GenerateBindingsOptions) string { + lang := "JS" + if options.TS { + lang = "TS" + } + return fmt.Sprintf("lang=%s/UseInterfaces=%v/UseNames=%v", lang, options.UseInterfaces, options.UseNames) +} + +// outputCreator returns a FileCreator that detects want/got pairs +// and schedules them for comparison. +// +// If no corresponding want file exists, it is created and reported. +func outputCreator(t *testing.T, params *testParams) config.FileCreator { + var mu sync.Mutex + return config.FileCreatorFunc(func(path string) (io.WriteCloser, error) { + path = filepath.Clean(path) + prefixedPath := filepath.Join(params.outputDir, path) + + // Protect want map accesses. + mu.Lock() + defer mu.Unlock() + + if seen, ok := params.want[path]; ok { + // File exists: mark as seen and compare. + if seen { + t.Errorf("Duplicate output file '%s'", path) + } + params.want[path] = true + + // Open want file. + wf, err := os.Open(prefixedPath) + if err != nil { + return nil, err + } + + // Create or truncate got file. + ext := filepath.Ext(prefixedPath) + gf, err := os.Create(fmt.Sprintf("%s.got%s", prefixedPath[:len(prefixedPath)-len(ext)], ext)) + if err != nil { + return nil, err + } + + // Initialise comparer. + return &outputComparer{t, path, wf, gf}, nil + } else { + // File does not exist: create it. + t.Errorf("Unexpected output file '%s'", path) + params.want[path] = true + + if err := os.MkdirAll(filepath.Dir(prefixedPath), 0777); err != nil { + return nil, err + } + + return os.Create(prefixedPath) + } + }) +} + +// outputComparer is a io.WriteCloser that writes to got. +// +// When Close is called, it compares want to got; if they are identical, +// it deletes got; otherwise it reports a testing error. +type outputComparer struct { + t *testing.T + path string + want *os.File + got *os.File +} + +func (comparer *outputComparer) Write(data []byte) (int, error) { + return comparer.got.Write(data) +} + +func (comparer *outputComparer) Close() error { + defer comparer.want.Close() + defer comparer.got.Close() + + comparer.got.Seek(0, io.SeekStart) + + // Read want data. + want, err := io.ReadAll(comparer.want) + if err != nil { + comparer.t.Error(err) + return nil + } + + got, err := io.ReadAll(comparer.got) + if err != nil { + comparer.t.Error(err) + return nil + } + + if diff := cmp.Diff(want, got); diff != "" { + comparer.t.Errorf("Output file '%s' mismatch (-want +got):\n%s", comparer.path, diff) + } else { + // On success, delete got file. + comparer.got.Close() + if err := os.Remove(comparer.got.Name()); err != nil { + comparer.t.Error(err) + } + } + + return nil +} diff --git a/v3/internal/generator/includes.go b/v3/internal/generator/includes.go new file mode 100644 index 000000000..b8bed2392 --- /dev/null +++ b/v3/internal/generator/includes.go @@ -0,0 +1,100 @@ +package generator + +import ( + "io" + "os" + "path/filepath" + "slices" + "strings" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// generateIncludes copies included files to the package directory +// for the package summarised by the given index. +func (generator *Generator) generateIncludes(index *collect.PackageIndex) { + for name, path := range index.Package.Includes { + // Validate filename. + switch name { + case generator.renderer.ModelsFile(): + if index.HasExportedModels { + generator.logger.Errorf( + "package %s: included file '%s' collides with models filename; please rename the file or choose a different filename for models", + index.Package.Path, + path, + ) + return + } + + case generator.renderer.IndexFile(): + if !generator.options.NoIndex && !index.IsEmpty() { + generator.logger.Errorf( + "package %s: included file '%s' collides with JS/TS index filename; please rename the file or choose a different filename for JS/TS indexes", + index.Package.Path, + path, + ) + return + } + } + + // Validate against services. + service, ok := slices.BinarySearchFunc(index.Services, name, func(service *collect.ServiceInfo, name string) int { + return strings.Compare(generator.renderer.ServiceFile(service.Name), name) + }) + if ok { + generator.logger.Errorf( + "package %s: included file '%s' collides with filename for service %s; please rename either the file or the service", + index.Package.Path, + path, + index.Services[service].Name, + ) + return + } + + // Copy file to destination in separate goroutine. + generator.scheduler.Schedule(func() { + src, err := os.Open(path) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: could not read included file '%s'", index.Package.Path, path) + return + } + defer src.Close() + + stat, err := src.Stat() + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: could not read included file '%s'", index.Package.Path, path) + return + } + + if stat.IsDir() { + generator.logger.Errorf( + "package %s: included file '%s' is a directory; please glob or list all descendants explicitly", + index.Package.Path, + path, + ) + return + } + + dst, err := generator.creator.Create(filepath.Join(index.Package.Path, name)) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: could not write included file '%s'", index.Package.Path, name) + return + } + defer func() { + if err := dst.Close(); err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: could not write included file '%s'", index.Package.Path, name) + } + }() + + _, err = io.Copy(dst, src) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: could not copy included file '%s'", index.Package.Path, name) + } + }) + } +} diff --git a/v3/internal/generator/index.go b/v3/internal/generator/index.go new file mode 100644 index 000000000..e7a73c71f --- /dev/null +++ b/v3/internal/generator/index.go @@ -0,0 +1,53 @@ +package generator + +import ( + "path/filepath" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// generateIndex generates an index file from the given index information. +func (generator *Generator) generateIndex(index *collect.PackageIndex) { + defer generator.reportDualRoles(index) + + file, err := generator.creator.Create(filepath.Join(index.Package.Path, generator.renderer.IndexFile())) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: index generation failed", index.Package.Path) + return + } + defer func() { + if err := file.Close(); err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: index generation failed", index.Package.Path) + } + }() + + err = generator.renderer.Index(file, index) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: index generation failed", index.Package.Path) + } +} + +// reportDualRoles checks for models types that are also service types +// and emits a warning. +func (generator *Generator) reportDualRoles(index *collect.PackageIndex) { + services, models := index.Services, index.Models + for len(services) > 0 && len(models) > 0 { + if services[0].Name < models[0].Name { + services = services[1:] + } else if services[0].Name > models[0].Name { + models = models[1:] + } else { + generator.logger.Warningf( + "package %s: type %s has been marked both as a service and as a model; shadowing between the two may take place when importing generated JS indexes", + index.Package.Path, + services[0].Name, + ) + + services = services[1:] + models = models[1:] + } + } +} diff --git a/v3/internal/generator/load.go b/v3/internal/generator/load.go new file mode 100644 index 000000000..da09ff76f --- /dev/null +++ b/v3/internal/generator/load.go @@ -0,0 +1,123 @@ +package generator + +import ( + "go/ast" + "go/parser" + "go/token" + + "github.com/wailsapp/wails/v3/internal/generator/config" + "golang.org/x/tools/go/packages" +) + +// ResolveSystemPaths resolves paths for stdlib and Wails packages. +func ResolveSystemPaths(buildFlags []string) (paths *config.SystemPaths, err error) { + // Resolve context pkg path. + contextPkgPaths, err := ResolvePatterns(buildFlags, "context") + if err != nil { + return + } else if len(contextPkgPaths) < 1 { + err = ErrNoContextPackage + return + } else if len(contextPkgPaths) > 1 { + // This should never happen... + panic("context package path matched multiple packages") + } + + // Resolve wails app pkg path. + wailsAppPkgPaths, err := ResolvePatterns(buildFlags, config.WailsAppPkgPath) + if err != nil { + return + } else if len(wailsAppPkgPaths) < 1 { + err = ErrNoApplicationPackage + return + } else if len(wailsAppPkgPaths) > 1 { + // This should never happen... + panic("wails application package path matched multiple packages") + } + + // Resolve wails internal pkg path. + wailsInternalPkgPaths, err := ResolvePatterns(buildFlags, config.WailsInternalPkgPath) + if err != nil { + return + } else if len(wailsInternalPkgPaths) < 1 { + err = ErrNoInternalPackage + return + } else if len(wailsInternalPkgPaths) > 1 { + // This should never happen... + panic("wails internal package path matched multiple packages") + } + + paths = &config.SystemPaths{ + ContextPackage: contextPkgPaths[0], + ApplicationPackage: wailsAppPkgPaths[0], + InternalPackage: wailsInternalPkgPaths[0], + } + return +} + +// ResolvePatterns returns a slice containing all package paths +// that match the given patterns, according to the underlying build tool +// and within the context of the current working directory. +func ResolvePatterns(buildFlags []string, patterns ...string) (paths []string, err error) { + rewrittenPatterns := make([]string, len(patterns)) + for i, pattern := range patterns { + rewrittenPatterns[i] = "pattern=" + pattern + } + + pkgs, err := packages.Load(&packages.Config{ + Mode: packages.NeedName, + BuildFlags: buildFlags, + }, rewrittenPatterns...) + + for _, pkg := range pkgs { + paths = append(paths, pkg.PkgPath) + } + + return +} + +// LoadPackages loads the packages specified by the given patterns +// and their whole dependency tree. It returns a slice containing +// all packages that match the given patterns and all of their direct +// and indirect dependencies. +// +// The returned slice is in post-order w.r.t. the dependency relation, +// i.e. if package A depends on package B, then package B precedes package A. +// +// All returned package instances include syntax trees and full type information. +// +// Syntax is loaded in the context of a global [token.FileSet], +// which is available through the field [packages.Package.Fset] +// on each returned package. Therefore, source positions +// are canonical across all loaded packages. +func LoadPackages(buildFlags []string, patterns ...string) (pkgs []*packages.Package, err error) { + rewrittenPatterns := make([]string, len(patterns)) + for i, pattern := range patterns { + rewrittenPatterns[i] = "pattern=" + pattern + } + + // Global file set. + fset := token.NewFileSet() + + roots, err := packages.Load(&packages.Config{ + // NOTE: some Go maintainers now believe deprecation was an error and recommend using Load* modes + // (see e.g. https://github.com/golang/go/issues/48226#issuecomment-1948792315). + Mode: packages.LoadAllSyntax, + BuildFlags: buildFlags, + Fset: fset, + ParseFile: func(fset *token.FileSet, filename string, src []byte) (file *ast.File, err error) { + file, err = parser.ParseFile(fset, filename, src, parser.ParseComments|parser.SkipObjectResolution) + return + }, + }, rewrittenPatterns...) + + // Flatten dependency tree. + packages.Visit(roots, nil, func(pkg *packages.Package) { + if pkg.Fset != fset { + panic("fileset missing or not the global one") + } + pkgs = append(pkgs, pkg) + }) + + return +} diff --git a/v3/internal/generator/models.go b/v3/internal/generator/models.go new file mode 100644 index 000000000..2fe4f407f --- /dev/null +++ b/v3/internal/generator/models.go @@ -0,0 +1,39 @@ +package generator + +import ( + "path/filepath" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// generateModels generates a JS/TS models file for the given list of models. +// A call to info.Collect must complete before entering generateModels. +func (generator *Generator) generateModels(info *collect.PackageInfo, models []*collect.ModelInfo) { + // Merge all import maps. + imports := collect.NewImportMap(info) + for _, model := range models { + imports.Merge(model.Imports) + } + + // Clear irrelevant imports. + imports.ImportModels = false + + file, err := generator.creator.Create(filepath.Join(info.Path, generator.renderer.ModelsFile())) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: models generation failed", info.Path) + return + } + defer func() { + if err := file.Close(); err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: models generation failed", info.Path) + } + }() + + err = generator.renderer.Models(file, imports, models) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: models generation failed", info.Path) + } +} diff --git a/v3/internal/generator/render/create.go b/v3/internal/generator/render/create.go new file mode 100644 index 000000000..4b0f3484e --- /dev/null +++ b/v3/internal/generator/render/create.go @@ -0,0 +1,359 @@ +package render + +import ( + "fmt" + "go/types" + "strings" + "text/template" + + "github.com/wailsapp/wails/v3/internal/generator/collect" + "golang.org/x/tools/go/types/typeutil" +) + +// SkipCreate returns true if the given array of types needs no creation code. +func (m *module) SkipCreate(ts []types.Type) bool { + for _, typ := range ts { + if m.NeedsCreate(typ) { + return false + } + } + return true +} + +// NeedsCreate returns true if the given type needs some creation code. +func (m *module) NeedsCreate(typ types.Type) bool { + return m.needsCreateImpl(typ, new(typeutil.Map)) +} + +// needsCreateImpl provides the actual implementation of NeedsCreate. +// The visited parameter is used to break cycles. +func (m *module) needsCreateImpl(typ types.Type, visited *typeutil.Map) bool { + switch t := typ.(type) { + case *types.Alias: + if m.collector.IsVoidAlias(t.Obj()) { + return false + } + + return m.needsCreateImpl(types.Unalias(typ), visited) + + case *types.Named: + if visited.Set(typ, true) != nil { + // The only way to hit a cycle here + // is through a chain of structs, nested pointers and arrays (not slices). + // We can safely return false at this point + // as the final answer is independent of the cycle. + return false + } + + if t.Obj().Pkg() == nil { + // Builtin named type: render underlying type. + return m.needsCreateImpl(t.Underlying(), visited) + } + + if m.collector.IsVoidAlias(t.Obj()) { + return false + } + + if collect.IsAny(typ) || collect.IsStringAlias(typ) { + break + } else if collect.IsClass(typ) { + return true + } else { + return m.needsCreateImpl(t.Underlying(), visited) + } + + case *types.Array, *types.Pointer: + return m.needsCreateImpl(typ.(interface{ Elem() types.Type }).Elem(), visited) + + case *types.Map, *types.Slice: + return true + + case *types.Struct: + if t.NumFields() == 0 || collect.MaybeJSONMarshaler(typ) != collect.NonMarshaler || collect.MaybeTextMarshaler(typ) != collect.NonMarshaler { + return false + } + + info := m.collector.Struct(t) + info.Collect() + + for _, field := range info.Fields { + if m.needsCreateImpl(field.Type, visited) { + return true + } + } + + case *types.TypeParam: + return true + } + + return false +} + +// JSCreate renders JS/TS code that creates an instance +// of the given type from JSON data. +// +// JSCreate's output may be incorrect +// if m.Imports.AddType has not been called for the given type. +func (m *module) JSCreate(typ types.Type) string { + return m.JSCreateWithParams(typ, "") +} + +// JSCreateWithParams renders JS/TS code that creates an instance +// of the given type from JSON data. For generic types, +// it renders parameterised code. +// +// JSCreateWithParams's output may be incorrect +// if m.Imports.AddType has not been called for the given type. +func (m *module) JSCreateWithParams(typ types.Type, params string) string { + if len(params) > 0 && !collect.IsParametric(typ) { + // Forget params for non-generic types. + params = "" + } + + switch t := typ.(type) { + case *types.Alias: + if m.collector.IsVoidAlias(t.Obj()) { + return "$Create.Any" + } + + return m.JSCreateWithParams(types.Unalias(typ), params) + + case *types.Array, *types.Pointer: + pp, ok := m.postponedCreates.At(typ).(*postponed) + if ok { + return fmt.Sprintf("$$createType%d%s", pp.index, params) + } + + createElement := m.JSCreateWithParams(typ.(interface{ Elem() types.Type }).Elem(), params) + if createElement != "$Create.Any" { + pp = &postponed{m.postponedCreates.Len(), params} + m.postponedCreates.Set(typ, pp) + return fmt.Sprintf("$$createType%d%s", pp.index, params) + } + + case *types.Map: + pp, ok := m.postponedCreates.At(typ).(*postponed) + if !ok { + m.JSCreateWithParams(t.Elem(), params) + pp = &postponed{m.postponedCreates.Len(), params} + m.postponedCreates.Set(typ, pp) + } + + return fmt.Sprintf("$$createType%d%s", pp.index, params) + + case *types.Named: + if t.Obj().Pkg() == nil { + // Builtin named type: render underlying type. + return m.JSCreateWithParams(t.Underlying(), params) + } + + if m.collector.IsVoidAlias(t.Obj()) { + return "$Create.Any" + } + + if !m.NeedsCreate(typ) { + break + } + + pp, ok := m.postponedCreates.At(typ).(*postponed) + if !ok { + if t.TypeArgs() != nil && t.TypeArgs().Len() > 0 { + // Postpone type args. + for i := range t.TypeArgs().Len() { + m.JSCreateWithParams(t.TypeArgs().At(i), params) + } + } + + pp = &postponed{m.postponedCreates.Len(), params} + m.postponedCreates.Set(typ, pp) + + if !collect.IsClass(typ) { + m.JSCreateWithParams(t.Underlying(), params) + } + } + + return fmt.Sprintf("$$createType%d%s", pp.index, params) + + case *types.Slice: + if types.Identical(typ, typeByteSlice) { + return "$Create.ByteSlice" + } + + pp, ok := m.postponedCreates.At(typ).(*postponed) + if !ok { + m.JSCreateWithParams(t.Elem(), params) + pp = &postponed{m.postponedCreates.Len(), params} + m.postponedCreates.Set(typ, pp) + } + + return fmt.Sprintf("$$createType%d%s", pp.index, params) + + case *types.Struct: + if t.NumFields() == 0 || collect.MaybeJSONMarshaler(typ) != collect.NonMarshaler || collect.MaybeTextMarshaler(typ) != collect.NonMarshaler { + break + } + + pp, ok := m.postponedCreates.At(typ).(*postponed) + if ok { + return fmt.Sprintf("$$createType%d%s", pp.index, params) + } + + info := m.collector.Struct(t) + info.Collect() + + postpone := false + for _, field := range info.Fields { + if m.JSCreateWithParams(field.Type, params) != "$Create.Any" { + postpone = true + } + } + + if postpone { + pp = &postponed{m.postponedCreates.Len(), params} + m.postponedCreates.Set(typ, pp) + return fmt.Sprintf("$$createType%d%s", pp.index, params) + } + + case *types.TypeParam: + return fmt.Sprintf("$$createParam%s", typeparam(t.Index(), t.Obj().Name())) + } + + return "$Create.Any" +} + +// PostponedCreates returns the list of postponed create functions +// for the given module. +func (m *module) PostponedCreates() []string { + result := make([]string, m.postponedCreates.Len()) + + m.postponedCreates.Iterate(func(key types.Type, value any) { + pp := value.(*postponed) + + pre, post := "", "" + if pp.params != "" { + if m.TS { + pre = createParamRegex.ReplaceAllString(pp.params, "${0}: any") + " => " + } else { + pre = "/** @type {(...args: any[]) => any} */(" + pp.params + " => " + post = ")" + } + } + + switch t := key.(type) { + case *types.Array, *types.Slice: + result[pp.index] = fmt.Sprintf("%s$Create.Array(%s)%s", pre, m.JSCreateWithParams(t.(interface{ Elem() types.Type }).Elem(), pp.params), post) + + case *types.Map: + result[pp.index] = fmt.Sprintf("%s$Create.Map($Create.Any, %s)%s", pre, m.JSCreateWithParams(t.Elem(), pp.params), post) + + case *types.Named: + if !collect.IsClass(key) { + // Creation functions for non-struct named types + // require an indirect assignment to break cycles. + + // Typescript cannot infer the return type on its own: add hints. + cast, argType, returnType := "", "", "" + if m.TS { + argType = ": any[]" + returnType = ": any" + } else { + cast = "/** @type {(...args: any[]) => any} */" + } + + result[pp.index] = fmt.Sprintf(` +%s(function $$initCreateType%d(...args%s)%s { + if ($$createType%d === $$initCreateType%d) { + $$createType%d = %s%s%s; + } + return $$createType%d(...args); +})`, + cast, pp.index, argType, returnType, + pp.index, pp.index, + pp.index, pre, m.JSCreateWithParams(t.Underlying(), pp.params), post, + pp.index, + )[1:] // Remove initial newline. + + // We're done. + break + } + + var builder strings.Builder + + builder.WriteString(pre) + + if t.Obj().Pkg().Path() == m.Imports.Self { + if m.Imports.ImportModels { + builder.WriteString("$models.") + } + } else { + builder.WriteString(jsimport(m.Imports.External[t.Obj().Pkg().Path()])) + builder.WriteRune('.') + } + builder.WriteString(jsid(t.Obj().Name())) + builder.WriteString(".createFrom") + + if t.TypeArgs() != nil && t.TypeArgs().Len() > 0 { + builder.WriteString("(") + for i := range t.TypeArgs().Len() { + if i > 0 { + builder.WriteString(", ") + } + builder.WriteString(m.JSCreateWithParams(t.TypeArgs().At(i), pp.params)) + } + builder.WriteString(")") + } + builder.WriteString(post) + + result[pp.index] = builder.String() + + case *types.Pointer: + result[pp.index] = fmt.Sprintf("%s$Create.Nullable(%s)%s", pre, m.JSCreateWithParams(t.Elem(), pp.params), post) + + case *types.Struct: + info := m.collector.Struct(t) + info.Collect() + + var builder strings.Builder + builder.WriteString(pre) + builder.WriteString("$Create.Struct({") + + for _, field := range info.Fields { + createField := m.JSCreateWithParams(field.Type, pp.params) + if createField == "$Create.Any" { + continue + } + + builder.WriteString("\n \"") + template.JSEscape(&builder, []byte(field.JsonName)) + builder.WriteString("\": ") + builder.WriteString(createField) + builder.WriteRune(',') + } + + if len(info.Fields) > 0 { + builder.WriteRune('\n') + } + builder.WriteString("})") + builder.WriteString(post) + + result[pp.index] = builder.String() + + default: + result[pp.index] = pre + "$Create.Any" + post + } + }) + + if Newline != "\n" { + // Replace newlines according to local git config. + for i := range result { + result[i] = strings.ReplaceAll(result[i], "\n", Newline) + } + } + + return result +} + +type postponed struct { + index int + params string +} diff --git a/v3/internal/generator/render/default.go b/v3/internal/generator/render/default.go new file mode 100644 index 000000000..793c704ba --- /dev/null +++ b/v3/internal/generator/render/default.go @@ -0,0 +1,179 @@ +package render + +import ( + "fmt" + "go/types" + "strings" + "text/template" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// JSDefault renders the Javascript representation +// of the zero value of the given type, +// using the receiver's import map to resolve dependencies. +// +// JSDefault's output may be incorrect +// if imports.AddType has not been called for the given type. +func (m *module) JSDefault(typ types.Type, quoted bool) (result string) { + switch t := typ.(type) { + case *types.Alias, *types.Named: + result, ok := m.renderNamedDefault(t.(aliasOrNamed), quoted) + if ok { + return result + } + + case *types.Array: + if t.Len() == 0 { + return "[]" + } else { + // Initialise array with expected number of elements + return fmt.Sprintf("Array.from({ length: %d }, () => %s)", t.Len(), m.JSDefault(t.Elem(), false)) + } + + case *types.Slice: + if types.Identical(typ, typeByteSlice) { + return `""` + } else { + return "[]" + } + + case *types.Basic: + return m.renderBasicDefault(t, quoted) + + case *types.Map: + return "{}" + + case *types.Struct: + return m.renderStructDefault(t) + + case *types.TypeParam: + // Should be unreachable + panic("type parameters have no default value") + } + + // Fall back to null. + // encoding/json ignores null values so this is safe. + return "null" +} + +// renderBasicDefault outputs the Javascript representation +// of the zero value for the given basic type. +func (*module) renderBasicDefault(typ *types.Basic, quoted bool) string { + switch { + case typ.Info()&types.IsBoolean != 0: + if quoted { + return `"false"` + } else { + return "false" + } + + case typ.Info()&types.IsNumeric != 0 && typ.Info()&types.IsComplex == 0: + if quoted { + return `"0"` + } else { + return "0" + } + + case typ.Info()&types.IsString != 0: + if quoted { + return `'""'` + } else { + return `""` + } + } + + // Fall back to untyped mode. + if quoted { + return `""` + } else { + // encoding/json ignores null values so this is safe. + return "null" + } +} + +// renderNamedDefault outputs the Javascript representation +// of the zero value for the given alias or named type. +// The result field named 'ok' is true when the resulting code is valid. +// If false, it must be discarded. +func (m *module) renderNamedDefault(typ aliasOrNamed, quoted bool) (result string, ok bool) { + if typ.Obj().Pkg() == nil { + // Builtin alias or named type: render underlying type. + return m.JSDefault(typ.Underlying(), quoted), true + } + + if quoted { + // WARN: Do not test with IsAny/IsStringAlias here!! We only want to catch marshalers. + if collect.MaybeJSONMarshaler(typ) == collect.NonMarshaler && collect.MaybeTextMarshaler(typ) == collect.NonMarshaler { + if basic, ok := typ.Underlying().(*types.Basic); ok { + // Quoted mode for basic alias/named type that is not a marshaler: delegate. + return m.renderBasicDefault(basic, quoted), true + } + // No need to handle typeparams: they are initialised to null anyways. + } + } + + prefix := "" + if m.Imports.ImportModels { + prefix = "$models." + } + + if collect.IsAny(typ) { + return "", false + } else if collect.MaybeTextMarshaler(typ) != collect.NonMarshaler { + return `""`, true + } else if collect.IsClass(typ) && !istpalias(typ) { + if typ.Obj().Pkg().Path() == m.Imports.Self { + return fmt.Sprintf("(new %s%s())", prefix, jsid(typ.Obj().Name())), true + } else { + return fmt.Sprintf("(new %s.%s())", jsimport(m.Imports.External[typ.Obj().Pkg().Path()]), jsid(typ.Obj().Name())), true + } + } else if _, isAlias := typ.(*types.Alias); isAlias { + return m.JSDefault(types.Unalias(typ), quoted), true + } else if len(m.collector.Model(typ.Obj()).Collect().Values) > 0 { + if typ.Obj().Pkg().Path() == m.Imports.Self { + return fmt.Sprintf("%s%s.$zero", prefix, jsid(typ.Obj().Name())), true + } else { + return fmt.Sprintf("%s.%s.$zero", jsimport(m.Imports.External[typ.Obj().Pkg().Path()]), jsid(typ.Obj().Name())), true + } + } else { + return m.JSDefault(typ.Underlying(), quoted), true + } +} + +// renderStructDefault outputs the Javascript representation +// of the zero value for the given struct type. +func (m *module) renderStructDefault(typ *types.Struct) string { + if collect.MaybeJSONMarshaler(typ) != collect.NonMarshaler { + return "null" + } else if collect.MaybeTextMarshaler(typ) != collect.NonMarshaler { + return `""` + } + + info := m.collector.Struct(typ) + info.Collect() + + var builder strings.Builder + + builder.WriteRune('{') + for i, field := range info.Fields { + if field.Optional { + continue + } + + if i > 0 { + builder.WriteString(", ") + } + + builder.WriteRune('"') + template.JSEscape(&builder, []byte(field.JsonName)) + builder.WriteRune('"') + + builder.WriteString(": ") + + builder.WriteString(m.JSDefault(field.Type, field.Quoted)) + } + builder.WriteRune('}') + + return builder.String() +} diff --git a/v3/internal/generator/render/doc.go b/v3/internal/generator/render/doc.go new file mode 100644 index 000000000..8ed46d9c5 --- /dev/null +++ b/v3/internal/generator/render/doc.go @@ -0,0 +1,136 @@ +package render + +import ( + "bufio" + "bytes" + "go/ast" + "strings" + "unicode" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// hasdoc checks whether the given comment group contains actual doc comments. +func hasdoc(group *ast.CommentGroup) bool { + if group == nil { + return false + } + + // TODO: this is horrible, make it more efficient? + return strings.ContainsFunc(group.Text(), func(r rune) bool { return !unicode.IsSpace(r) }) +} + +var commentTerminator = []byte("*/") + +// jsdoc splits the given comment into lines and rewrites it as follows: +// - first, line terminators are stripped; +// - then a line terminator, the indent string and ' * ' +// are prepended to each line; +// - occurrences of the comment terminator '*/' are replaced with '* /' +// to avoid accidentally terminating the surrounding comment. +// +// All lines thus modified are joined back together. +// +// The returned string can be inserted in a multiline JSDoc comment +// with the given indentation. +func jsdoc(comment string, indent string) string { + var builder strings.Builder + prefix := []byte(Newline + indent + " * ") + + scanner := bufio.NewScanner(bytes.NewReader([]byte(comment))) + for scanner.Scan() { + line := scanner.Bytes() + + // Prepend prefix. + builder.Write(prefix) + + // Escape comment terminators. + for t := bytes.Index(line, commentTerminator); t >= 0; t = bytes.Index(line, commentTerminator) { + builder.Write(line[:t+1]) + builder.WriteRune(' ') + line = line[t+1:] + } + + builder.Write(line) + } + + return builder.String() +} + +// jsdocline removes all newlines in the given comment +// and escapes comment terminators using the same strategy as jsdoc. +func jsdocline(comment string) string { + var builder strings.Builder + + scanner := bufio.NewScanner(bytes.NewReader([]byte(comment))) + for scanner.Scan() { + line := bytes.TrimSpace(scanner.Bytes()) + if len(line) == 0 { + // Skip empty lines. + continue + } + + // Prepend space to separate lines. + builder.WriteRune(' ') + + // Escape comment terminators. + for t := bytes.Index(line, commentTerminator); t >= 0; t = bytes.Index(line, commentTerminator) { + builder.Write(line[:t+1]) + builder.WriteRune(' ') + line = line[t+1:] + } + + builder.Write(line) + } + + // Return resulting string, but skip initial space. + return builder.String()[1:] +} + +// isjsdocid returns true if the given string is a valid ECMAScript identifier, +// excluding unicode escape sequences. This is the property name format supported by JSDoc. +func isjsdocid(name string) bool { + for i, r := range name { + if i == 0 && !id_start(r) && r != '$' && r != '_' { + return false + } else if i > 0 && !id_continue(r) && r != '$' { + return false + } + } + return true +} + +// isjsdocobj returns true if all field names in the given model +// are valid jsdoc property names. +func isjsdocobj(model *collect.ModelInfo) bool { + if len(model.Fields) == 0 { + return false + } + for _, decl := range model.Fields { + for _, field := range decl { + if !isjsdocid(field.JsonName) { + return false + } + } + } + return true +} + +// id_start returns true if the given rune is in the ID_Start category +// according to UAX#31 (https://unicode.org/reports/tr31/). +func id_start(r rune) bool { + return (unicode.IsLetter(r) || + unicode.Is(unicode.Nl, r) || + unicode.Is(unicode.Other_ID_Start, r)) && !unicode.Is(unicode.Pattern_Syntax, r) && !unicode.Is(unicode.Pattern_White_Space, r) +} + +// id_continue returns true if the given rune is in the ID_Continue category +// according to UAX#31 (https://unicode.org/reports/tr31/). +func id_continue(r rune) bool { + return (id_start(r) || + unicode.Is(unicode.Mn, r) || + unicode.Is(unicode.Mc, r) || + unicode.Is(unicode.Nd, r) || + unicode.Is(unicode.Pc, r) || + unicode.Is(unicode.Other_ID_Continue, r)) && !unicode.Is(unicode.Pattern_Syntax, r) && !unicode.Is(unicode.Pattern_White_Space, r) +} diff --git a/v3/internal/generator/render/functions.go b/v3/internal/generator/render/functions.go new file mode 100644 index 000000000..6123095e8 --- /dev/null +++ b/v3/internal/generator/render/functions.go @@ -0,0 +1,107 @@ +package render + +import ( + "fmt" + "go/types" + "math/big" + "strconv" + "strings" + "text/template" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// tmplFunctions holds a map of utility functions +// that should be available in every template. +var tmplFunctions = template.FuncMap{ + "fixext": fixext, + "hasdoc": hasdoc, + "isjsdocid": isjsdocid, + "isjsdocobj": isjsdocobj, + "istpalias": istpalias, + "jsdoc": jsdoc, + "jsdocline": jsdocline, + "jsid": jsid, + "jsimport": jsimport, + "jsparam": jsparam, + "jsvalue": jsvalue, + "modelinfo": modelinfo, + "typeparam": typeparam, + "unalias": types.Unalias, +} + +// fixext replaces a *.ts extension with *.js in the given string. +// This is necessary to allow emitting javascript with the Typescript compiler. +func fixext(path string) string { + if strings.HasSuffix(path, ".ts") { + return path[:len(path)-3] + ".js" + } else { + return path + } +} + +// jsimport formats an external import name +// by joining the name with its occurrence index. +// Names are modified even when the index is 0 +// to avoid collisions with Go identifiers. +func jsimport(info collect.ImportInfo) string { + return fmt.Sprintf("%s$%d", info.Name, info.Index) +} + +// jsparam renders the JS name of a parameter. +// Blank parameters are replaced with a dollar sign followed by the given index. +// Non-blank parameters are escaped by [jsid]. +func jsparam(index int, param *collect.ParamInfo) string { + if param.Blank { + return "$" + strconv.Itoa(index) + } else { + return jsid(param.Name) + } +} + +// typeparam renders the TS name of a type parameter. +// Blank parameters are replaced with a double dollar sign +// followed by the given index. +// Non-blank parameters are escaped with jsid. +func typeparam(index int, param string) string { + if param == "" || param == "_" { + return "$$" + strconv.Itoa(index) + } else { + return jsid(param) + } +} + +// jsvalue renders a Go constant value to its Javascript representation. +func jsvalue(value any) string { + switch v := value.(type) { + case bool: + if v { + return "true" + } else { + return "false" + } + case string: + return fmt.Sprintf(`"%s"`, template.JSEscapeString(v)) + case int64: + return strconv.FormatInt(v, 10) + case *big.Int: + return v.String() + case *big.Float: + return v.Text('e', -1) + case *big.Rat: + return v.RatString() + } + + // Fall back to undefined. + return "(void(0))" +} + +// istpalias determines whether typ is an alias +// that when uninstantiated resolves to a typeparam. +func istpalias(typ types.Type) bool { + if alias, ok := typ.(*types.Alias); ok { + return collect.IsTypeParam(alias.Origin()) + } + + return false +} diff --git a/v3/internal/generator/render/identifier.go b/v3/internal/generator/render/identifier.go new file mode 100644 index 000000000..7a7b89f9d --- /dev/null +++ b/v3/internal/generator/render/identifier.go @@ -0,0 +1,92 @@ +package render + +import ( + "slices" +) + +// jsid escapes identifiers that match JS/TS reserved words +// by prepending a dollar sign. +func jsid(ident string) string { + if _, reserved := slices.BinarySearch(protectedWords, ident); reserved { + return "$" + ident + } + return ident +} + +func init() { + // Ensure reserved words are sorted in ascending lexicographical order. + slices.Sort(protectedWords) +} + +// protectedWords is a list of JS + TS words that are either reserved +// or have special meaning. Keep in ascending lexicographical order +// for best startup performance. +var protectedWords = []string{ + "JSON", + "Object", + "any", + "arguments", + "as", + "async", + "await", + "boolean", + "break", + "case", + "catch", + "class", + "const", + "constructor", + "continue", + "debugger", + "declare", + "default", + "delete", + "do", + "else", + "enum", + "export", + "extends", + "false", + "finally", + "for", + "from", + "function", + "get", + "if", + "implements", + "import", + "in", + "instanceof", + "interface", + "let", + "module", + "namespace", + "new", + "null", + "number", + "of", + "package", + "private", + "protected", + "public", + "require", + "return", + "set", + "static", + "string", + "super", + "switch", + "symbol", + "this", + "throw", + "true", + "try", + "type", + "typeof", + "undefined", + "var", + "void", + "while", + "with", + "yield", +} diff --git a/v3/internal/generator/render/info.go b/v3/internal/generator/render/info.go new file mode 100644 index 000000000..3947f117a --- /dev/null +++ b/v3/internal/generator/render/info.go @@ -0,0 +1,77 @@ +package render + +import ( + "regexp" + "strings" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// modelInfo gathers useful information about a model. +type modelInfo struct { + HasValues bool + IsEnum bool + + IsAlias bool + IsClassAlias bool + IsTypeAlias bool + + IsClassOrInterface bool + IsInterface bool + IsClass bool + + Template struct { + Params string + ParamList string + CreateList string + } +} + +// createParamRegex must match type parameter creation strings as generated by [modelinfo]. +var createParamRegex = regexp.MustCompile(`\$\$createParam[^\s,)]*`) + +// modelinfo gathers and returns useful information about the given model. +func modelinfo(model *collect.ModelInfo, useInterfaces bool) (info modelInfo) { + info.HasValues = len(model.Values) > 0 + info.IsEnum = info.HasValues && !model.Alias + + info.IsAlias = !info.IsEnum && model.Type != nil + info.IsClassAlias = info.IsAlias && model.Predicates.IsClass && !useInterfaces + info.IsTypeAlias = info.IsAlias && !info.IsClassAlias + + info.IsClassOrInterface = !info.IsEnum && !info.IsAlias + info.IsInterface = info.IsClassOrInterface && (model.Alias || useInterfaces) + info.IsClass = info.IsClassOrInterface && !info.IsInterface + + if len(model.TypeParams) > 0 { + var params, paramList, createList strings.Builder + + paramList.WriteRune('<') + createList.WriteRune('(') + + for i, param := range model.TypeParams { + param = typeparam(i, param) + + if i > 0 { + params.WriteRune(',') + paramList.WriteString(", ") + createList.WriteString(", ") + } + + params.WriteString(param) + paramList.WriteString(param) + + createList.WriteString("$$createParam") + createList.WriteString(param) + } + + paramList.WriteRune('>') + createList.WriteRune(')') + + info.Template.Params = params.String() + info.Template.ParamList = paramList.String() + info.Template.CreateList = createList.String() + } + + return +} diff --git a/v3/internal/generator/render/module.go b/v3/internal/generator/render/module.go new file mode 100644 index 000000000..3611c939a --- /dev/null +++ b/v3/internal/generator/render/module.go @@ -0,0 +1,26 @@ +package render + +import ( + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/generator/collect" + "golang.org/x/tools/go/types/typeutil" +) + +// module gathers data that is used when rendering a single JS/TS module. +type module struct { + *Renderer + *flags.GenerateBindingsOptions + + Imports *collect.ImportMap + + postponedCreates typeutil.Map +} + +// Runtime returns the import path for the Wails JS runtime module. +func (m *module) Runtime() string { + if m.UseBundledRuntime { + return "/wails/runtime.js" + } else { + return "@wailsio/runtime" + } +} diff --git a/v3/internal/generator/render/renderer.go b/v3/internal/generator/render/renderer.go new file mode 100644 index 000000000..81a6f5871 --- /dev/null +++ b/v3/internal/generator/render/renderer.go @@ -0,0 +1,183 @@ +package render + +import ( + "go/types" + "io" + "slices" + "strings" + "text/template" + + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// Renderer holds the template set for a given configuration. +// It provides methods for rendering various output modules. +type Renderer struct { + collector *collect.Collector + options *flags.GenerateBindingsOptions + + ext string + + service *template.Template + models *template.Template +} + +// NewRenderer initialises a code renderer +// for the given configuration and data collector. +func NewRenderer(collector *collect.Collector, options *flags.GenerateBindingsOptions) *Renderer { + ext := ".js" + if options.TS { + ext = ".ts" + } + + return &Renderer{ + collector: collector, + options: options, + + ext: ext, + + service: tmplService[tmplLanguage(options.TS)], + models: tmplModels[tmplLanguage(options.TS)], + } +} + +// ServiceFile returns the standard name of a service file +// for the given struct name, with the appropriate extension. +func (renderer *Renderer) ServiceFile(name string) string { + return strings.ToLower(name) + renderer.ext +} + +// ModelsFile returns the standard name of a models file +// with the appropriate extension. +func (renderer *Renderer) ModelsFile() string { + return renderer.options.ModelsFilename + renderer.ext +} + +// EventDataFile returns the standard name of the event data definitions file +// with the appropriate extension. +func (renderer *Renderer) EventDataFile() string { + return "eventdata.d.ts" +} + +// EventCreateFile returns the standard name of the event data creation file +// with the appropriate extension. +func (renderer *Renderer) EventCreateFile() string { + return "eventcreate" + renderer.ext +} + +// IndexFile returns the standard name of a package index file +// with the appropriate extension. +func (renderer *Renderer) IndexFile() string { + return renderer.options.IndexFilename + renderer.ext +} + +// Service renders binding code for the given service type to w. +func (renderer *Renderer) Service(w io.Writer, info *collect.ServiceInfo) error { + return renderer.service.Execute(w, &struct { + module + Service *collect.ServiceInfo + }{ + module{ + Renderer: renderer, + GenerateBindingsOptions: renderer.options, + Imports: info.Imports, + }, + info, + }) +} + +// Models renders type definitions for the given list of models. +func (renderer *Renderer) Models(w io.Writer, imports *collect.ImportMap, models []*collect.ModelInfo) error { + if !renderer.options.UseInterfaces { + // Sort class aliases after the class they alias. + // Works in amortized linear time thanks to an auxiliary map. + + // Track postponed class aliases and their dependencies. + aliases := make(map[types.Object][]*collect.ModelInfo, len(models)) + + models = slices.Clone(models) + for i, j := 0, 0; i < len(models); i++ { + if models[i].Type != nil && models[i].Predicates.IsClass { + // models[i] is a class alias: + // models[i].Type is guaranteed to be + // either an alias or a named type + obj := models[i].Type.(interface{ Obj() *types.TypeName }).Obj() + if obj.Pkg().Path() == imports.Self { + // models[i] aliases a type from the current module. + if a, ok := aliases[obj]; !ok || len(a) > 0 { + // The aliased type has not been visited already, postpone. + aliases[obj] = append(a, models[i]) + continue + } + } + } + + // Append models[i]. + models[j] = models[i] + j++ + + // Keep appending aliases whose aliased type has been just appended. + for k := j - 1; k < j; k++ { + a := aliases[models[k].Object()] + aliases[models[k].Object()] = nil // Mark aliased model as visited + j += copy(models[j:], a) + } + } + } + + return renderer.models.Execute(w, &struct { + module + Models []*collect.ModelInfo + }{ + module{ + Renderer: renderer, + GenerateBindingsOptions: renderer.options, + Imports: imports, + }, + models, + }) +} + +// EventData renders the given event map to w as an event data table. +func (renderer *Renderer) EventData(w io.Writer, events *collect.EventMap) error { + return tmplEventData.Execute(w, &struct { + module + Events *collect.EventMap + }{ + module{ + Renderer: renderer, + GenerateBindingsOptions: renderer.options, + Imports: events.Imports, + }, + events, + }) +} + +// EventCreate renders the given event map to w as event data creation code. +func (renderer *Renderer) EventCreate(w io.Writer, events *collect.EventMap) error { + return tmplEventCreate.Execute(w, &struct { + module + Events *collect.EventMap + }{ + module{ + Renderer: renderer, + GenerateBindingsOptions: renderer.options, + Imports: events.Imports, + }, + events, + }) +} + +// Index renders the given package index to w. +func (renderer *Renderer) Index(w io.Writer, index *collect.PackageIndex) error { + return tmplIndex.Execute(w, &struct { + *collect.PackageIndex + *Renderer + *flags.GenerateBindingsOptions + }{ + index, + renderer, + renderer.options, + }) +} diff --git a/v3/internal/generator/render/templates.go b/v3/internal/generator/render/templates.go new file mode 100644 index 000000000..5b137d665 --- /dev/null +++ b/v3/internal/generator/render/templates.go @@ -0,0 +1,42 @@ +package render + +import ( + "embed" + "strings" + "text/template" +) + +//go:embed templates/*.tmpl +var templates embed.FS + +type tmplLanguage bool + +const tmplJS, tmplTS tmplLanguage = false, true + +var tmplService = map[tmplLanguage]*template.Template{ + tmplJS: template.Must(template.New("service.js.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/service.js.tmpl")), + tmplTS: template.Must(template.New("service.ts.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/service.ts.tmpl")), +} + +var tmplModels = map[tmplLanguage]*template.Template{ + tmplJS: template.Must(template.New("models.js.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/models.js.tmpl")), + tmplTS: template.Must(template.New("models.ts.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/models.ts.tmpl")), +} + +var tmplEventData = template.Must(template.New("eventdata.d.ts.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/eventdata.d.ts.tmpl")) +var tmplEventCreate = template.Must(template.New("eventcreate.js.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/eventcreate.js.tmpl")) + +var tmplIndex = template.Must(template.New("index.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/index.tmpl")) + +var Newline string + +func init() { + var builder strings.Builder + + err := template.Must(template.New("newline.tmpl").ParseFS(templates, "templates/newline.tmpl")).Execute(&builder, nil) + if err != nil { + panic(err) + } + + Newline = builder.String() +} diff --git a/v3/internal/generator/render/templates/eventcreate.js.tmpl b/v3/internal/generator/render/templates/eventcreate.js.tmpl new file mode 100644 index 000000000..2aa8eb254 --- /dev/null +++ b/v3/internal/generator/render/templates/eventcreate.js.tmpl @@ -0,0 +1,47 @@ +{{$module := .}} +{{- $runtime := $module.Runtime}} +{{- $models := (fixext $module.ModelsFile)}} +{{- $useInterfaces := $module.UseInterfaces}} +{{- $imports := $module.Imports}} +{{- with .Events -}} +//@ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "{{js $runtime}}"; +{{if (and (not $useInterfaces) .Defs)}} +{{- range $imports.External}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as {{jsimport .}} from "{{js .RelPath}}/{{js $models}}"; +{{- end}}{{if $imports.External}} +{{end}} +{{- if $imports.ImportModels}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./{{js $models}}"; +{{end}} +function configure() { + Object.freeze(Object.assign($Create.Events, { + {{- range .Defs}} + {{- $create := ($module.JSCreate .Data)}} + {{- if ne $create "$Create.Any"}} + "{{js .Name}}": {{$create}}, + {{- end}} + {{- end}} + })); +} +{{$postponed := $module.PostponedCreates}} +{{- if $postponed}} +// Private type creation functions +{{- range $i, $create := $postponed}} +{{if and (ge (len $create) 54) (eq (slice $create 39 54) "function $$init")}}var {{else}}const {{end -}} +$$createType{{$i}} = {{$create}}; +{{- end}} +{{end}} +configure(); +{{else}} +Object.freeze($Create.Events); +{{end}}{{end -}} diff --git a/v3/internal/generator/render/templates/eventdata.d.ts.tmpl b/v3/internal/generator/render/templates/eventdata.d.ts.tmpl new file mode 100644 index 000000000..9278a6a7c --- /dev/null +++ b/v3/internal/generator/render/templates/eventdata.d.ts.tmpl @@ -0,0 +1,32 @@ +{{$module := .}} +{{- $runtime := $module.Runtime}} +{{- $models := (fixext $module.ModelsFile)}} +{{- $imports := $module.Imports}} +{{- with .Events -}} +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +{{if .Defs}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type { Events } from "{{js $runtime}}"; +{{range $imports.External}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as {{jsimport .}} from "{{js .RelPath}}/{{js $models}}"; +{{- end}}{{if $imports.External}} +{{end}} +{{- if $imports.ImportModels}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as $models from "./{{js $models}}"; +{{end}} +declare module "{{js $runtime}}" { + namespace Events { + interface CustomEvents { + {{- range .Defs}} + "{{js .Name}}": {{$module.JSType .Data}}; + {{- end}} + } + } +} +{{end}}{{end -}} diff --git a/v3/internal/generator/render/templates/index.tmpl b/v3/internal/generator/render/templates/index.tmpl new file mode 100644 index 000000000..fd067bf50 --- /dev/null +++ b/v3/internal/generator/render/templates/index.tmpl @@ -0,0 +1,102 @@ +{{$renderer := .}} +{{- $useInterfaces := .UseInterfaces}} +{{- $models := (fixext .ModelsFile)}} +{{- if not .TS -}} +// @ts-check +{{end -}} +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +{{$hasDocs := false}} +{{- range .Package.Docs}} +{{- if hasdoc .}}{{$hasDocs := true}}{{break}}{{end}} +{{- end}} +{{- if $hasDocs}} +/** +{{- range .Package.Docs}} +{{- jsdoc .Text ""}} +{{- end}} + * @module + */ +{{end}} +{{- if .Services}} +{{- range .Services}}{{if .Internal}}{{break}}{{end}} +import * as {{jsid .Name}} from "./{{js (fixext ($renderer.ServiceFile .Name))}}"; +{{- end}} +export { +{{- range $i, $service := .Services}} + {{- if .Internal}}{{break}}{{end}} + {{- if gt $i 0}},{{end}} + {{jsid .Name}} +{{- end}} +}; +{{end}} + +{{- $hasObjects := false}} +{{- $hasTypes := false}} + +{{- range $model := .Models}} +{{- if $model.Internal}}{{break}}{{end}} + +{{- $info := modelinfo $model $useInterfaces }} + +{{- if or $info.HasValues $info.IsClassAlias $info.IsClass}} +{{- if not $hasObjects}} + {{- $hasObjects = true}} +export { +{{- else}},{{end}} + {{jsid $model.Name}} +{{- else}} + {{- $hasTypes = true}} +{{- end}} +{{- end}} +{{- if $hasObjects}} +} from "./{{js $models}}"; +{{end}} + +{{- if $hasTypes}} +{{- $hasTypes = false}} + +{{- if .TS}} +export type { +{{- else}} +import * as $models from "./{{js $models}}"; +{{end}} +{{- range $model := .Models}} +{{- if $model.Internal}}{{break}}{{end}} + +{{- $info := modelinfo $model $useInterfaces }} +{{- $template := $info.Template }} + +{{- if or $info.HasValues $info.IsClassAlias $info.IsClass}}{{continue}}{{end}} + +{{- if $renderer.TS}} + {{- if $hasTypes}},{{end}} + {{jsid $model.Name}} +{{- else}} +/** +{{- if hasdoc $model.Decl.Doc}} +{{- jsdoc $model.Decl.Doc.Text ""}}{{if hasdoc $model.Doc}} + *{{end}} +{{- end}} +{{- if hasdoc $model.Doc}} +{{- jsdoc $model.Doc.Text ""}} +{{- end}} +{{- if $template.ParamList}} + * @template {{$template.Params}} +{{- end}} + * @typedef {$models.{{jsid $model.Name}}{{$template.ParamList -}} } {{jsid $model.Name}} + */ +{{end}} + +{{- $hasTypes = true}} +{{- end}} + +{{- if .TS}} +} from "./{{js $models}}"; +{{end}} + +{{- end}} +{{- range .Package.Injections}} +{{.}} +{{- end}}{{if .Package.Injections}} +{{end -}} diff --git a/v3/internal/generator/render/templates/models.js.tmpl b/v3/internal/generator/render/templates/models.js.tmpl new file mode 100644 index 000000000..6c0e98b8b --- /dev/null +++ b/v3/internal/generator/render/templates/models.js.tmpl @@ -0,0 +1,207 @@ +{{$module := .}} +{{- $runtime := $module.Runtime}} +{{- $models := (fixext $module.ModelsFile)}} +{{- $useInterfaces := .UseInterfaces}} +{{- $imports := $module.Imports -}} +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +{{if not $useInterfaces}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "{{js $runtime}}"; +{{end -}} +{{range $imports.External}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as {{jsimport .}} from "{{js .RelPath}}/{{js $models}}"; +{{- end}}{{if $imports.External}} +{{end}} +{{- range $model := .Models}} + +{{- $info := modelinfo $model $useInterfaces }} +{{- $template := $info.Template }} + +{{- if or $template.ParamList (hasdoc $model.Decl.Doc) (hasdoc $model.Doc) $info.IsEnum $info.IsTypeAlias $info.IsInterface}} +/** +{{- if hasdoc $model.Decl.Doc}} +{{- jsdoc $model.Decl.Doc.Text ""}}{{if hasdoc $model.Doc}} + *{{end}} +{{- end}} +{{- if hasdoc $model.Doc}} +{{- jsdoc $model.Doc.Text ""}} +{{- end}} +{{- if and $template.ParamList (not $info.IsClassAlias)}} + * @template {{$template.Params}} +{{- end}} +{{- if $info.IsEnum}} + * @readonly + * @enum { {{- $module.JSType $model.Type -}} } +{{- else if $info.IsTypeAlias}} + * @typedef { {{- $module.JSType $model.Type -}} } {{jsid $model.Name}} +{{- else if $info.IsInterface}} +{{- if isjsdocobj $model}} + * @typedef {Object} {{jsid $model.Name}} +{{- range $i, $decl := $model.Fields}}{{range $j, $field := $decl}} + * @property { {{- $module.JSFieldType $field.StructField -}} } + {{- if $field.Optional}} [{{else}} {{end}}{{$field.JsonName}}{{if $field.Optional}}]{{end}} + {{- if hasdoc $field.Decl.Doc}} - {{jsdocline $field.Decl.Doc.Text}}{{end}} +{{- end}}{{end}} +{{- else}} + * @typedef { { +{{- range $i, $decl := $model.Fields}}{{range $j, $field := $decl}} + * "{{js $field.JsonName}}"{{if $field.Optional}}?{{end}}: {{$module.JSFieldType $field.StructField}}, +{{- end}}{{end}} + * } } {{jsid $model.Name}} +{{- end}} +{{- end}} + */ +{{- end}} +{{- if $info.HasValues}} +{{- if not $info.IsEnum}} + +/** + * Predefined constants for type {{jsid $model.Name}}. + * @namespace + */ +{{- end}} +export const {{jsid $model.Name}} = { +{{- if $info.IsEnum}} + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: {{$module.JSDefault $model.Type false}}, +{{end}} +{{- range $i, $decl := $model.Values}}{{range $j, $spec := $decl}}{{range $k, $value := $spec}} + {{- if and (ne $i 0) (eq $j 0) (eq $k 0)}} +{{end}} + {{- if or (and (eq $j 0) (eq $k 0) (hasdoc $value.Decl.Doc)) (and (eq $k 0) (hasdoc $value.Spec.Doc))}} + {{- if gt $j 0}} +{{end}} + /** + {{- if and (eq $j 0) (eq $k 0) (hasdoc $value.Decl.Doc)}} + {{- jsdoc $value.Decl.Doc.Text " "}}{{if and (eq $k 0) (hasdoc $value.Spec.Doc)}} + *{{end}} + {{- end}} + {{- if and (eq $k 0) (hasdoc $value.Spec.Doc)}} + {{- jsdoc $value.Spec.Doc.Text " "}} + {{- end}} + */ + {{- end}} + {{jsid $value.Name}}: {{jsvalue $value.Value}}, +{{- end}}{{end}}{{end}} +}; +{{else if $info.IsClassAlias}} +export const {{jsid $model.Name}} = {{if istpalias $model.Type -}} + {{$module.JSType (unalias $model.Type).Origin}}; +{{- else -}} + {{$module.JSType $model.Type.Origin}}; +{{- end}} + +/** +{{- if hasdoc $model.Decl.Doc}} +{{- jsdoc $model.Decl.Doc.Text ""}}{{if hasdoc $model.Doc}} + *{{end}} +{{- end}} +{{- if hasdoc $model.Doc}} +{{- jsdoc $model.Doc.Text ""}} +{{- end}} +{{- if $template.ParamList}} + * @template {{$template.Params}} +{{- end}} + * @typedef { {{- $module.JSType $model.Type -}} } {{jsid $model.Name}} + */ +{{else if and $info.IsClass}} +export class {{jsid $model.Name}} { + /** + * Creates a new {{jsid $model.Name}} instance. + * @param {Partial<{{jsid $model.Name}}{{$template.ParamList}}>} [$$source = {}] - The source object to create the {{jsid $model.Name}}. + */ + constructor($$source = {}) { + {{- range $decl := $model.Fields}}{{range $j, $field := $decl}} + {{- /* + In JS we need to set all properties explicitly + because JSDoc has no support for arbitrary property names yet. + See https://github.com/jsdoc/jsdoc/issues/1468 + + For optional fields we make the initialization code unreachable + and cast the false condition to any to prevent any complaint from Typescript. + */}} + if ({{if $field.Optional}}/** @type {any} */(false){{else}}!("{{js $field.JsonName}}" in $$source){{end}}) { + /** + {{- if and (eq $j 0) (hasdoc $field.Decl.Doc)}} + {{- jsdoc $field.Decl.Doc.Text " "}} + {{- end}} + * @member + * @type { {{- $module.JSFieldType $field.StructField}}{{if $field.Optional}} | undefined{{end -}} } + */ + this["{{js $field.JsonName}}"] = {{if $field.Optional}}undefined{{else}}{{$module.JSDefault $field.Type $field.Quoted}}{{end}}; + } + {{- end}}{{end}} + + Object.assign(this, $$source); + } + + /** + {{- if $template.ParamList}} + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class {{jsid $model.Name}}. + {{- range $i, $param := $model.TypeParams}} + {{- $param = (typeparam $i $param)}} + * @template [{{$param}}=any] + {{- end}} + {{- range $i, $param := $model.TypeParams}} + {{- $param = (typeparam $i $param)}} + * @param {(source: any) => {{$param -}} } $$createParam{{$param}} + {{- end}} + * @returns {($$source?: any) => {{jsid $model.Name}}{{$template.ParamList -}} } + {{- else}} + * Creates a new {{jsid $model.Name}} instance from a string or object. + * @param {any} [$$source = {}] + * @returns { {{- jsid $model.Name -}} } + {{- end}} + */ + static createFrom{{if $template.ParamList}}{{$template.CreateList}}{{else}}($$source = {}){{end}} { + {{- range $i, $spec := $model.Fields}}{{range $j, $field := $spec}} + {{- $create := ($module.JSCreateWithParams $field.Type $template.CreateList)}} + {{- if ne $create "$Create.Any"}} + const $$createField{{$i}}_{{$j}} = {{$create}}; + {{- end}} + {{- end}}{{end}} + {{- $indent := ""}} + {{- if $template.ParamList}} + {{- $indent = " "}} + return ($$source = {}) => { + {{- end}} + {{$indent}}let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + {{- range $i, $spec := $model.Fields}}{{range $j, $field := $spec}} + {{- if $module.NeedsCreate $field.Type}} + {{$indent}}if ("{{js $field.JsonName}}" in $$parsedSource) { + {{$indent}} $$parsedSource["{{js $field.JsonName}}"] = $$createField{{$i}}_{{$j}}($$parsedSource["{{js $field.JsonName}}"]); + {{$indent -}} } + {{- end}} + {{- end}}{{end}} + {{$indent}}return new {{jsid $model.Name}}(/** @type {Partial<{{jsid $model.Name}}{{$template.ParamList}}>} */($$parsedSource)); + {{- if $template.ParamList}} + }; + {{- end}} + } +} +{{else}} +{{- /* Rendered as a @typedef */}} +{{end}} +{{- end}} +{{- $postponed := $module.PostponedCreates}} +{{- if $postponed}} +// Private type creation functions +{{- range $i, $create := $postponed}} +{{if and (ge (len $create) 54) (eq (slice $create 39 54) "function $$init")}}var {{else}}const {{end -}} +$$createType{{$i}} = {{$create}}; +{{- end}} +{{end}} +{{- if $useInterfaces}} +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; +{{end -}} diff --git a/v3/internal/generator/render/templates/models.ts.tmpl b/v3/internal/generator/render/templates/models.ts.tmpl new file mode 100644 index 000000000..a6073af08 --- /dev/null +++ b/v3/internal/generator/render/templates/models.ts.tmpl @@ -0,0 +1,189 @@ +{{$module := .}} +{{- $runtime := $module.Runtime}} +{{- $models := (fixext $module.ModelsFile)}} +{{- $useInterfaces := .UseInterfaces}} +{{- $imports := $module.Imports -}} +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +{{if not $useInterfaces}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "{{js $runtime}}"; +{{end -}} +{{range $imports.External}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as {{jsimport .}} from "{{js .RelPath}}/{{js $models}}"; +{{- end}}{{if $imports.External}} +{{end}} +{{- range $model := .Models}} + +{{- $info := modelinfo $model $useInterfaces }} +{{- $template := $info.Template }} + +{{- if or (hasdoc $model.Decl.Doc) (hasdoc $model.Doc)}} +/** +{{- if hasdoc $model.Decl.Doc}} +{{- jsdoc $model.Decl.Doc.Text ""}}{{if hasdoc $model.Doc}} + *{{end}} +{{- end}} +{{- if hasdoc $model.Doc}} +{{- jsdoc $model.Doc.Text ""}} +{{- end}} + */ +{{- end}} +{{- if $info.IsEnum}} +export enum {{jsid $model.Name}} { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = {{$module.JSDefault $model.Type false}}, +{{range $i, $decl := $model.Values}}{{range $j, $spec := $decl}}{{range $k, $value := $spec}} + {{- if and (ne $i 0) (eq $j 0) (eq $k 0)}} +{{end}} + {{- if or (and (eq $j 0) (eq $k 0) (hasdoc $value.Decl.Doc)) (and (eq $k 0) (hasdoc $value.Spec.Doc))}} + {{- if gt $j 0}} +{{end}} + /** + {{- if and (eq $j 0) (eq $k 0) (hasdoc $value.Decl.Doc)}} + {{- jsdoc $value.Decl.Doc.Text " "}}{{if and (eq $k 0) (hasdoc $value.Spec.Doc)}} + *{{end}} + {{- end}} + {{- if and (eq $k 0) (hasdoc $value.Spec.Doc)}} + {{- jsdoc $value.Spec.Doc.Text " "}} + {{- end}} + */ + {{- end}} + {{jsid $value.Name}} = {{jsvalue $value.Value}}, + {{- end}}{{end}}{{end}} +}; +{{else if $info.IsClassAlias}} +export const {{jsid $model.Name}} = {{if istpalias $model.Type -}} + {{$module.JSType (unalias $model.Type).Origin}}; +{{- else -}} + {{$module.JSType $model.Type.Origin}}; +{{- end}} +{{- if or (hasdoc $model.Decl.Doc) (hasdoc $model.Doc)}} + +/** +{{- if hasdoc $model.Decl.Doc}} +{{- jsdoc $model.Decl.Doc.Text ""}}{{if hasdoc $model.Doc}} + *{{end}} +{{- end}} +{{- if hasdoc $model.Doc}} +{{- jsdoc $model.Doc.Text ""}} +{{- end}} + */ +{{- end}} +export type {{jsid $model.Name}}{{$template.ParamList}} = {{$module.JSType $model.Type}}; +{{else if $info.IsTypeAlias}} +export type {{jsid $model.Name}}{{$template.ParamList}} = {{$module.JSType $model.Type}}; +{{- if $info.HasValues}} + +/** + * Predefined constants for type {{jsid $model.Name}}. + * @namespace + */ +export const {{jsid $model.Name}} = { +{{- range $i, $decl := $model.Values}}{{range $j, $spec := $decl}}{{range $k, $value := $spec}} + {{- if and (ne $i 0) (eq $j 0) (eq $k 0)}} +{{end}} + {{- if or (and (eq $j 0) (eq $k 0) (hasdoc $value.Decl.Doc)) (and (eq $k 0) (hasdoc $value.Spec.Doc))}} + {{- if gt $j 0}} +{{end}} + /** + {{- if and (eq $j 0) (eq $k 0) (hasdoc $value.Decl.Doc)}} + {{- jsdoc $value.Decl.Doc.Text " "}}{{if and (eq $k 0) (hasdoc $value.Spec.Doc)}} + *{{end}} + {{- end}} + {{- if and (eq $k 0) (hasdoc $value.Spec.Doc)}} + {{- jsdoc $value.Spec.Doc.Text " "}} + {{- end}} + */ + {{- end}} + {{jsid $value.Name}}: {{jsvalue $value.Value}}, +{{- end}}{{end}}{{end}} +}; +{{- end}} +{{else if $info.IsClassOrInterface}} +export {{if $info.IsInterface}}interface{{else}}class{{end}} {{jsid $model.Name}}{{$template.ParamList}} { + {{- range $i, $decl := $model.Fields}}{{range $j, $field := $decl}} + {{- if and (eq $j 0) (hasdoc $field.Decl.Doc)}} + {{- if gt $i 0}} +{{end}} + /** + {{- jsdoc $field.Decl.Doc.Text " "}} + */ + {{- end}} + "{{js $field.JsonName}}"{{if $field.Optional}}?{{end}}: {{$module.JSFieldType $field.StructField}}; + {{- end}}{{end}} +{{- if $info.IsClass}} + + /** Creates a new {{jsid $model.Name}} instance. */ + constructor($$source: Partial<{{jsid $model.Name}}{{$template.ParamList}}> = {}) { + {{- range $spec := $model.Fields}}{{range $i, $field := $spec}}{{if not $field.Optional}} + if (!("{{js $field.JsonName}}" in $$source)) { + this["{{js $field.JsonName}}"] = {{$module.JSDefault $field.Type $field.Quoted}}; + } + {{- end}}{{end}}{{end}} + + Object.assign(this, $$source); + } + + /** + {{- if $template.ParamList}} + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class {{jsid $model.Name}}. + {{- else}} + * Creates a new {{jsid $model.Name}} instance from a string or object. + {{- end}} + */ + static createFrom{{if $template.ParamList}}< + {{- range $i, $param := $model.TypeParams}} + {{- $param = (typeparam $i $param)}} + {{- if gt $i 0}}, {{end -}} + {{$param}} = any + {{- end}}>{{end}}({{if $template.ParamList}} + {{- range $i, $param := $model.TypeParams}} + {{- $param = (typeparam $i $param)}} + {{- if gt $i 0}}, {{end -}} + $$createParam{{$param}}: (source: any) => {{$param}}{{end -}} + {{else}}$$source: any = {}{{end}}): + {{- if $template.ParamList}} ($$source?: any) =>{{end}} {{jsid $model.Name}}{{$template.ParamList}} { + {{- range $i, $spec := $model.Fields}}{{range $j, $field := $spec}} + {{- $create := ($module.JSCreateWithParams $field.Type $template.CreateList)}} + {{- if ne $create "$Create.Any"}} + const $$createField{{$i}}_{{$j}} = {{$create}}; + {{- end}} + {{- end}}{{end}} + {{- $indent := ""}} + {{- if $template.ParamList}} + {{- $indent = " "}} + return ($$source: any = {}) => { + {{- end}} + {{$indent}}let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + {{- range $i, $spec := $model.Fields}}{{range $j, $field := $spec}} + {{- if $module.NeedsCreate $field.Type}} + {{$indent}}if ("{{js $field.JsonName}}" in $$parsedSource) { + {{$indent}} $$parsedSource["{{js $field.JsonName}}"] = $$createField{{$i}}_{{$j}}($$parsedSource["{{js $field.JsonName}}"]); + {{$indent -}} } + {{- end}} + {{- end}}{{end}} + {{$indent}}return new {{jsid $model.Name}}{{$template.ParamList}}($$parsedSource as Partial<{{jsid $model.Name}}{{$template.ParamList}}>); + {{- if $template.ParamList}} + }; + {{- end}} + } +{{- end}} +} +{{end}} +{{- end}} +{{- $postponed := $module.PostponedCreates}} +{{- if $postponed}} +// Private type creation functions +{{- range $i, $create := $postponed}} +{{if and (ge (len $create) 16) (eq (slice $create 1 16) "function $$init")}}var {{else}}const {{end -}} +$$createType{{$i}} = {{$create}}; +{{- end}} +{{end -}} diff --git a/v3/internal/generator/render/templates/newline.tmpl b/v3/internal/generator/render/templates/newline.tmpl new file mode 100644 index 000000000..d28790668 --- /dev/null +++ b/v3/internal/generator/render/templates/newline.tmpl @@ -0,0 +1,6 @@ +{{/* This template should render to a single newline (either LF or CRLF). */}} +{{/* + Git might be configured to rewrite LF newlines to CRLF + in templates, test cases and data, especially on Windows. + Having a newline template enables detection of the current newline mode. +*/ -}} diff --git a/v3/internal/generator/render/templates/service.js.tmpl b/v3/internal/generator/render/templates/service.js.tmpl new file mode 100644 index 000000000..f71e055bf --- /dev/null +++ b/v3/internal/generator/render/templates/service.js.tmpl @@ -0,0 +1,100 @@ +{{$module := .}} +{{- $runtime := $module.Runtime}} +{{- $models := (fixext $module.ModelsFile)}} +{{- $useNames := $module.UseNames}} +{{- $useInterfaces := $module.UseInterfaces}} +{{- $imports := $module.Imports}} +{{- with .Service -}} +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +{{if or (hasdoc .Decl.Doc) (hasdoc .Doc)}} +/** +{{- if hasdoc .Decl.Doc}} +{{- jsdoc .Decl.Doc.Text ""}}{{if hasdoc .Doc}} + *{{end}} +{{- end}} +{{- if hasdoc .Doc}} +{{- jsdoc .Doc.Text ""}} +{{- end}} + * @module + */ +{{end}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise{{if not $useInterfaces}}, Create as $Create{{end}} } from "{{js $runtime}}"; +{{range $imports.External}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as {{jsimport .}} from "{{js .RelPath}}/{{js $models}}"; +{{- end}}{{if $imports.External}} +{{end}} +{{- if $imports.ImportModels}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./{{js $models}}"; +{{end}} +{{- range .Methods}} +/** +{{- if hasdoc .Decl.Doc}} +{{- jsdoc .Decl.Doc.Text ""}}{{if hasdoc .Doc}} + *{{end}} +{{- end}} +{{- if hasdoc .Doc}} +{{- jsdoc .Doc.Text ""}} +{{- end}} +{{- range $i, $param := .Params}} + * @param { {{- $module.JSType .Type}}{{if .Variadic}}[]{{end -}} } {{jsparam $i .}} +{{- end}} + * @returns {$CancellablePromise< + {{- if eq 0 (len .Results) -}} + void + {{- else if eq 1 (len .Results)}} + {{- $module.JSType (index .Results 0)}} + {{- else -}} + [{{range $i, $result := .Results}} + {{- if gt $i 0}}, {{end}} + {{- $module.JSType $result}} + {{- end}}] + {{- end}}>} + */ +{{if not .Internal}}export {{end}}function {{.Name}}({{range $i, $param := .Params -}} + {{- if gt $i 0}}, {{end}} + {{- if .Variadic}}...{{end}} + {{- jsparam $i .}} +{{- end}}) { + {{- if $useNames}} + return $Call.ByName("{{js .FQN}}" + {{- else}} + return $Call.ByID({{.ID}} + {{- end}}{{range $i, $param := .Params}}, {{jsparam $i .}}{{end}}) + {{- if or $useInterfaces (not .Results) ($module.SkipCreate .Results) -}} + ; + {{- else -}} + .then(/** @type {($result: any) => any} */(($result) => { + {{- if eq 1 (len .Results)}} + return {{$module.JSCreate (index .Results 0)}}($result); + {{- else}} + {{- range $i, $type := .Results}} + {{- $create := ($module.JSCreate $type)}} + {{- if ne $create "$Create.Any"}} + $result[{{$i}}] = {{$create}}($result[{{$i}}]); + {{- end}}{{end}} + return $result; + {{- end}} + })); + {{- end}} +} +{{end}} +{{- $postponed := $module.PostponedCreates}} +{{- if $postponed}} +// Private type creation functions +{{- range $i, $create := $postponed}} +{{if and (ge (len $create) 54) (eq (slice $create 39 54) "function $$init")}}var {{else}}const {{end -}} +$$createType{{$i}} = {{$create}}; +{{- end}} +{{end}} +{{- range .Injections}} +{{.}} +{{- end}}{{if .Injections}} +{{end}}{{end -}} diff --git a/v3/internal/generator/render/templates/service.ts.tmpl b/v3/internal/generator/render/templates/service.ts.tmpl new file mode 100644 index 000000000..4db90fe60 --- /dev/null +++ b/v3/internal/generator/render/templates/service.ts.tmpl @@ -0,0 +1,97 @@ +{{$module := .}} +{{- $runtime := $module.Runtime}} +{{- $models := (fixext $module.ModelsFile)}} +{{- $useNames := $module.UseNames}} +{{- $useInterfaces := $module.UseInterfaces}} +{{- $imports := $module.Imports}} +{{- with .Service -}} +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +{{if or (hasdoc .Decl.Doc) (hasdoc .Doc)}} +/** +{{- if hasdoc .Decl.Doc}} +{{- jsdoc .Decl.Doc.Text ""}}{{if hasdoc .Doc}} + *{{end}} +{{- end}} +{{- if hasdoc .Doc}} +{{- jsdoc .Doc.Text ""}} +{{- end}} + * @module + */ +{{end}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise{{if not $useInterfaces}}, Create as $Create{{end}} } from "{{js $runtime}}"; +{{range $imports.External}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as {{jsimport .}} from "{{js .RelPath}}/{{js $models}}"; +{{- end}}{{if $imports.External}} +{{end}} +{{- if $imports.ImportModels}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./{{js $models}}"; +{{end}} +{{- range .Methods}} +{{- if or (hasdoc .Decl.Doc) (hasdoc .Doc)}} +/** +{{- if hasdoc .Decl.Doc}} +{{- jsdoc .Decl.Doc.Text ""}}{{if hasdoc .Doc}} + *{{end}} +{{- end}} +{{- if .Doc}} +{{- jsdoc .Doc.Text ""}} +{{- end}} + */ +{{- end}} +{{if not .Internal}}export {{end}}function {{.Name}}({{range $i, $param := .Params -}} + {{- if gt $i 0}}, {{end}} + {{- if .Variadic}}...{{end}} + {{- jsparam $i .}}: {{$module.JSType .Type}}{{if .Variadic}}[]{{end}} +{{- end}}): $CancellablePromise< + {{- if eq 0 (len .Results) -}} + void + {{- else if eq 1 (len .Results)}} + {{- $module.JSType (index .Results 0)}} + {{- else -}} + [{{range $i, $result := .Results}} + {{- if gt $i 0}}, {{end}} + {{- $module.JSType $result}} + {{- end}}] + {{- end}}> { + {{- if $useNames}} + return $Call.ByName("{{js .FQN}}" + {{- else}} + return $Call.ByID({{.ID}} + {{- end}}{{range $i, $param := .Params}}, {{jsparam $i .}}{{end}}) + {{- if or $useInterfaces (not .Results) ($module.SkipCreate .Results) -}} + ; + {{- else -}} + .then(($result: any) => { + {{- if eq 1 (len .Results)}} + return {{$module.JSCreate (index .Results 0)}}($result); + {{- else}} + {{- range $i, $type := .Results}} + {{- $create := ($module.JSCreate $type)}} + {{- if ne $create "$Create.Any"}} + $result[{{$i}}] = {{$create}}($result[{{$i}}]); + {{- end}}{{end}} + return $result; + {{- end}} + }); + {{- end}} +} +{{end}} +{{- $postponed := $module.PostponedCreates}} +{{- if $postponed}} +// Private type creation functions +{{- range $i, $create := $postponed}} +{{if and (ge (len $create) 16) (eq (slice $create 1 16) "function $$init")}}var {{else}}const {{end -}} +$$createType{{$i}} = {{$create}}; +{{- end}} +{{end}} +{{- range .Injections}} +{{.}} +{{- end}}{{if .Injections}} +{{end}}{{end -}} diff --git a/v3/internal/generator/render/type.go b/v3/internal/generator/render/type.go new file mode 100644 index 000000000..f694a11c3 --- /dev/null +++ b/v3/internal/generator/render/type.go @@ -0,0 +1,270 @@ +package render + +import ( + "fmt" + "go/types" + "strings" + "text/template" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// aliasOrNamed is a common interface for *types.Alias and *types.Named. +type aliasOrNamed interface { + types.Type + Obj() *types.TypeName +} + +// typeByteSlice caches the type-checker type for a slice of bytes. +var typeByteSlice = types.NewSlice(types.Universe.Lookup("byte").Type()) + +// JSType renders a Go type to its TypeScript representation, +// using the receiver's import map to resolve dependencies. +// +// JSType's output may be incorrect if m.Imports.AddType +// has not been called for the given type. +func (m *module) JSType(typ types.Type) string { + result, _ := m.renderType(typ, false) + return result +} + +// JSFieldType renders a struct field type to its TypeScript representation, +// using the receiver's import map to resolve dependencies. +// +// JSFieldType's output may be incorrect if m.Imports.AddType +// has not been called for the given type. +func (m *module) JSFieldType(field *collect.StructField) string { + result, _ := m.renderType(field.Type, field.Quoted) + return result +} + +// renderType provides the actual implementation of [module.Type]. +// It returns the rendered type and a boolean indicating whether +// the resulting expression describes a nullable type. +func (m *module) renderType(typ types.Type, quoted bool) (result string, nullable bool) { + switch t := typ.(type) { + case *types.Alias, *types.Named: + return m.renderNamedType(typ.(aliasOrNamed), quoted) + + case *types.Array, *types.Slice: + null := "" + if _, isSlice := typ.(*types.Slice); isSlice && m.UseInterfaces { + // In interface mode, record the fact that encoding/json marshals nil slices as null. + null = " | null" + } + + if types.Identical(typ, typeByteSlice) { + // encoding/json marshals byte slices as base64 strings + return "string" + null, null != "" + } + + elem, ptr := m.renderType(typ.(interface{ Elem() types.Type }).Elem(), false) + if ptr { + return fmt.Sprintf("(%s)[]%s", elem, null), null != "" + } else { + return fmt.Sprintf("%s[]%s", elem, null), null != "" + } + + case *types.Basic: + return m.renderBasicType(t, quoted), false + + case *types.Map: + return m.renderMapType(t) + + case *types.Pointer: + elem, nullable := m.renderType(t.Elem(), false) + if nullable { + return elem, nullable + } else { + return fmt.Sprintf("%s | null", elem), true + } + + case *types.Struct: + return m.renderStructType(t), false + + case *types.TypeParam: + pre, post := "", "" + if quoted { + pre, post = "(", " | string)" + } + return fmt.Sprintf("%s%s%s", pre, typeparam(t.Index(), t.Obj().Name()), post), false + } + + // Fall back to untyped mode. + return "any", false +} + +// renderBasicType outputs the TypeScript representation +// of the given basic type. +func (*module) renderBasicType(typ *types.Basic, quoted bool) string { + switch { + case typ.Info()&types.IsBoolean != 0: + if quoted { + return "`${boolean}`" + } else { + return "boolean" + } + + case typ.Info()&types.IsNumeric != 0 && typ.Info()&types.IsComplex == 0: + if quoted { + return "`${number}`" + } else { + return "number" + } + + case typ.Info()&types.IsString != 0: + if quoted { + return "`\"${string}\"`" + } else { + return "string" + } + } + + // Fall back to untyped mode. + if quoted { + return "string" + } else { + return "any" + } +} + +// renderMapType outputs the TypeScript representation of the given map type. +func (m *module) renderMapType(typ *types.Map) (result string, nullable bool) { + null := "" + if m.UseInterfaces { + // In interface mode, record the fact that encoding/json marshals nil slices as null. + null = " | null" + } + + key := "string" + elem, _ := m.renderType(typ.Elem(), false) + + // Test whether we can upgrade key rendering. + switch k := typ.Key().(type) { + case *types.Basic: + if k.Info()&types.IsString == 0 && collect.IsMapKey(k) { + // Render non-string basic type in quoted mode. + key = m.renderBasicType(k, true) + } + + case *types.Alias, *types.Named, *types.Pointer: + if collect.IsMapKey(k) { + if collect.IsStringAlias(k) { + // Alias or named type is a string and therefore + // safe to use as a JS object key. + if ptr, ok := k.(*types.Pointer); ok { + // Unwrap pointers to string aliases. + key, _ = m.renderType(ptr.Elem(), false) + } else { + key, _ = m.renderType(k, false) + } + } else if basic, ok := k.Underlying().(*types.Basic); ok && basic.Info()&types.IsString == 0 { + // Render non-string basic type in quoted mode. + key = m.renderBasicType(basic, true) + } + } + } + + return fmt.Sprintf("{ [_: %s]: %s }%s", key, elem, null), m.UseInterfaces +} + +// renderNamedType outputs the TS representation +// of the given named or alias type. +func (m *module) renderNamedType(typ aliasOrNamed, quoted bool) (result string, nullable bool) { + if typ.Obj().Pkg() == nil { + // Builtin alias or named type: render underlying type. + return m.renderType(typ.Underlying(), quoted) + } + + // Special case: application.Void renders as TS void + if m.collector.IsVoidAlias(typ.Obj()) { + return "void", false + } + + if quoted { + switch a := types.Unalias(typ).(type) { + case *types.Basic: + // Quoted mode for (alias of?) basic type: delegate. + return m.renderBasicType(a, quoted), false + case *types.TypeParam: + // Quoted mode for (alias of?) typeparam: delegate. + return m.renderType(a, quoted) + case *types.Named: + // Quoted mode for (alias of?) named type. + // WARN: Do not test with IsAny/IsStringAlias here!! We only want to catch marshalers. + if collect.MaybeJSONMarshaler(typ) == collect.NonMarshaler && collect.MaybeTextMarshaler(typ) == collect.NonMarshaler { + // No custom marshaling for this type. + if u, ok := a.Underlying().(*types.Basic); ok { + // Quoted mode for basic named type that is not a marshaler: delegate. + return m.renderBasicType(u, quoted), false + } + } + } + } + + var builder strings.Builder + + if typ.Obj().Pkg().Path() == m.Imports.Self { + if m.Imports.ImportModels { + builder.WriteString("$models.") + } + } else { + builder.WriteString(jsimport(m.Imports.External[typ.Obj().Pkg().Path()])) + builder.WriteRune('.') + } + builder.WriteString(jsid(typ.Obj().Name())) + + instance, _ := typ.(interface{ TypeArgs() *types.TypeList }) + if instance != nil { + // Render type arguments. + if targs := instance.TypeArgs(); targs != nil && targs.Len() > 0 { + builder.WriteRune('<') + for i := range targs.Len() { + if i > 0 { + builder.WriteString(", ") + } + arg, _ := m.renderType(targs.At(i), false) + builder.WriteString(arg) + } + builder.WriteRune('>') + } + } + + return builder.String(), false +} + +// renderStructType outputs the TS representation +// of the given anonymous struct type. +func (m *module) renderStructType(typ *types.Struct) string { + if collect.MaybeJSONMarshaler(typ) != collect.NonMarshaler { + return "any" + } else if collect.MaybeTextMarshaler(typ) != collect.NonMarshaler { + return "string" + } + + info := m.collector.Struct(typ) + info.Collect() + + var builder strings.Builder + + builder.WriteRune('{') + for i, field := range info.Fields { + if i > 0 { + builder.WriteString(", ") + } + + builder.WriteRune('"') + template.JSEscape(&builder, []byte(field.JsonName)) + builder.WriteRune('"') + + if field.Optional { + builder.WriteRune('?') + } + + builder.WriteString(": ") + builder.WriteString(m.JSFieldType(field)) + } + builder.WriteRune('}') + + return builder.String() +} diff --git a/v3/internal/generator/service.go b/v3/internal/generator/service.go new file mode 100644 index 000000000..fcbfa9447 --- /dev/null +++ b/v3/internal/generator/service.go @@ -0,0 +1,102 @@ +package generator + +import ( + "go/types" + "path/filepath" +) + +// generateService collects information +// and generates JS/TS binding code +// for the given service type object. +func (generator *Generator) generateService(obj *types.TypeName) { + generator.logger.Debugf( + "discovered service type %s from package %s", + obj.Name(), + obj.Pkg().Path(), + ) + + success := false + defer func() { + if !success { + generator.logger.Errorf( + "package %s: type %s: service code generation failed", + obj.Pkg().Path(), + obj.Name(), + ) + } + }() + + // Collect service information. + info := generator.collector.Service(obj).Collect() + if info == nil { + return + } + + if info.IsEmpty() { + if !info.HasInternalMethods { + generator.logger.Infof( + "package %s: type %s: service has no valid exported methods, skipping", + obj.Pkg().Path(), + obj.Name(), + ) + } + success = true + return + } + + // Check for standard filename collisions. + filename := generator.renderer.ServiceFile(info.Name) + switch filename { + case generator.renderer.ModelsFile(): + generator.logger.Errorf( + "package %s: type %s: service filename collides with models filename; please rename the type or choose a different filename for models", + obj.Pkg().Path(), + obj.Name(), + ) + return + + case generator.renderer.IndexFile(): + if !generator.options.NoIndex { + generator.logger.Errorf( + "package %s: type %s: service filename collides with JS/TS index filename; please rename the type or choose a different filename for JS/TS indexes", + obj.Pkg().Path(), + obj.Name(), + ) + return + } + } + + // Check for upper/lower-case filename collisions. + path := filepath.Join(info.Imports.Self, filename) + if other, present := generator.serviceFiles.LoadOrStore(path, obj); present { + generator.logger.Errorf( + "package %s: type %s: service filename collides with filename for service %s; please avoid multiple services whose names differ only in case", + obj.Pkg().Path(), + obj.Name(), + other.(*types.TypeName).Name(), + ) + return + } + + // Create service file. + file, err := generator.creator.Create(path) + if err != nil { + generator.logger.Errorf("%v", err) + return + } + defer func() { + if err := file.Close(); err != nil { + generator.logger.Errorf("%v", err) + success = false + } + }() + + // Render service code. + err = generator.renderer.Service(file, info) + if err != nil { + generator.logger.Errorf("%v", err) + return + } + + success = true +} diff --git a/v3/internal/generator/testcases/aliases/bound_types.json b/v3/internal/generator/testcases/aliases/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/aliases/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/aliases/main.go b/v3/internal/generator/testcases/aliases/main.go new file mode 100644 index 000000000..f50d1634a --- /dev/null +++ b/v3/internal/generator/testcases/aliases/main.go @@ -0,0 +1,137 @@ +package main + +import ( + _ "embed" + "encoding" + "log" + + nobindingshere "github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService int + +// A nice type Alias. +type Alias = int + +// A class alias. +type AliasedPerson = Person + +// An empty struct alias. +type EmptyAliasStruct = struct{} + +// A struct alias. +// This should be rendered as a typedef or interface in every mode. +type AliasStruct = struct { + // A field with a comment. + Foo []int + Bar, Baz string `json:",omitempty"` // Definitely not Foo. + + Other OtherAliasStruct // A nested alias struct. +} + +// Another struct alias. +type OtherAliasStruct = struct { + NoMoreIdeas []rune +} + +// An empty struct. +type EmptyStruct struct{} + +// A non-generic struct containing an alias. +type Person struct { + Name string // The Person's name. + AliasedField Alias // A random alias field. +} + +// A generic struct containing an alias. +type GenericPerson[T any] struct { + Name T + AliasedField Alias +} + +// Another class alias, but ordered after its aliased class. +type StrangelyAliasedPerson = Person + +// A generic alias that forwards to a type parameter. +type GenericAlias[T any] = T + +// A generic alias that wraps a pointer type. +type GenericPtrAlias[T any] = *GenericAlias[T] + +// A generic alias that wraps a map. +type GenericMapAlias[T interface { + comparable + encoding.TextMarshaler +}, U any] = map[T]U + +// A generic alias that wraps a generic struct. +type GenericPersonAlias[T any] = GenericPerson[[]GenericPtrAlias[T]] + +// An alias that wraps a class through a non-typeparam alias. +type IndirectPersonAlias = GenericPersonAlias[bool] + +// An alias that wraps a class through a typeparam alias. +type TPIndirectPersonAlias = GenericAlias[GenericPerson[bool]] + +// A class whose fields have various aliased types. +type AliasGroup struct { + GAi GenericAlias[int] + GAP GenericAlias[GenericPerson[bool]] + GPAs GenericPtrAlias[[]string] + GPAP GenericPtrAlias[GenericPerson[[]int]] + GMA GenericMapAlias[struct{ encoding.TextMarshaler }, float32] + GPA GenericPersonAlias[bool] + IPA IndirectPersonAlias + TPIPA TPIndirectPersonAlias +} + +// Get someone. +func (GreetService) Get(aliasValue Alias) Person { + return Person{"hello", aliasValue} +} + +// Get someone quite different. +func (GreetService) GetButDifferent() GenericPerson[bool] { + return GenericPerson[bool]{true, 13} +} + +// Apparently, aliases are all the rage right now. +func (GreetService) GetButAliased(p AliasedPerson) StrangelyAliasedPerson { + return p +} + +func (GreetService) GetButForeignPrivateAlias() (_ nobindingshere.PrivatePerson) { + return +} + +func (GreetService) GetButGenericAliases() (_ AliasGroup) { + return +} + +// Greet a lot of unusual things. +func (GreetService) Greet(EmptyAliasStruct, EmptyStruct) AliasStruct { + return AliasStruct{} +} + +func NewGreetService() application.Service { + return application.NewService(new(GreetService)) +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + NewGreetService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/complex_expressions/bound_types.json b/v3/internal/generator/testcases/complex_expressions/bound_types.json new file mode 100644 index 000000000..b00766ba0 --- /dev/null +++ b/v3/internal/generator/testcases/complex_expressions/bound_types.json @@ -0,0 +1,14 @@ +[ + ".Service1", + ".Service2", + ".Service3", + ".Service4", + ".Service5", + ".Service6", + "/config.Service7", + "/config.Service8", + "/config.Service9", + "/config.Service10", + "/config.Service11", + "/config.Service12" +] diff --git a/v3/internal/generator/testcases/complex_expressions/config/config.go b/v3/internal/generator/testcases/complex_expressions/config/config.go new file mode 100644 index 000000000..f5f40dd86 --- /dev/null +++ b/v3/internal/generator/testcases/complex_expressions/config/config.go @@ -0,0 +1,82 @@ +package config + +import "github.com/wailsapp/wails/v3/pkg/application" + +type Service7 struct{} +type Service8 struct{} +type Service9 struct{} +type Service10 struct{} +type Service11 struct{} +type Service12 struct{} + +func (*Service7) TestMethod() {} + +func (*Service9) TestMethod2() {} + +func NewService7() application.Service { + return application.NewService(new(Service7)) +} + +func NewService8() (result application.Service) { + result = application.NewService(&Service8{}) + return +} + +type ServiceProvider struct { + AService application.Service + *ProviderWithMethod + HeresAnotherOne application.Service +} + +type ProviderWithMethod struct { + OtherService any +} + +func (pm *ProviderWithMethod) Init() { + pm.OtherService = application.NewService(&Service10{}) +} + +var Services []application.Service + +func init() { + var ourServices = []application.Service{ + NewService7(), + NewService8(), + } + + Services = make([]application.Service, len(ourServices)) + + for i, el := range ourServices { + Services[len(ourServices)-i] = el + } +} + +func MoreServices() ServiceProvider { + var provider ServiceProvider + + provider.AService = application.NewService(&Service9{}) + provider.ProviderWithMethod = new(ProviderWithMethod) + + return provider +} + +type ProviderInitialiser interface { + InitProvider(provider any) +} + +type internalProviderInitialiser struct{} + +func NewProviderInitialiser() ProviderInitialiser { + return internalProviderInitialiser{} +} + +func (internalProviderInitialiser) InitProvider(provider any) { + switch p := provider.(type) { + case *ServiceProvider: + p.HeresAnotherOne = application.NewService(&Service11{}) + default: + if anyp, ok := p.(*any); ok { + *anyp = application.NewService(&Service12{}) + } + } +} diff --git a/v3/internal/generator/testcases/complex_expressions/main.go b/v3/internal/generator/testcases/complex_expressions/main.go new file mode 100644 index 000000000..eca22794e --- /dev/null +++ b/v3/internal/generator/testcases/complex_expressions/main.go @@ -0,0 +1,66 @@ +package main + +import ( + _ "embed" + "log" + "slices" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Service1 struct{} +type Service2 struct{} +type Service3 struct{} +type Service4 struct{} +type Service5 struct{} +type Service6 struct{} + +var GlobalServices []application.Service + +func main() { + services := []application.Service{ + application.NewService(&Service1{}), + } + + services = append(services, application.NewService(&Service2{}), application.Service{}, application.Service{}) + services[2] = application.NewService(&Service3{}) + + var options = application.Options{ + Services: config.Services, + } + + provider := config.MoreServices() + provider.Init() + + pinit := config.NewProviderInitialiser() + pinit.InitProvider(&provider) + // Method resolution should work here just like above. + config.NewProviderInitialiser().InitProvider(&services[3]) + + copy(options.Services, []application.Service{application.NewService(&Service4{}), provider.HeresAnotherOne, provider.OtherService.(application.Service)}) + (options.Services) = append(options.Services, slices.Insert(services, 1, GlobalServices[2:]...)...) + + app := application.New(options) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} + +func init() { + var global = make([]application.Service, 4) + + global[0] = application.NewService(&Service5{}) + global = slices.Replace(global, 1, 4, + application.NewService(&Service6{}), + config.MoreServices().AService, + ) + + GlobalServices = slices.Clip(global) +} diff --git a/v3/internal/generator/testcases/complex_instantiations/bound_types.json b/v3/internal/generator/testcases/complex_instantiations/bound_types.json new file mode 100644 index 000000000..663519c0c --- /dev/null +++ b/v3/internal/generator/testcases/complex_instantiations/bound_types.json @@ -0,0 +1,18 @@ +[ + ".Service1", + ".Service2", + ".Service3", + ".Service4", + ".Service5", + ".Service6", + ".Service7", + ".Service8", + ".Service9", + ".Service10", + ".Service11", + ".Service12", + ".Service13", + ".Service14", + ".Service15", + "/other.Service16" +] diff --git a/v3/internal/generator/testcases/complex_instantiations/factory.go b/v3/internal/generator/testcases/complex_instantiations/factory.go new file mode 100644 index 000000000..854af6967 --- /dev/null +++ b/v3/internal/generator/testcases/complex_instantiations/factory.go @@ -0,0 +1,21 @@ +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +type Factory[T any, U any] struct { + simpleFactory[T] +} + +func NewFactory[T any, U any]() *Factory[T, U] { + return &Factory[T, U]{} +} + +func (*Factory[T, U]) GetU() application.Service { + return application.NewService(new(U)) +} + +type simpleFactory[T any] struct{} + +func (simpleFactory[U]) Get() application.Service { + return application.NewService(new(U)) +} diff --git a/v3/internal/generator/testcases/complex_instantiations/funcs.go b/v3/internal/generator/testcases/complex_instantiations/funcs.go new file mode 100644 index 000000000..7aea0e7e0 --- /dev/null +++ b/v3/internal/generator/testcases/complex_instantiations/funcs.go @@ -0,0 +1,14 @@ +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +func ServiceInitialiser[T any]() func(*T) application.Service { + return application.NewService[T] +} + +func CustomNewServices[T any, U any]() []application.Service { + return []application.Service{ + application.NewService(new(T)), + application.NewService(new(U)), + } +} diff --git a/v3/internal/generator/testcases/complex_instantiations/main.go b/v3/internal/generator/testcases/complex_instantiations/main.go new file mode 100644 index 000000000..3b6a0fb6c --- /dev/null +++ b/v3/internal/generator/testcases/complex_instantiations/main.go @@ -0,0 +1,59 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/complex_instantiations/other" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Service1 struct{} +type Service2 struct{} +type Service3 struct{} +type Service4 struct{} +type Service5 struct{} +type Service6 struct{} +type Service7 struct{} +type Service8 struct{} +type Service9 struct{} +type Service10 struct{} +type Service11 struct{} +type Service12 struct{} +type Service13 struct{} +type Service14 struct{} +type Service15 struct{} + +type SimplifiedFactory[T any] = Factory[T, Service15] + +func main() { + factory := NewFactory[Service1, Service2]() + otherFactory := other.NewFactory[Service3, Service4]() + + app := application.New(application.Options{ + Services: append(append( + []application.Service{ + factory.Get(), + factory.GetU(), + otherFactory.Get(), + otherFactory.GetU(), + application.NewService(&Service5{}), + ServiceInitialiser[Service6]()(&Service6{}), + other.CustomNewService(Service7{}), + other.ServiceInitialiser[Service8]()(&Service8{}), + application.NewServiceWithOptions(&Service13{}, application.ServiceOptions{Name: "custom name"}), + SimplifiedFactory[Service14]{}.Get(), + other.LocalService, + }, + CustomNewServices[Service9, Service10]()...), + other.CustomNewServices[Service11, Service12]()...), + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/generator/testcases/complex_instantiations/other/factory.go b/v3/internal/generator/testcases/complex_instantiations/other/factory.go new file mode 100644 index 000000000..d8e3aadb7 --- /dev/null +++ b/v3/internal/generator/testcases/complex_instantiations/other/factory.go @@ -0,0 +1,21 @@ +package other + +import "github.com/wailsapp/wails/v3/pkg/application" + +type Factory[T any, U any] struct { + simpleFactory[T] +} + +func NewFactory[T any, U any]() *Factory[T, U] { + return &Factory[T, U]{} +} + +func (*Factory[T, U]) GetU() application.Service { + return application.NewService(new(U)) +} + +type simpleFactory[T any] struct{} + +func (simpleFactory[U]) Get() application.Service { + return application.NewService(new(U)) +} diff --git a/v3/internal/generator/testcases/complex_instantiations/other/funcs.go b/v3/internal/generator/testcases/complex_instantiations/other/funcs.go new file mode 100644 index 000000000..daf1ad066 --- /dev/null +++ b/v3/internal/generator/testcases/complex_instantiations/other/funcs.go @@ -0,0 +1,18 @@ +package other + +import "github.com/wailsapp/wails/v3/pkg/application" + +func CustomNewService[T any](srv T) application.Service { + return application.NewService(&srv) +} + +func ServiceInitialiser[T any]() func(*T) application.Service { + return application.NewService[T] +} + +func CustomNewServices[T any, U any]() []application.Service { + return []application.Service{ + application.NewService(new(T)), + application.NewService(new(U)), + } +} diff --git a/v3/internal/generator/testcases/complex_instantiations/other/local.go b/v3/internal/generator/testcases/complex_instantiations/other/local.go new file mode 100644 index 000000000..f08abb26a --- /dev/null +++ b/v3/internal/generator/testcases/complex_instantiations/other/local.go @@ -0,0 +1,7 @@ +package other + +import "github.com/wailsapp/wails/v3/pkg/application" + +type Service16 int + +var LocalService = application.NewService(new(Service16)) diff --git a/v3/internal/generator/testcases/complex_json/bound_types.json b/v3/internal/generator/testcases/complex_json/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/complex_json/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/complex_json/events.json b/v3/internal/generator/testcases/complex_json/events.json new file mode 100644 index 000000000..f32a5804e --- /dev/null +++ b/v3/internal/generator/testcases/complex_json/events.json @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/v3/internal/generator/testcases/complex_json/main.go b/v3/internal/generator/testcases/complex_json/main.go new file mode 100644 index 000000000..2ed78d55b --- /dev/null +++ b/v3/internal/generator/testcases/complex_json/main.go @@ -0,0 +1,129 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// Title is a title +type Title string + +const ( + // Mister is a title + Mister Title = "Mr" + Miss Title = "Miss" + Ms Title = "Ms" + Mrs Title = "Mrs" + Dr Title = "Dr" +) + +// GreetService is great +type GreetService struct{} + +type Embedded1 struct { + // Friends should be shadowed in Person by a field of lesser depth + Friends int + + // Vanish should be omitted from Person because there is another field with same depth and no tag + Vanish float32 + + // StillThere should be shadowed in Person by other field with same depth and a json tag + StillThere string + + // embedded4 should effectively appear as an embedded field + embedded4 + + // unexported should be invisible + unexported bool +} + +type Embedded2 struct { + // Vanish should be omitted from Person because there is another field with same depth and no tag + Vanish bool + + // StillThereButRenamed should shadow in Person the other field with same depth and no json tag + StillThereButRenamed *Embedded3 `json:"StillThere"` +} + +type Embedded3 string + +// Person represents a person +type Person struct { + // Titles is optional in JSON + Titles []Title `json:",omitzero"` + + // Names has a + // multiline comment + Names []string + + // Partner has a custom and complex JSON key + Partner *Person `json:"the person's partner ❤️"` + Friends []*Person + + Embedded1 + Embedded2 + + // UselessMap is invisible to JSON + UselessMap map[int]bool `json:"-"` + + // StrangeNumber maps to "-" + StrangeNumber float32 `json:"-,"` + + // Embedded3 should appear with key "Embedded3" + Embedded3 + + // StrangerNumber is serialized as a string + StrangerNumber int `json:",string"` + // StrangestString is optional and serialized as a JSON string + StrangestString string `json:",omitempty,string"` + // StringStrangest is serialized as a JSON string and optional + StringStrangest string `json:",string,omitempty"` + + // unexportedToo should be invisible even with a json tag + unexportedToo bool `json:"Unexported"` + + // embedded4 should be optional and appear with key "emb4" + embedded4 `json:"emb4,omitempty"` +} + +type embedded4 struct { + // NamingThingsIsHard is a law of programming + NamingThingsIsHard bool `json:",string"` + + // Friends should not be shadowed in Person as embedded4 is not embedded + // from encoding/json's point of view; + // however, it should be shadowed in Embedded1 + Friends bool + + // Embedded string should be invisible because it's unexported + string +} + +// Greet does XYZ +func (*GreetService) Greet(person Person, emb Embedded1) string { + return "" +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} + +func init() { + application.RegisterEvent[map[string]int]("collision") + application.RegisterEvent[*struct{ Field []bool }]("overlap") +} diff --git a/v3/internal/generator/testcases/complex_method/bound_types.json b/v3/internal/generator/testcases/complex_method/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/complex_method/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/complex_method/main.go b/v3/internal/generator/testcases/complex_method/main.go new file mode 100644 index 000000000..23ea29dee --- /dev/null +++ b/v3/internal/generator/testcases/complex_method/main.go @@ -0,0 +1,43 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct{} + +// Person represents a person +type Person struct { + Name string +} + +// Greet does XYZ +// It has a multiline doc comment +// The comment has even some */ traps!! +func (*GreetService) Greet(str string, people []Person, _ struct { + AnotherCount int + AnotherOne *Person +}, assoc map[int]*bool, _ []*float32, other ...string) (person Person, _ any, err1 error, _ []int, err error) { + return +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/cyclic_imports/bound_types.json b/v3/internal/generator/testcases/cyclic_imports/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/cyclic_imports/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/cyclic_imports/main.go b/v3/internal/generator/testcases/cyclic_imports/main.go new file mode 100644 index 000000000..58feb98a1 --- /dev/null +++ b/v3/internal/generator/testcases/cyclic_imports/main.go @@ -0,0 +1,55 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService int + +type StructA struct { + B *structB +} + +type structB struct { + A *StructA +} + +type StructC struct { + D structD +} + +type structD struct { + E StructE +} + +type StructE struct{} + +// Make a cycle. +func (GreetService) MakeCycles() (_ StructA, _ StructC) { + return +} + +func NewGreetService() application.Service { + return application.NewService(new(GreetService)) +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + NewGreetService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/cyclic_types/bound_types.json b/v3/internal/generator/testcases/cyclic_types/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/cyclic_types/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/cyclic_types/main.go b/v3/internal/generator/testcases/cyclic_types/main.go new file mode 100644 index 000000000..a5a9f5b46 --- /dev/null +++ b/v3/internal/generator/testcases/cyclic_types/main.go @@ -0,0 +1,46 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService int + +type Cyclic []map[string]Alias + +type Alias = *Cyclic + +type GenericCyclic[T any] []struct { + X *GenericCyclic[T] + Y []T +} + +// Make a cycle. +func (GreetService) MakeCycles() (_ Cyclic, _ GenericCyclic[GenericCyclic[int]]) { + return +} + +func NewGreetService() application.Service { + return application.NewService(new(GreetService)) +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + NewGreetService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/directives/bound_types.json b/v3/internal/generator/testcases/directives/bound_types.json new file mode 100644 index 000000000..62c02c0ca --- /dev/null +++ b/v3/internal/generator/testcases/directives/bound_types.json @@ -0,0 +1,5 @@ +[ + ".InternalService", + ".Service", + ".unexportedService" +] diff --git a/v3/internal/generator/testcases/directives/includes.go b/v3/internal/generator/testcases/directives/includes.go new file mode 100644 index 000000000..669d5305c --- /dev/null +++ b/v3/internal/generator/testcases/directives/includes.go @@ -0,0 +1,11 @@ +//wails:include js/test.js +//wails:include **:js/test_all.js +//wails:include *c:js/test_c.js +//wails:include *i:js/test_i.js +//wails:include j*:js/test_j.js +//wails:include jc:js/test_jc.js +//wails:include ji:js/test_ji.js +//wails:include t*:js/test_t.ts +//wails:include tc:js/test_tc.ts +//wails:include ti:js/test_ti.ts +package main diff --git a/v3/internal/generator/testcases/directives/internal.go b/v3/internal/generator/testcases/directives/internal.go new file mode 100644 index 000000000..86f06e02d --- /dev/null +++ b/v3/internal/generator/testcases/directives/internal.go @@ -0,0 +1,15 @@ +package main + +// An exported but internal model. +// +//wails:internal +type InternalModel struct { + Field string +} + +// An exported but internal service. +// +//wails:internal +type InternalService struct{} + +func (InternalService) Method(InternalModel) {} diff --git a/v3/internal/generator/testcases/directives/js/test.js b/v3/internal/generator/testcases/directives/js/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testcases/directives/js/test_all.js b/v3/internal/generator/testcases/directives/js/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testcases/directives/js/test_c.js b/v3/internal/generator/testcases/directives/js/test_c.js new file mode 100644 index 000000000..724e79e12 --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_c.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Classes"); diff --git a/v3/internal/generator/testcases/directives/js/test_i.js b/v3/internal/generator/testcases/directives/js/test_i.js new file mode 100644 index 000000000..442f20472 --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_i.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Interfaces"); diff --git a/v3/internal/generator/testcases/directives/js/test_j.js b/v3/internal/generator/testcases/directives/js/test_j.js new file mode 100644 index 000000000..b2f9c5edb --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testcases/directives/js/test_jc.js b/v3/internal/generator/testcases/directives/js/test_jc.js new file mode 100644 index 000000000..ddf4920e5 --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_jc.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS Classes"); diff --git a/v3/internal/generator/testcases/directives/js/test_ji.js b/v3/internal/generator/testcases/directives/js/test_ji.js new file mode 100644 index 000000000..36e28f09b --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_ji.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS Interfaces"); diff --git a/v3/internal/generator/testcases/directives/js/test_t.ts b/v3/internal/generator/testcases/directives/js/test_t.ts new file mode 100644 index 000000000..253d3f2f6 --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testcases/directives/js/test_tc.ts b/v3/internal/generator/testcases/directives/js/test_tc.ts new file mode 100644 index 000000000..66b739d3a --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_tc.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS Classes"); diff --git a/v3/internal/generator/testcases/directives/js/test_ti.ts b/v3/internal/generator/testcases/directives/js/test_ti.ts new file mode 100644 index 000000000..7400e97aa --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_ti.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS Interfaces"); diff --git a/v3/internal/generator/testcases/directives/main.go b/v3/internal/generator/testcases/directives/main.go new file mode 100644 index 000000000..ee204027a --- /dev/null +++ b/v3/internal/generator/testcases/directives/main.go @@ -0,0 +1,60 @@ +//wails:inject console.log("Hello everywhere!"); +//wails:inject **:console.log("Hello everywhere again!"); +//wails:inject *c:console.log("Hello Classes!"); +//wails:inject *i:console.log("Hello Interfaces!"); +//wails:inject j*:console.log("Hello JS!"); +//wails:inject jc:console.log("Hello JS Classes!"); +//wails:inject ji:console.log("Hello JS Interfaces!"); +//wails:inject t*:console.log("Hello TS!"); +//wails:inject tc:console.log("Hello TS Classes!"); +//wails:inject ti:console.log("Hello TS Interfaces!"); +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type IgnoredType struct { + Field int +} + +//wails:inject j*:/** +//wails:inject j*: * @param {string} arg +//wails:inject j*: * @returns {Promise} +//wails:inject j*: */ +//wails:inject j*:export async function CustomMethod(arg) { +//wails:inject t*:export async function CustomMethod(arg: string): Promise { +//wails:inject await InternalMethod("Hello " + arg + "!"); +//wails:inject } +type Service struct{} + +func (*Service) VisibleMethod(otherpackage.Dummy) {} + +//wails:ignore +func (*Service) IgnoredMethod(IgnoredType) {} + +//wails:internal +func (*Service) InternalMethod(string) {} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&Service{}), + application.NewService(&unexportedService{}), + application.NewService(&InternalService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/directives/otherpackage/dummy.go b/v3/internal/generator/testcases/directives/otherpackage/dummy.go new file mode 100644 index 000000000..e8f688f05 --- /dev/null +++ b/v3/internal/generator/testcases/directives/otherpackage/dummy.go @@ -0,0 +1,5 @@ +//wails:include jc:js/*_j*.js +//wails:include tc:js/*_t*.ts +package otherpackage + +type Dummy struct{} diff --git a/v3/internal/generator/testcases/directives/otherpackage/js/test_j.js b/v3/internal/generator/testcases/directives/otherpackage/js/test_j.js new file mode 100644 index 000000000..2166d33b6 --- /dev/null +++ b/v3/internal/generator/testcases/directives/otherpackage/js/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testcases/directives/otherpackage/js/test_jc.js b/v3/internal/generator/testcases/directives/otherpackage/js/test_jc.js new file mode 100644 index 000000000..338898726 --- /dev/null +++ b/v3/internal/generator/testcases/directives/otherpackage/js/test_jc.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("JS Classes"); diff --git a/v3/internal/generator/testcases/directives/otherpackage/js/test_t.ts b/v3/internal/generator/testcases/directives/otherpackage/js/test_t.ts new file mode 100644 index 000000000..6703820f1 --- /dev/null +++ b/v3/internal/generator/testcases/directives/otherpackage/js/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testcases/directives/otherpackage/js/test_tc.ts b/v3/internal/generator/testcases/directives/otherpackage/js/test_tc.ts new file mode 100644 index 000000000..15d2994e9 --- /dev/null +++ b/v3/internal/generator/testcases/directives/otherpackage/js/test_tc.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("TS Classes"); diff --git a/v3/internal/generator/testcases/directives/unexported.go b/v3/internal/generator/testcases/directives/unexported.go new file mode 100644 index 000000000..56708c5ae --- /dev/null +++ b/v3/internal/generator/testcases/directives/unexported.go @@ -0,0 +1,11 @@ +package main + +// An unexported model. +type unexportedModel struct { + Field string +} + +// An unexported service. +type unexportedService struct{} + +func (unexportedService) Method(unexportedModel) {} diff --git a/v3/internal/generator/testcases/embedded_interface/bound_types.json b/v3/internal/generator/testcases/embedded_interface/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/embedded_interface/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/embedded_interface/main.go b/v3/internal/generator/testcases/embedded_interface/main.go new file mode 100644 index 000000000..2f65d6519 --- /dev/null +++ b/v3/internal/generator/testcases/embedded_interface/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + AnInterface +} + +type AnInterface interface { + // Comment 1. + Method1() + + Method2() // Comment 2. + + // Comment 3a. + Method3() // Comment 3b. + + interface { + // Comment 4. + Method4() + } + + InterfaceAlias +} + +type InterfaceAlias = interface { + // Comment 5. + Method5() +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/enum/bound_types.json b/v3/internal/generator/testcases/enum/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/enum/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/enum/main.go b/v3/internal/generator/testcases/enum/main.go new file mode 100644 index 000000000..22603ce37 --- /dev/null +++ b/v3/internal/generator/testcases/enum/main.go @@ -0,0 +1,74 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// Title is a title +type Title string + +const ( + // Mister is a title + Mister Title = "Mr" + Miss Title = "Miss" + Ms Title = "Ms" + Mrs Title = "Mrs" + Dr Title = "Dr" +) + +// Age is an integer with some predefined values +type Age = int + +const ( + NewBorn Age = 0 + Teenager Age = 12 + YoungAdult Age = 18 + + // Oh no, some grey hair! + MiddleAged Age = 50 + Mathusalem Age = 1000 // Unbelievable! +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string + target *Person +} + +// Person represents a person +type Person struct { + Title Title + Name string + Age Age +} + +// Greet does XYZ +func (*GreetService) Greet(name string, title Title) string { + return "Hello " + string(title) + " " + name +} + +// NewPerson creates a new person +func (*GreetService) NewPerson(name string) *Person { + return &Person{Name: name} +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/enum_from_imported_package/bound_types.json b/v3/internal/generator/testcases/enum_from_imported_package/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/enum_from_imported_package/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/enum_from_imported_package/main.go b/v3/internal/generator/testcases/enum_from_imported_package/main.go new file mode 100644 index 000000000..f0348eb5c --- /dev/null +++ b/v3/internal/generator/testcases/enum_from_imported_package/main.go @@ -0,0 +1,38 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet does XYZ +func (*GreetService) Greet(name string, title services.Title) string { + return "Hello " + title.String() + " " + name +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/enum_from_imported_package/services/other.go b/v3/internal/generator/testcases/enum_from_imported_package/services/other.go new file mode 100644 index 000000000..5d15f239c --- /dev/null +++ b/v3/internal/generator/testcases/enum_from_imported_package/services/other.go @@ -0,0 +1,16 @@ +package services + +type Title string + +func (t Title) String() string { + return string(t) +} + +const ( + // Mister is a title + Mister Title = "Mr" + Miss Title = "Miss" + Ms Title = "Ms" + Mrs Title = "Mrs" + Dr Title = "Dr" +) diff --git a/v3/internal/generator/testcases/events_only/events.go b/v3/internal/generator/testcases/events_only/events.go new file mode 100644 index 000000000..dcd4c642c --- /dev/null +++ b/v3/internal/generator/testcases/events_only/events.go @@ -0,0 +1,26 @@ +package events_only + +import ( + "fmt" + + nobindingshere "github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// SomeClass renders as a TS class. +type SomeClass struct { + Field string + Meadow nobindingshere.HowDifferent[rune] +} + +func init() { + application.RegisterEvent[string]("events_only:string") + application.RegisterEvent[map[string][]int]("events_only:map") + application.RegisterEvent[SomeClass]("events_only:class") + application.RegisterEvent[int]("collision") + application.RegisterEvent[bool](fmt.Sprintf("events_only:%s%d", "dynamic", 3)) +} + +func init() { + application.RegisterEvent[application.Void]("events_only:nodata") +} diff --git a/v3/internal/generator/testcases/events_only/events.json b/v3/internal/generator/testcases/events_only/events.json new file mode 100644 index 000000000..f32a5804e --- /dev/null +++ b/v3/internal/generator/testcases/events_only/events.json @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/v3/internal/generator/testcases/events_only/other.go b/v3/internal/generator/testcases/events_only/other.go new file mode 100644 index 000000000..1f76b4280 --- /dev/null +++ b/v3/internal/generator/testcases/events_only/other.go @@ -0,0 +1,26 @@ +package events_only + +import ( + "github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more" + "github.com/wailsapp/wails/v3/pkg/application" +) + +const eventPrefix = "events_only" + `:` + +var registerStringEvent = application.RegisterEvent[string] + +func registerIntEvent(name string) { + application.RegisterEvent[int](name) +} + +func registerSliceEvent[T any]() { + application.RegisterEvent[[]T]("parametric") +} + +func init() { + application.RegisterEvent[[]more.StringPtr](eventPrefix + "other") + application.RegisterEvent[string]("common:ApplicationStarted") + registerStringEvent("indirect_var") + registerIntEvent("indirect_fn") + registerSliceEvent[uintptr]() +} diff --git a/v3/internal/generator/testcases/function_from_imported_package/bound_types.json b/v3/internal/generator/testcases/function_from_imported_package/bound_types.json new file mode 100644 index 000000000..86a0a8812 --- /dev/null +++ b/v3/internal/generator/testcases/function_from_imported_package/bound_types.json @@ -0,0 +1,4 @@ +[ + ".GreetService", + "/services.OtherService" +] diff --git a/v3/internal/generator/testcases/function_from_imported_package/main.go b/v3/internal/generator/testcases/function_from_imported_package/main.go new file mode 100644 index 000000000..03e5b209f --- /dev/null +++ b/v3/internal/generator/testcases/function_from_imported_package/main.go @@ -0,0 +1,51 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string + target *Person +} + +// Person is a person +type Person struct { + Name string + Address *services.Address +} + +// Greet does XYZ +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// NewPerson creates a new person +func (*GreetService) NewPerson(name string) *Person { + return &Person{Name: name} +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + services.NewOtherService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/function_from_imported_package/services/other.go b/v3/internal/generator/testcases/function_from_imported_package/services/other.go new file mode 100644 index 000000000..4ac6e6efd --- /dev/null +++ b/v3/internal/generator/testcases/function_from_imported_package/services/other.go @@ -0,0 +1,28 @@ +package services + +import "github.com/wailsapp/wails/v3/pkg/application" + +// OtherService is a struct +// that does things +type OtherService struct { + t int +} + +type Address struct { + Street string + State string + Country string +} + +// Yay does this and that +func (o *OtherService) Yay() *Address { + return &Address{ + Street: "123 Pitt Street", + State: "New South Wales", + Country: "Australia", + } +} + +func NewOtherService() application.Service { + return application.NewService(&OtherService{}) +} diff --git a/v3/internal/generator/testcases/function_from_nested_imported_package/bound_types.json b/v3/internal/generator/testcases/function_from_nested_imported_package/bound_types.json new file mode 100644 index 000000000..7f9f0ea61 --- /dev/null +++ b/v3/internal/generator/testcases/function_from_nested_imported_package/bound_types.json @@ -0,0 +1,4 @@ +[ + ".GreetService", + "/services/other.OtherService" +] diff --git a/v3/internal/generator/testcases/function_from_nested_imported_package/main.go b/v3/internal/generator/testcases/function_from_nested_imported_package/main.go new file mode 100644 index 000000000..820fc4f83 --- /dev/null +++ b/v3/internal/generator/testcases/function_from_nested_imported_package/main.go @@ -0,0 +1,50 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string + target *Person +} + +type Person struct { + Name string + Address *other.Address +} + +// Greet does XYZ +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// NewPerson creates a new person +func (*GreetService) NewPerson(name string) *Person { + return &Person{Name: name} +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + other.NewOtherService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/other.go b/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/other.go new file mode 100644 index 000000000..fa12f63ce --- /dev/null +++ b/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/other.go @@ -0,0 +1,28 @@ +package other + +import "github.com/wailsapp/wails/v3/pkg/application" + +// OtherService is a struct +// that does things +type OtherService struct { + t int +} + +type Address struct { + Street string + State string + Country string +} + +// Yay does this and that +func (o *OtherService) Yay() *Address { + return &Address{ + Street: "123 Pitt Street", + State: "New South Wales", + Country: "Australia", + } +} + +func NewOtherService() application.Service { + return application.NewService(&OtherService{}) +} diff --git a/v3/internal/generator/testcases/function_multiple_files/bound_types.json b/v3/internal/generator/testcases/function_multiple_files/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/function_multiple_files/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/function_multiple_files/greet.go b/v3/internal/generator/testcases/function_multiple_files/greet.go new file mode 100644 index 000000000..b0153f22f --- /dev/null +++ b/v3/internal/generator/testcases/function_multiple_files/greet.go @@ -0,0 +1,20 @@ +package main + +import ( + _ "embed" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type GreetService struct { + SomeVariable int + lowerCase string +} + +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func NewGreetService() application.Service { + return application.NewService(&GreetService{}) +} diff --git a/v3/internal/generator/testcases/function_multiple_files/main.go b/v3/internal/generator/testcases/function_multiple_files/main.go new file mode 100644 index 000000000..240168530 --- /dev/null +++ b/v3/internal/generator/testcases/function_multiple_files/main.go @@ -0,0 +1,25 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + NewGreetService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/function_single/bound_types.json b/v3/internal/generator/testcases/function_single/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/function_single/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/function_single/main.go b/v3/internal/generator/testcases/function_single/main.go new file mode 100644 index 000000000..738505455 --- /dev/null +++ b/v3/internal/generator/testcases/function_single/main.go @@ -0,0 +1,40 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func NewGreetService() application.Service { + return application.NewService(&GreetService{}) +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + NewGreetService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/function_single_context/bound_types.json b/v3/internal/generator/testcases/function_single_context/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/function_single_context/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/function_single_context/main.go b/v3/internal/generator/testcases/function_single_context/main.go new file mode 100644 index 000000000..1a785ddba --- /dev/null +++ b/v3/internal/generator/testcases/function_single_context/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "context" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// Greet someone +func (*GreetService) GreetWithContext(ctx context.Context, name string) string { + return "Hello " + name +} + +func NewGreetService() application.Service { + return application.NewService(&GreetService{}) +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + NewGreetService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/function_single_internal/bound_types.json b/v3/internal/generator/testcases/function_single_internal/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/function_single_internal/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/function_single_internal/main.go b/v3/internal/generator/testcases/function_single_internal/main.go new file mode 100644 index 000000000..f66dd3649 --- /dev/null +++ b/v3/internal/generator/testcases/function_single_internal/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "context" + _ "embed" + "log" + "net/http" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// Debugging name +func (*GreetService) ServiceName() string { + return "GreetService" +} + +// Lifecycle +func (*GreetService) ServiceStartup(context.Context, application.ServiceOptions) error { + return nil +} + +// Lifecycle +func (*GreetService) ServiceShutdown() error { + return nil +} + +// Serve some routes +func (*GreetService) ServeHTTP(http.ResponseWriter, *http.Request) { +} + +func NewGreetService() application.Service { + return application.NewService(&GreetService{}) +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + NewGreetService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/map_keys/bound_types.json b/v3/internal/generator/testcases/map_keys/bound_types.json new file mode 100644 index 000000000..1a27d889e --- /dev/null +++ b/v3/internal/generator/testcases/map_keys/bound_types.json @@ -0,0 +1,3 @@ +[ + ".Service" +] diff --git a/v3/internal/generator/testcases/map_keys/main.go b/v3/internal/generator/testcases/map_keys/main.go new file mode 100644 index 000000000..b5d331bad --- /dev/null +++ b/v3/internal/generator/testcases/map_keys/main.go @@ -0,0 +1,307 @@ +package main + +import ( + _ "embed" + "encoding" + "encoding/json" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Service struct{} + +type NonTextMarshaler struct{} + +type ValueTextMarshaler struct{} + +func (ValueTextMarshaler) MarshalText() ([]byte, error) { return nil, nil } + +type PointerTextMarshaler struct{} + +func (*PointerTextMarshaler) MarshalText() ([]byte, error) { return nil, nil } + +type JsonTextMarshaler struct{} + +func (JsonTextMarshaler) MarshalJSON() ([]byte, error) { return nil, nil } +func (JsonTextMarshaler) MarshalText() ([]byte, error) { return nil, nil } + +type CustomInterface interface { + MarshalText() ([]byte, error) +} + +type EmbeddedInterface interface { + encoding.TextMarshaler +} + +type EmbeddedInterfaces interface { + json.Marshaler + encoding.TextMarshaler +} + +type BasicConstraint interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | + ~string +} + +type BadTildeConstraint interface { + int | ~struct{} | string +} + +type GoodTildeConstraint interface { + int | ~struct{} | string + MarshalText() ([]byte, error) +} + +type NonBasicConstraint interface { + ValueTextMarshaler | *PointerTextMarshaler +} + +type PointableConstraint interface { + ValueTextMarshaler | PointerTextMarshaler +} + +type MixedConstraint interface { + uint | ~string | ValueTextMarshaler | *PointerTextMarshaler +} + +type InterfaceConstraint interface { + comparable + encoding.TextMarshaler +} + +type PointerConstraint[T comparable] interface { + *T + encoding.TextMarshaler +} + +type EmbeddedValue struct{ ValueTextMarshaler } +type EmbeddedValuePtr struct{ *ValueTextMarshaler } +type EmbeddedPointer struct{ PointerTextMarshaler } +type EmbeddedPointerPtr struct{ *PointerTextMarshaler } + +type EmbeddedCustomInterface struct{ CustomInterface } +type EmbeddedOriginalInterface struct{ encoding.TextMarshaler } + +type WrongType bool +type WrongAlias = bool +type StringType string +type StringAlias = string +type IntType int +type IntAlias = int + +type ValueType ValueTextMarshaler +type ValuePtrType *ValueTextMarshaler +type ValueAlias = ValueTextMarshaler +type ValuePtrAlias = *ValueTextMarshaler + +type PointerType PointerTextMarshaler +type PointerPtrType *PointerTextMarshaler +type PointerAlias = PointerTextMarshaler +type PointerPtrAlias = *PointerTextMarshaler + +type InterfaceType encoding.TextMarshaler +type InterfacePtrType *encoding.TextMarshaler +type InterfaceAlias = encoding.TextMarshaler +type InterfacePtrAlias = *encoding.TextMarshaler + +type ComparableCstrAlias[R comparable] = R +type ComparableCstrPtrAlias[R comparable] = *R +type BasicCstrAlias[S BasicConstraint] = S +type BasicCstrPtrAlias[S BasicConstraint] = *S +type BadTildeCstrAlias[T BadTildeConstraint] = T +type BadTildeCstrPtrAlias[T BadTildeConstraint] = *T +type GoodTildeCstrAlias[U GoodTildeConstraint] = U +type GoodTildeCstrPtrAlias[U GoodTildeConstraint] = *U +type NonBasicCstrAlias[V NonBasicConstraint] = V +type NonBasicCstrPtrAlias[V NonBasicConstraint] = *V +type PointableCstrAlias[W PointableConstraint] = W +type PointableCstrPtrAlias[W PointableConstraint] = *W +type MixedCstrAlias[X MixedConstraint] = X +type MixedCstrPtrAlias[X MixedConstraint] = *X +type InterfaceCstrAlias[Y InterfaceConstraint] = Y +type InterfaceCstrPtrAlias[Y InterfaceConstraint] = *Y +type PointerCstrAlias[R comparable, Z PointerConstraint[R]] = Z +type PointerCstrPtrAlias[R comparable, Z PointerConstraint[R]] = *Z + +type Maps[R comparable, S BasicConstraint, T BadTildeConstraint, U GoodTildeConstraint, V NonBasicConstraint, W PointableConstraint, X MixedConstraint, Y InterfaceConstraint, Z PointerConstraint[R]] struct { + Bool map[bool]int // Reject + Int map[int]int // Accept + Uint map[uint]int // Accept + Float map[float32]int // Reject + Complex map[complex64]int // Reject + Byte map[byte]int // Accept + Rune map[rune]int // Accept + String map[string]int // Accept + + IntPtr map[*int]int // Reject + UintPtr map[*uint]int // Reject + FloatPtr map[*float32]int // Reject + ComplexPtr map[*complex64]int // Reject + StringPtr map[*string]int // Reject + + NTM map[NonTextMarshaler]int // Reject + NTMPtr map[*NonTextMarshaler]int // Reject + VTM map[ValueTextMarshaler]int // Accept + VTMPtr map[*ValueTextMarshaler]int // Accept + PTM map[PointerTextMarshaler]int // Reject + PTMPtr map[*PointerTextMarshaler]int // Accept + JTM map[JsonTextMarshaler]int // Accept, hide + JTMPtr map[*JsonTextMarshaler]int // Accept, hide + + A map[any]int // Reject + APtr map[*any]int // Reject + TM map[encoding.TextMarshaler]int // Accept, hide + TMPtr map[*encoding.TextMarshaler]int // Reject + CI map[CustomInterface]int // Accept, hide + CIPtr map[*CustomInterface]int // Reject + EI map[EmbeddedInterface]int // Accept, hide + EIPtr map[*EmbeddedInterface]int // Reject + + EV map[EmbeddedValue]int // Accept + EVPtr map[*EmbeddedValue]int // Accept + EVP map[EmbeddedValuePtr]int // Accept + EVPPtr map[*EmbeddedValuePtr]int // Accept + EP map[EmbeddedPointer]int // Reject + EPPtr map[*EmbeddedPointer]int // Accept + EPP map[EmbeddedPointerPtr]int // Accept + EPPPtr map[*EmbeddedPointerPtr]int // Accept + + ECI map[EmbeddedCustomInterface]int // Accept + ECIPtr map[*EmbeddedCustomInterface]int // Accept + EOI map[EmbeddedOriginalInterface]int // Accept + EOIPtr map[*EmbeddedOriginalInterface]int // Accept + + WT map[WrongType]int // Reject + WA map[WrongAlias]int // Reject + ST map[StringType]int // Accept + SA map[StringAlias]int // Accept + IntT map[IntType]int // Accept + IntA map[IntAlias]int // Accept + + VT map[ValueType]int // Reject + VTPtr map[*ValueType]int // Reject + VPT map[ValuePtrType]int // Reject + VPTPtr map[*ValuePtrType]int // Reject + VA map[ValueAlias]int // Accept + VAPtr map[*ValueAlias]int // Accept + VPA map[ValuePtrAlias]int // Accept, hide + VPAPtr map[*ValuePtrAlias]int // Reject + + PT map[PointerType]int // Reject + PTPtr map[*PointerType]int // Reject + PPT map[PointerPtrType]int // Reject + PPTPtr map[*PointerPtrType]int // Reject + PA map[PointerAlias]int // Reject + PAPtr map[*PointerAlias]int // Accept + PPA map[PointerPtrAlias]int // Accept, hide + PPAPtr map[*PointerPtrAlias]int // Reject + + IT map[InterfaceType]int // Accept, hide + ITPtr map[*InterfaceType]int // Reject + IPT map[InterfacePtrType]int // Reject + IPTPtr map[*InterfacePtrType]int // Reject + IA map[InterfaceAlias]int // Accept, hide + IAPtr map[*InterfaceAlias]int // Reject + IPA map[InterfacePtrAlias]int // Reject + IPAPtr map[*InterfacePtrAlias]int // Reject + + TPR map[R]int // Soft reject + TPRPtr map[*R]int // Soft reject + TPS map[S]int // Accept, hide + TPSPtr map[*S]int // Soft reject + TPT map[T]int // Soft reject + TPTPtr map[*T]int // Soft reject + TPU map[U]int // Accept, hide + TPUPtr map[*U]int // Soft reject + TPV map[V]int // Accept, hide + TPVPtr map[*V]int // Soft reject + TPW map[W]int // Soft reject + TPWPtr map[*W]int // Accept, hide + TPX map[X]int // Accept, hide + TPXPtr map[*X]int // Soft reject + TPY map[Y]int // Accept, hide + TPYPtr map[*Y]int // Soft reject + TPZ map[Z]int // Accept, hide + TPZPtr map[*Z]int // Soft reject + + GAR map[ComparableCstrAlias[R]]int // Soft reject + GARPtr map[ComparableCstrPtrAlias[R]]int // Soft reject + GAS map[BasicCstrAlias[S]]int // Accept, hide + GASPtr map[BasicCstrPtrAlias[S]]int // Soft reject + GAT map[BadTildeCstrAlias[T]]int // Soft reject + GATPtr map[BadTildeCstrPtrAlias[T]]int // Soft reject + GAU map[GoodTildeCstrAlias[U]]int // Accept, hide + GAUPtr map[GoodTildeCstrPtrAlias[U]]int // Soft reject + GAV map[NonBasicCstrAlias[V]]int // Accept, hide + GAVPtr map[NonBasicCstrPtrAlias[V]]int // Soft reject + GAW map[PointableCstrAlias[W]]int // Soft reject + GAWPtr map[PointableCstrPtrAlias[W]]int // Accept, hide + GAX map[MixedCstrAlias[X]]int // Accept, hide + GAXPtr map[MixedCstrPtrAlias[X]]int // Soft reject + GAY map[InterfaceCstrAlias[Y]]int // Accept, hide + GAYPtr map[InterfaceCstrPtrAlias[Y]]int // Soft reject + GAZ map[PointerCstrAlias[R, Z]]int // Accept, hide + GAZPtr map[PointerCstrPtrAlias[R, Z]]int // Soft reject + + GACi map[ComparableCstrAlias[int]]int // Accept, hide + GACV map[ComparableCstrAlias[ValueTextMarshaler]]int // Accept + GACP map[ComparableCstrAlias[PointerTextMarshaler]]int // Reject + GACiPtr map[ComparableCstrPtrAlias[int]]int // Reject + GACVPtr map[ComparableCstrPtrAlias[ValueTextMarshaler]]int // Accept, hide + GACPPtr map[ComparableCstrPtrAlias[PointerTextMarshaler]]int // Accept, hide + GABi map[BasicCstrAlias[int]]int // Accept, hide + GABs map[BasicCstrAlias[string]]int // Accept + GABiPtr map[BasicCstrPtrAlias[int]]int // Reject + GABT map[BadTildeCstrAlias[struct{}]]int // Reject + GABTPtr map[BadTildeCstrPtrAlias[struct{}]]int // Reject + GAGT map[GoodTildeCstrAlias[ValueTextMarshaler]]int // Accept + GAGTPtr map[GoodTildeCstrPtrAlias[ValueTextMarshaler]]int // Accept, hide + GANBV map[NonBasicCstrAlias[ValueTextMarshaler]]int // Accept + GANBP map[NonBasicCstrAlias[*PointerTextMarshaler]]int // Accept, hide + GANBVPtr map[NonBasicCstrPtrAlias[ValueTextMarshaler]]int // Accept, hide + GANBPPtr map[NonBasicCstrPtrAlias[*PointerTextMarshaler]]int // Reject + GAPlV1 map[PointableCstrAlias[ValueTextMarshaler]]int // Accept + GAPlV2 map[*PointableCstrAlias[ValueTextMarshaler]]int // Accept + GAPlP1 map[PointableCstrAlias[PointerTextMarshaler]]int // Reject + GAPlP2 map[*PointableCstrAlias[PointerTextMarshaler]]int // Accept + GAPlVPtr map[PointableCstrPtrAlias[ValueTextMarshaler]]int // Accept, hide + GAPlPPtr map[PointableCstrPtrAlias[PointerTextMarshaler]]int // Accept, hide + GAMi map[MixedCstrAlias[uint]]int // Accept, hide + GAMS map[MixedCstrAlias[StringType]]int // Accept + GAMV map[MixedCstrAlias[ValueTextMarshaler]]int // Accept + GAMSPtr map[MixedCstrPtrAlias[StringType]]int // Reject + GAMVPtr map[MixedCstrPtrAlias[ValueTextMarshaler]]int // Accept, hide + GAII map[InterfaceCstrAlias[encoding.TextMarshaler]]int // Accept, hide + GAIV map[InterfaceCstrAlias[ValueTextMarshaler]]int // Accept + GAIP map[InterfaceCstrAlias[*PointerTextMarshaler]]int // Accept, hide + GAIIPtr map[InterfaceCstrPtrAlias[encoding.TextMarshaler]]int // Reject + GAIVPtr map[InterfaceCstrPtrAlias[ValueTextMarshaler]]int // Accept, hide + GAIPPtr map[InterfaceCstrPtrAlias[*PointerTextMarshaler]]int // Reject + GAPrV map[PointerCstrAlias[ValueTextMarshaler, *ValueTextMarshaler]]int // Accept, hide + GAPrP map[PointerCstrAlias[PointerTextMarshaler, *PointerTextMarshaler]]int // Accept, hide + GAPrVPtr map[PointerCstrPtrAlias[ValueTextMarshaler, *ValueTextMarshaler]]int // Reject + GAPrPPtr map[PointerCstrPtrAlias[PointerTextMarshaler, *PointerTextMarshaler]]int // Reject +} + +func (*Service) Method() (_ Maps[PointerTextMarshaler, int, int, ValueTextMarshaler, *PointerTextMarshaler, ValueTextMarshaler, StringType, ValueTextMarshaler, *PointerTextMarshaler]) { + return +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&Service{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/marshalers/bound_types.json b/v3/internal/generator/testcases/marshalers/bound_types.json new file mode 100644 index 000000000..1a27d889e --- /dev/null +++ b/v3/internal/generator/testcases/marshalers/bound_types.json @@ -0,0 +1,3 @@ +[ + ".Service" +] diff --git a/v3/internal/generator/testcases/marshalers/events.json b/v3/internal/generator/testcases/marshalers/events.json new file mode 100644 index 000000000..f32a5804e --- /dev/null +++ b/v3/internal/generator/testcases/marshalers/events.json @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/v3/internal/generator/testcases/marshalers/main.go b/v3/internal/generator/testcases/marshalers/main.go new file mode 100644 index 000000000..95300480c --- /dev/null +++ b/v3/internal/generator/testcases/marshalers/main.go @@ -0,0 +1,215 @@ +package main + +import ( + _ "embed" + "encoding" + "encoding/json" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Service struct{} + +// class {} +type NonMarshaler struct{} + +// any +type ValueJsonMarshaler struct{} + +func (ValueJsonMarshaler) MarshalJSON() ([]byte, error) { return nil, nil } + +// any +type PointerJsonMarshaler struct{} + +func (*PointerJsonMarshaler) MarshalJSON() ([]byte, error) { return nil, nil } + +// string +type ValueTextMarshaler struct{} + +func (ValueTextMarshaler) MarshalText() ([]byte, error) { return nil, nil } + +// string +type PointerTextMarshaler struct{} + +func (*PointerTextMarshaler) MarshalText() ([]byte, error) { return nil, nil } + +// any +type ValueMarshaler struct{} + +func (ValueMarshaler) MarshalJSON() ([]byte, error) { return nil, nil } +func (ValueMarshaler) MarshalText() ([]byte, error) { return nil, nil } + +// any +type PointerMarshaler struct{} + +func (*PointerMarshaler) MarshalJSON() ([]byte, error) { return nil, nil } +func (*PointerMarshaler) MarshalText() ([]byte, error) { return nil, nil } + +// any +type UnderlyingJsonMarshaler struct{ json.Marshaler } + +// string +type UnderlyingTextMarshaler struct{ encoding.TextMarshaler } + +// any +type UnderlyingMarshaler struct { + json.Marshaler + encoding.TextMarshaler +} + +type customJsonMarshaler interface { + MarshalJSON() ([]byte, error) +} + +type customTextMarshaler interface { + MarshalText() ([]byte, error) +} + +type customMarshaler interface { + MarshalJSON() ([]byte, error) + MarshalText() ([]byte, error) +} + +// struct{} +type AliasNonMarshaler = struct{} + +// any +type AliasJsonMarshaler = struct{ json.Marshaler } + +// string +type AliasTextMarshaler = struct{ encoding.TextMarshaler } + +// any +type AliasMarshaler = struct { + json.Marshaler + encoding.TextMarshaler +} + +// any +type ImplicitJsonMarshaler UnderlyingJsonMarshaler + +// string +type ImplicitTextMarshaler UnderlyingTextMarshaler + +// any +type ImplicitMarshaler UnderlyingMarshaler + +// string +type ImplicitNonJson UnderlyingMarshaler + +func (ImplicitNonJson) MarshalJSON() {} + +// any +type ImplicitNonText UnderlyingMarshaler + +func (ImplicitNonText) MarshalText() {} + +// class{ Marshaler, TextMarshaler } +type ImplicitNonMarshaler UnderlyingMarshaler + +func (ImplicitNonMarshaler) MarshalJSON() {} +func (ImplicitNonMarshaler) MarshalText() {} + +// any +type ImplicitJsonButText UnderlyingJsonMarshaler + +func (ImplicitJsonButText) MarshalText() ([]byte, error) { return nil, nil } + +// any +type ImplicitTextButJson UnderlyingTextMarshaler + +func (ImplicitTextButJson) MarshalJSON() ([]byte, error) { return nil, nil } + +type Data struct { + NM NonMarshaler + NMPtr *NonMarshaler // NonMarshaler | null + + VJM ValueJsonMarshaler + VJMPtr *ValueJsonMarshaler // ValueJsonMarshaler | null + PJM PointerJsonMarshaler + PJMPtr *PointerJsonMarshaler // PointerJsonMarshaler | null + + VTM ValueTextMarshaler + VTMPtr *ValueTextMarshaler // ValueTextMarshaler | null + PTM PointerTextMarshaler + PTMPtr *PointerTextMarshaler // PointerTextMarshaler | null + + VM ValueMarshaler + VMPtr *ValueMarshaler // ValueMarshaler | null + PM PointerMarshaler + PMPtr *PointerMarshaler // PointerMarshaler | null + + UJM UnderlyingJsonMarshaler + UJMPtr *UnderlyingJsonMarshaler // UnderlyingJsonMarshaler | null + UTM UnderlyingTextMarshaler + UTMPtr *UnderlyingTextMarshaler // UnderlyingTextMarshaler | null + UM UnderlyingMarshaler + UMPtr *UnderlyingMarshaler // UnderlyingMarshaler | null + + JM struct{ json.Marshaler } // any + JMPtr *struct{ json.Marshaler } // any | null + TM struct{ encoding.TextMarshaler } // string + TMPtr *struct{ encoding.TextMarshaler } // string | null + CJM struct{ customJsonMarshaler } // any + CJMPtr *struct{ customJsonMarshaler } // any | null + CTM struct{ customTextMarshaler } // string + CTMPtr *struct{ customTextMarshaler } // string | null + CM struct{ customMarshaler } // any + CMPtr *struct{ customMarshaler } // any | null + + ANM AliasNonMarshaler + ANMPtr *AliasNonMarshaler // AliasNonMarshaler | null + AJM AliasJsonMarshaler + AJMPtr *AliasJsonMarshaler // AliasJsonMarshaler | null + ATM AliasTextMarshaler + ATMPtr *AliasTextMarshaler // AliasTextMarshaler | null + AM AliasMarshaler + AMPtr *AliasMarshaler // AliasMarshaler | null + + ImJM ImplicitJsonMarshaler + ImJMPtr *ImplicitJsonMarshaler // ImplicitJsonMarshaler | null + ImTM ImplicitTextMarshaler + ImTMPtr *ImplicitTextMarshaler // ImplicitTextMarshaler | null + ImM ImplicitMarshaler + ImMPtr *ImplicitMarshaler // ImplicitMarshaler | null + + ImNJ ImplicitNonJson + ImNJPtr *ImplicitNonJson // ImplicitNonJson | null + ImNT ImplicitNonText + ImNTPtr *ImplicitNonText // ImplicitNonText | null + ImNM ImplicitNonMarshaler + ImNMPtr *ImplicitNonMarshaler // ImplicitNonMarshaler | null + + ImJbT ImplicitJsonButText + ImJbTPtr *ImplicitJsonButText // ImplicitJsonButText | null + ImTbJ ImplicitTextButJson + ImTbJPtr *ImplicitTextButJson // ImplicitTextButJson | null +} + +func (*Service) Method() (_ Data) { + return +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&Service{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} + +func init() { + application.RegisterEvent[*struct{ Field []bool }]("collision") + application.RegisterEvent[*struct{ Field []bool }]("overlap") + application.RegisterEvent[json.Marshaler]("interface") +} diff --git a/v3/internal/generator/testcases/no_bindings_here/more/more.go b/v3/internal/generator/testcases/no_bindings_here/more/more.go new file mode 100644 index 000000000..d17eff09c --- /dev/null +++ b/v3/internal/generator/testcases/no_bindings_here/more/more.go @@ -0,0 +1,4 @@ +package more + +// StringPtr is a nullable string. +type StringPtr *string diff --git a/v3/internal/generator/testcases/no_bindings_here/other/othermethods.go b/v3/internal/generator/testcases/no_bindings_here/other/othermethods.go new file mode 100644 index 000000000..8d4ec6ba8 --- /dev/null +++ b/v3/internal/generator/testcases/no_bindings_here/other/othermethods.go @@ -0,0 +1,11 @@ +package other + +// OtherMethods has another method, but through a private embedded type. +type OtherMethods struct { + otherMethodsImpl +} + +type otherMethodsImpl int + +// LikeThisOtherOne does nothing as well, but is different. +func (*otherMethodsImpl) LikeThisOtherOne() {} diff --git a/v3/internal/generator/testcases/no_bindings_here/other/otherperson.go b/v3/internal/generator/testcases/no_bindings_here/other/otherperson.go new file mode 100644 index 000000000..516c965b5 --- /dev/null +++ b/v3/internal/generator/testcases/no_bindings_here/other/otherperson.go @@ -0,0 +1,10 @@ +package other + +// OtherPerson is like a person, but different. +type OtherPerson[T any] struct { + // They have a name as well. + Name string + + // But they may have many differences. + Differences []T +} diff --git a/v3/internal/generator/testcases/no_bindings_here/person.go b/v3/internal/generator/testcases/no_bindings_here/person.go new file mode 100644 index 000000000..b0b6f941d --- /dev/null +++ b/v3/internal/generator/testcases/no_bindings_here/person.go @@ -0,0 +1,26 @@ +package nobindingshere + +import "github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other" + +// Person is not a number. +type Person struct { + // They have a name. + Name string + Friends [4]Impersonator // Exactly 4 sketchy friends. +} + +// Impersonator gets their fields from other people. +type Impersonator = other.OtherPerson[int] + +// HowDifferent is a curious kind of person +// that lets other people decide how they are different. +type HowDifferent[How any] other.OtherPerson[map[string]How] + +// PrivatePerson gets their fields from hidden sources. +type PrivatePerson = personImpl + +type personImpl struct { + // Nickname conceals a person's identity. + Nickname string + Person +} diff --git a/v3/internal/generator/testcases/no_bindings_here/somemethods.go b/v3/internal/generator/testcases/no_bindings_here/somemethods.go new file mode 100644 index 000000000..4b0602a64 --- /dev/null +++ b/v3/internal/generator/testcases/no_bindings_here/somemethods.go @@ -0,0 +1,13 @@ +package nobindingshere + +import "github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other" + +// SomeMethods exports some methods. +type SomeMethods struct { + other.OtherMethods +} + +// LikeThisOne is an example method that does nothing. +func (SomeMethods) LikeThisOne() (_ Person, _ HowDifferent[bool], _ PrivatePerson) { + return +} diff --git a/v3/internal/generator/testcases/out_of_tree/bound_types.json b/v3/internal/generator/testcases/out_of_tree/bound_types.json new file mode 100644 index 000000000..c0714f177 --- /dev/null +++ b/v3/internal/generator/testcases/out_of_tree/bound_types.json @@ -0,0 +1,7 @@ +[ + ".GreetService", + ".EmbedService", + ".EmbedOther", + "/../no_bindings_here.SomeMethods", + "/../no_bindings_here/other.OtherMethods" +] diff --git a/v3/internal/generator/testcases/out_of_tree/main.go b/v3/internal/generator/testcases/out_of_tree/main.go new file mode 100644 index 000000000..c3dfbc9ee --- /dev/null +++ b/v3/internal/generator/testcases/out_of_tree/main.go @@ -0,0 +1,47 @@ +package main + +import ( + _ "embed" + "log" + + nobindingshere "github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here" + "github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService int + +// EmbedService is tricky. +type EmbedService struct { + nobindingshere.SomeMethods +} + +// EmbedOther is even trickier. +type EmbedOther struct { + other.OtherMethods +} + +// Greet someone +func (*GreetService) Greet(string) {} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(new(GreetService)), + application.NewService(&EmbedService{}), + application.NewService(&EmbedOther{}), + application.NewService(&nobindingshere.SomeMethods{}), + application.NewService(&other.OtherMethods{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/struct_literal_multiple/bound_types.json b/v3/internal/generator/testcases/struct_literal_multiple/bound_types.json new file mode 100644 index 000000000..be815a97e --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple/bound_types.json @@ -0,0 +1,4 @@ +[ + ".GreetService", + ".OtherService" +] diff --git a/v3/internal/generator/testcases/struct_literal_multiple/main.go b/v3/internal/generator/testcases/struct_literal_multiple/main.go new file mode 100644 index 000000000..5e1081504 --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple/main.go @@ -0,0 +1,41 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type GreetService struct { + SomeVariable int + lowerCase string +} + +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +type OtherService struct { + t int +} + +func (o *OtherService) Hello() {} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + application.NewService(&OtherService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/struct_literal_multiple_files/bound_types.json b/v3/internal/generator/testcases/struct_literal_multiple_files/bound_types.json new file mode 100644 index 000000000..be815a97e --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple_files/bound_types.json @@ -0,0 +1,4 @@ +[ + ".GreetService", + ".OtherService" +] diff --git a/v3/internal/generator/testcases/struct_literal_multiple_files/greet.go b/v3/internal/generator/testcases/struct_literal_multiple_files/greet.go new file mode 100644 index 000000000..2a45396a7 --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple_files/greet.go @@ -0,0 +1,14 @@ +package main + +import ( + _ "embed" +) + +type GreetService struct { + SomeVariable int + lowerCase string +} + +func (*GreetService) Greet(name string) string { + return "Hello " + name +} diff --git a/v3/internal/generator/testcases/struct_literal_multiple_files/main.go b/v3/internal/generator/testcases/struct_literal_multiple_files/main.go new file mode 100644 index 000000000..ae80a4cba --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple_files/main.go @@ -0,0 +1,26 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + application.NewService(&OtherService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/struct_literal_multiple_files/other.go b/v3/internal/generator/testcases/struct_literal_multiple_files/other.go new file mode 100644 index 000000000..ad5e661ef --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple_files/other.go @@ -0,0 +1,7 @@ +package main + +type OtherService struct { + t int +} + +func (o *OtherService) Hello() {} diff --git a/v3/internal/generator/testcases/struct_literal_multiple_other/bound_types.json b/v3/internal/generator/testcases/struct_literal_multiple_other/bound_types.json new file mode 100644 index 000000000..86a0a8812 --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple_other/bound_types.json @@ -0,0 +1,4 @@ +[ + ".GreetService", + "/services.OtherService" +] diff --git a/v3/internal/generator/testcases/struct_literal_multiple_other/main.go b/v3/internal/generator/testcases/struct_literal_multiple_other/main.go new file mode 100644 index 000000000..7b7a3a2ab --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple_other/main.go @@ -0,0 +1,49 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string + target *Person +} + +type Person struct { + Name string + Address *services.Address +} + +// Greet does XYZ +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// NewPerson creates a new person +func (*GreetService) NewPerson(name string) *Person { + return &Person{Name: name} +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + application.NewService(&services.OtherService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/struct_literal_multiple_other/services/other.go b/v3/internal/generator/testcases/struct_literal_multiple_other/services/other.go new file mode 100644 index 000000000..55472595b --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple_other/services/other.go @@ -0,0 +1,22 @@ +package services + +// OtherService is a struct +// that does things +type OtherService struct { + t int +} + +type Address struct { + Street string + State string + Country string +} + +// Yay does this and that +func (o *OtherService) Yay() *Address { + return &Address{ + Street: "123 Pitt Street", + State: "New South Wales", + Country: "Australia", + } +} diff --git a/v3/internal/generator/testcases/struct_literal_non_pointer_single/bound_types.json b/v3/internal/generator/testcases/struct_literal_non_pointer_single/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_non_pointer_single/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/struct_literal_non_pointer_single/main.go b/v3/internal/generator/testcases/struct_literal_non_pointer_single/main.go new file mode 100644 index 000000000..e0fd8247d --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_non_pointer_single/main.go @@ -0,0 +1,205 @@ +package main + +import ( + _ "embed" + "log" + "strings" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Person struct { + Name string + Parent *Person + Details struct { + Age int + Address struct { + Street string + } + } +} + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (GreetService) Greet(name string) string { + return "Hello " + name +} + +func (GreetService) NoInputsStringOut() string { + return "Hello" +} + +func (GreetService) StringArrayInputStringOut(in []string) string { + return strings.Join(in, ",") +} + +func (GreetService) StringArrayInputStringArrayOut(in []string) []string { + return in +} + +func (GreetService) StringArrayInputNamedOutput(in []string) (output []string) { + return in +} + +func (GreetService) StringArrayInputNamedOutputs(in []string) (output []string, err error) { + return in, nil +} + +func (GreetService) IntPointerInputNamedOutputs(in *int) (output *int, err error) { + return in, nil +} + +func (GreetService) UIntPointerInAndOutput(in *uint) *uint { + return in +} + +func (GreetService) UInt8PointerInAndOutput(in *uint8) *uint8 { + return in +} + +func (GreetService) UInt16PointerInAndOutput(in *uint16) *uint16 { + return in +} + +func (GreetService) UInt32PointerInAndOutput(in *uint32) *uint32 { + return in +} + +func (GreetService) UInt64PointerInAndOutput(in *uint64) *uint64 { + return in +} + +func (GreetService) IntPointerInAndOutput(in *int) *int { + return in +} + +func (GreetService) Int8PointerInAndOutput(in *int8) *int8 { + return in +} + +func (GreetService) Int16PointerInAndOutput(in *int16) *int16 { + return in +} + +func (GreetService) Int32PointerInAndOutput(in *int32) *int32 { + return in +} + +func (GreetService) Int64PointerInAndOutput(in *int64) *int64 { + return in +} + +func (GreetService) IntInIntOut(in int) int { + return in +} + +func (GreetService) Int8InIntOut(in int8) int8 { + return in +} +func (GreetService) Int16InIntOut(in int16) int16 { + return in +} +func (GreetService) Int32InIntOut(in int32) int32 { + return in +} +func (GreetService) Int64InIntOut(in int64) int64 { + return in +} + +func (GreetService) UIntInUIntOut(in uint) uint { + return in +} + +func (GreetService) UInt8InUIntOut(in uint8) uint8 { + return in +} +func (GreetService) UInt16InUIntOut(in uint16) uint16 { + return in +} +func (GreetService) UInt32InUIntOut(in uint32) uint32 { + return in +} +func (GreetService) UInt64InUIntOut(in uint64) uint64 { + return in +} + +func (GreetService) Float32InFloat32Out(in float32) float32 { + return in +} + +func (GreetService) Float64InFloat64Out(in float64) float64 { + return in +} + +func (GreetService) PointerFloat32InFloat32Out(in *float32) *float32 { + return in +} + +func (GreetService) PointerFloat64InFloat64Out(in *float64) *float64 { + return in +} + +func (GreetService) BoolInBoolOut(in bool) bool { + return in +} + +func (GreetService) PointerBoolInBoolOut(in *bool) *bool { + return in +} + +func (GreetService) PointerStringInStringOut(in *string) *string { + return in +} + +func (GreetService) StructPointerInputErrorOutput(in *Person) error { + return nil +} + +func (GreetService) StructInputStructOutput(in Person) Person { + return in +} + +func (GreetService) StructPointerInputStructPointerOutput(in *Person) *Person { + return in +} + +func (GreetService) MapIntInt(in map[int]int) { +} + +func (GreetService) PointerMapIntInt(in *map[int]int) { +} + +func (GreetService) MapIntIntPointer(in map[int]*int) { +} + +func (GreetService) MapIntSliceInt(in map[int][]int) { +} + +func (GreetService) MapIntSliceIntInMapIntSliceIntOut(in map[int][]int) (out map[int][]int) { + return nil +} + +func (GreetService) ArrayInt(in [4]int) { +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/struct_literal_single/bound_types.json b/v3/internal/generator/testcases/struct_literal_single/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_single/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/struct_literal_single/main.go b/v3/internal/generator/testcases/struct_literal_single/main.go new file mode 100644 index 000000000..945874a0b --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_single/main.go @@ -0,0 +1,205 @@ +package main + +import ( + _ "embed" + "log" + "strings" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Person struct { + Name string + Parent *Person + Details struct { + Age int + Address struct { + Street string + } + } +} + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func (*GreetService) NoInputsStringOut() string { + return "Hello" +} + +func (*GreetService) StringArrayInputStringOut(in []string) string { + return strings.Join(in, ",") +} + +func (*GreetService) StringArrayInputStringArrayOut(in []string) []string { + return in +} + +func (*GreetService) StringArrayInputNamedOutput(in []string) (output []string) { + return in +} + +func (*GreetService) StringArrayInputNamedOutputs(in []string) (output []string, err error) { + return in, nil +} + +func (*GreetService) IntPointerInputNamedOutputs(in *int) (output *int, err error) { + return in, nil +} + +func (*GreetService) UIntPointerInAndOutput(in *uint) *uint { + return in +} + +func (*GreetService) UInt8PointerInAndOutput(in *uint8) *uint8 { + return in +} + +func (*GreetService) UInt16PointerInAndOutput(in *uint16) *uint16 { + return in +} + +func (*GreetService) UInt32PointerInAndOutput(in *uint32) *uint32 { + return in +} + +func (*GreetService) UInt64PointerInAndOutput(in *uint64) *uint64 { + return in +} + +func (*GreetService) IntPointerInAndOutput(in *int) *int { + return in +} + +func (*GreetService) Int8PointerInAndOutput(in *int8) *int8 { + return in +} + +func (*GreetService) Int16PointerInAndOutput(in *int16) *int16 { + return in +} + +func (*GreetService) Int32PointerInAndOutput(in *int32) *int32 { + return in +} + +func (*GreetService) Int64PointerInAndOutput(in *int64) *int64 { + return in +} + +func (*GreetService) IntInIntOut(in int) int { + return in +} + +func (*GreetService) Int8InIntOut(in int8) int8 { + return in +} +func (*GreetService) Int16InIntOut(in int16) int16 { + return in +} +func (*GreetService) Int32InIntOut(in int32) int32 { + return in +} +func (*GreetService) Int64InIntOut(in int64) int64 { + return in +} + +func (*GreetService) UIntInUIntOut(in uint) uint { + return in +} + +func (*GreetService) UInt8InUIntOut(in uint8) uint8 { + return in +} +func (*GreetService) UInt16InUIntOut(in uint16) uint16 { + return in +} +func (*GreetService) UInt32InUIntOut(in uint32) uint32 { + return in +} +func (*GreetService) UInt64InUIntOut(in uint64) uint64 { + return in +} + +func (*GreetService) Float32InFloat32Out(in float32) float32 { + return in +} + +func (*GreetService) Float64InFloat64Out(in float64) float64 { + return in +} + +func (*GreetService) PointerFloat32InFloat32Out(in *float32) *float32 { + return in +} + +func (*GreetService) PointerFloat64InFloat64Out(in *float64) *float64 { + return in +} + +func (*GreetService) BoolInBoolOut(in bool) bool { + return in +} + +func (*GreetService) PointerBoolInBoolOut(in *bool) *bool { + return in +} + +func (*GreetService) PointerStringInStringOut(in *string) *string { + return in +} + +func (*GreetService) StructPointerInputErrorOutput(in *Person) error { + return nil +} + +func (*GreetService) StructInputStructOutput(in Person) Person { + return in +} + +func (*GreetService) StructPointerInputStructPointerOutput(in *Person) *Person { + return in +} + +func (*GreetService) MapIntInt(in map[int]int) { +} + +func (*GreetService) PointerMapIntInt(in *map[int]int) { +} + +func (*GreetService) MapIntIntPointer(in map[int]*int) { +} + +func (*GreetService) MapIntSliceInt(in map[int][]int) { +} + +func (*GreetService) MapIntSliceIntInMapIntSliceIntOut(in map[int][]int) (out map[int][]int) { + return nil +} + +func (*GreetService) ArrayInt(in [4]int) { +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/variable_single/bound_types.json b/v3/internal/generator/testcases/variable_single/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/variable_single/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/variable_single/main.go b/v3/internal/generator/testcases/variable_single/main.go new file mode 100644 index 000000000..5baf1a04f --- /dev/null +++ b/v3/internal/generator/testcases/variable_single/main.go @@ -0,0 +1,37 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func main() { + greetService := application.NewService(&GreetService{}) + app := application.New(application.Options{ + Services: []application.Service{ + greetService, + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/variable_single_from_function/bound_types.json b/v3/internal/generator/testcases/variable_single_from_function/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/variable_single_from_function/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/variable_single_from_function/main.go b/v3/internal/generator/testcases/variable_single_from_function/main.go new file mode 100644 index 000000000..247702051 --- /dev/null +++ b/v3/internal/generator/testcases/variable_single_from_function/main.go @@ -0,0 +1,42 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func NewGreetService() application.Service { + return application.NewService(&GreetService{}) +} + +func main() { + greetService := NewGreetService() + app := application.New(application.Options{ + Services: []application.Service{ + greetService, + }, + }) + + _ = app.Window.New() // discard + // or: win := app.Window.New() // keep for later + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/variable_single_from_other_function/bound_types.json b/v3/internal/generator/testcases/variable_single_from_other_function/bound_types.json new file mode 100644 index 000000000..86a0a8812 --- /dev/null +++ b/v3/internal/generator/testcases/variable_single_from_other_function/bound_types.json @@ -0,0 +1,4 @@ +[ + ".GreetService", + "/services.OtherService" +] diff --git a/v3/internal/generator/testcases/variable_single_from_other_function/main.go b/v3/internal/generator/testcases/variable_single_from_other_function/main.go new file mode 100644 index 000000000..6bbb73f46 --- /dev/null +++ b/v3/internal/generator/testcases/variable_single_from_other_function/main.go @@ -0,0 +1,53 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string + target *Person +} + +// Person is a person! +// They have a name and an address +type Person struct { + Name string + Address *services.Address +} + +// Greet does XYZ +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// NewPerson creates a new person +func (*GreetService) NewPerson(name string) *Person { + return &Person{Name: name} +} + +func main() { + otherService := services.NewOtherService() + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + otherService, + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/variable_single_from_other_function/services/other.go b/v3/internal/generator/testcases/variable_single_from_other_function/services/other.go new file mode 100644 index 000000000..4ac6e6efd --- /dev/null +++ b/v3/internal/generator/testcases/variable_single_from_other_function/services/other.go @@ -0,0 +1,28 @@ +package services + +import "github.com/wailsapp/wails/v3/pkg/application" + +// OtherService is a struct +// that does things +type OtherService struct { + t int +} + +type Address struct { + Street string + State string + Country string +} + +// Yay does this and that +func (o *OtherService) Yay() *Address { + return &Address{ + Street: "123 Pitt Street", + State: "New South Wales", + Country: "Australia", + } +} + +func NewOtherService() application.Service { + return application.NewService(&OtherService{}) +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/index.js new file mode 100644 index 000000000..cf48d86db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/index.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {$models.TextMarshaler} TextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/json/index.js new file mode 100644 index 000000000..22f1fd904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/json/index.js @@ -0,0 +1,11 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {$models.Marshaler} Marshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/json/models.js new file mode 100644 index 000000000..96abf0ef1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/json/models.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {any} Marshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/models.js new file mode 100644 index 000000000..59cfef5bd --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/models.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {any} TextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/eventcreate.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/eventcreate.js new file mode 100644 index 000000000..9cc7781aa --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/eventcreate.js @@ -0,0 +1,39 @@ +//@ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../encoding/json/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as events_only$0 from "./generator/testcases/events_only/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as more$0 from "./generator/testcases/no_bindings_here/more/models.js"; + +function configure() { + Object.freeze(Object.assign($Create.Events, { + "events_only:class": $$createType0, + "events_only:map": $$createType2, + "events_only:other": $$createType3, + "overlap": $$createType6, + })); +} + +// Private type creation functions +const $$createType0 = events_only$0.SomeClass.createFrom; +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = $Create.Map($Create.Any, $$createType1); +const $$createType3 = $Create.Array($Create.Any); +const $$createType4 = $Create.Array($Create.Any); +const $$createType5 = $Create.Struct({ + "Field": $$createType4, +}); +const $$createType6 = $Create.Nullable($$createType5); + +configure(); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/eventdata.d.ts b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/eventdata.d.ts new file mode 100644 index 000000000..d265e3adc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/eventdata.d.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type { Events } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as json$0 from "../../../../../encoding/json/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as events_only$0 from "./generator/testcases/events_only/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as more$0 from "./generator/testcases/no_bindings_here/more/models.js"; + +declare module "/wails/runtime.js" { + namespace Events { + interface CustomEvents { + "events_only:class": events_only$0.SomeClass; + "events_only:map": { [_: string]: number[] }; + "events_only:nodata": void; + "events_only:other": more$0.StringPtr[]; + "events_only:string": string; + "interface": json$0.Marshaler; + "overlap": {"Field": boolean[]} | null; + } + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js new file mode 100644 index 000000000..fcbd41a3a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js @@ -0,0 +1,97 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + * @param {$models.Alias} aliasValue + * @returns {$CancellablePromise<$models.Person>} + */ +export function Get(aliasValue) { + return $Call.ByID(1928502664, aliasValue).then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +/** + * Apparently, aliases are all the rage right now. + * @param {$models.AliasedPerson} p + * @returns {$CancellablePromise<$models.StrangelyAliasedPerson>} + */ +export function GetButAliased(p) { + return $Call.ByID(1896499664, p).then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +/** + * Get someone quite different. + * @returns {$CancellablePromise<$models.GenericPerson>} + */ +export function GetButDifferent() { + return $Call.ByID(2240931744).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +/** + * @returns {$CancellablePromise} + */ +export function GetButForeignPrivateAlias() { + return $Call.ByID(643456960).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @returns {$CancellablePromise<$models.AliasGroup>} + */ +export function GetButGenericAliases() { + return $Call.ByID(914093800).then(/** @type {($result: any) => any} */(($result) => { + return $$createType3($result); + })); +} + +/** + * Greet a lot of unusual things. + * @param {$models.EmptyAliasStruct} $0 + * @param {$models.EmptyStruct} $1 + * @returns {$CancellablePromise<$models.AliasStruct>} + */ +export function Greet($0, $1) { + return $Call.ByID(1411160069, $0, $1).then(/** @type {($result: any) => any} */(($result) => { + return $$createType7($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.GenericPerson.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; +const $$createType3 = $models.AliasGroup.createFrom; +const $$createType4 = $Create.Array($Create.Any); +const $$createType5 = $Create.Array($Create.Any); +const $$createType6 = $Create.Struct({ + "NoMoreIdeas": $$createType5, +}); +const $$createType7 = $Create.Struct({ + "Foo": $$createType4, + "Other": $$createType6, +}); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js new file mode 100644 index 000000000..4278c7958 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js @@ -0,0 +1,61 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + AliasGroup, + AliasedPerson, + EmptyStruct, + GenericPerson, + GenericPersonAlias, + IndirectPersonAlias, + Person, + StrangelyAliasedPerson, + TPIndirectPersonAlias +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * A nice type Alias. + * @typedef {$models.Alias} Alias + */ + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {$models.AliasStruct} AliasStruct + */ + +/** + * An empty struct alias. + * @typedef {$models.EmptyAliasStruct} EmptyAliasStruct + */ + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {$models.GenericAlias} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {$models.GenericMapAlias} GenericMapAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {$models.GenericPtrAlias} GenericPtrAlias + */ + +/** + * Another struct alias. + * @typedef {$models.OtherAliasStruct} OtherAliasStruct + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js new file mode 100644 index 000000000..3de57786d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js @@ -0,0 +1,334 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * A nice type Alias. + * @typedef {number} Alias + */ + +/** + * A class whose fields have various aliased types. + */ +export class AliasGroup { + /** + * Creates a new AliasGroup instance. + * @param {Partial} [$$source = {}] - The source object to create the AliasGroup. + */ + constructor($$source = {}) { + if (!("GAi" in $$source)) { + /** + * @member + * @type {GenericAlias} + */ + this["GAi"] = 0; + } + if (!("GAP" in $$source)) { + /** + * @member + * @type {GenericAlias>} + */ + this["GAP"] = (new GenericPerson()); + } + if (!("GPAs" in $$source)) { + /** + * @member + * @type {GenericPtrAlias} + */ + this["GPAs"] = null; + } + if (!("GPAP" in $$source)) { + /** + * @member + * @type {GenericPtrAlias>} + */ + this["GPAP"] = null; + } + if (!("GMA" in $$source)) { + /** + * @member + * @type {GenericMapAlias} + */ + this["GMA"] = {}; + } + if (!("GPA" in $$source)) { + /** + * @member + * @type {GenericPersonAlias} + */ + this["GPA"] = (new GenericPersonAlias()); + } + if (!("IPA" in $$source)) { + /** + * @member + * @type {IndirectPersonAlias} + */ + this["IPA"] = (new IndirectPersonAlias()); + } + if (!("TPIPA" in $$source)) { + /** + * @member + * @type {TPIndirectPersonAlias} + */ + this["TPIPA"] = (new TPIndirectPersonAlias()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new AliasGroup instance from a string or object. + * @param {any} [$$source = {}] + * @returns {AliasGroup} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType0; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType5; + const $$createField4_0 = $$createType6; + const $$createField5_0 = $$createType8; + const $$createField6_0 = $$createType8; + const $$createField7_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("GAP" in $$parsedSource) { + $$parsedSource["GAP"] = $$createField1_0($$parsedSource["GAP"]); + } + if ("GPAs" in $$parsedSource) { + $$parsedSource["GPAs"] = $$createField2_0($$parsedSource["GPAs"]); + } + if ("GPAP" in $$parsedSource) { + $$parsedSource["GPAP"] = $$createField3_0($$parsedSource["GPAP"]); + } + if ("GMA" in $$parsedSource) { + $$parsedSource["GMA"] = $$createField4_0($$parsedSource["GMA"]); + } + if ("GPA" in $$parsedSource) { + $$parsedSource["GPA"] = $$createField5_0($$parsedSource["GPA"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField6_0($$parsedSource["IPA"]); + } + if ("TPIPA" in $$parsedSource) { + $$parsedSource["TPIPA"] = $$createField7_0($$parsedSource["TPIPA"]); + } + return new AliasGroup(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {Object} AliasStruct + * @property {number[]} Foo - A field with a comment. + * @property {string} [Bar] - Definitely not Foo. + * @property {string} [Baz] - Definitely not Foo. + * @property {OtherAliasStruct} Other - A nested alias struct. + */ + +/** + * An empty struct alias. + * @typedef { { + * } } EmptyAliasStruct + */ + +/** + * An empty struct. + */ +export class EmptyStruct { + /** + * Creates a new EmptyStruct instance. + * @param {Partial} [$$source = {}] - The source object to create the EmptyStruct. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new EmptyStruct instance from a string or object. + * @param {any} [$$source = {}] + * @returns {EmptyStruct} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new EmptyStruct(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {T} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {{ [_: string]: U }} GenericMapAlias + */ + +/** + * A generic struct containing an alias. + * @template T + */ +export class GenericPerson { + /** + * Creates a new GenericPerson instance. + * @param {Partial>} [$$source = {}] - The source object to create the GenericPerson. + */ + constructor($$source = {}) { + if (/** @type {any} */(false)) { + /** + * @member + * @type {T | undefined} + */ + this["Name"] = undefined; + } + if (!("AliasedField" in $$source)) { + /** + * @member + * @type {Alias} + */ + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class GenericPerson. + * @template [T=any] + * @param {(source: any) => T} $$createParamT + * @returns {($$source?: any) => GenericPerson} + */ + static createFrom($$createParamT) { + const $$createField0_0 = $$createParamT; + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Name" in $$parsedSource) { + $$parsedSource["Name"] = $$createField0_0($$parsedSource["Name"]); + } + return new GenericPerson(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +/** + * A generic alias that wraps a generic struct. + */ +export const GenericPersonAlias = GenericPerson; + +/** + * A generic alias that wraps a generic struct. + * @template T + * @typedef {GenericPerson[]>} GenericPersonAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {GenericAlias | null} GenericPtrAlias + */ + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export const IndirectPersonAlias = GenericPersonAlias; + +/** + * An alias that wraps a class through a non-typeparam alias. + * @typedef {GenericPersonAlias} IndirectPersonAlias + */ + +/** + * Another struct alias. + * @typedef {Object} OtherAliasStruct + * @property {number[]} NoMoreIdeas + */ + +/** + * A non-generic struct containing an alias. + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * The Person's name. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("AliasedField" in $$source)) { + /** + * A random alias field. + * @member + * @type {Alias} + */ + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * A class alias. + */ +export const AliasedPerson = Person; + +/** + * A class alias. + * @typedef {Person} AliasedPerson + */ + +/** + * Another class alias, but ordered after its aliased class. + */ +export const StrangelyAliasedPerson = Person; + +/** + * Another class alias, but ordered after its aliased class. + * @typedef {Person} StrangelyAliasedPerson + */ + +/** + * An alias that wraps a class through a typeparam alias. + */ +export const TPIndirectPersonAlias = GenericPerson; + +/** + * An alias that wraps a class through a typeparam alias. + * @typedef {GenericAlias>} TPIndirectPersonAlias + */ + +// Private type creation functions +const $$createType0 = GenericPerson.createFrom($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = $Create.Nullable($$createType1); +const $$createType3 = $Create.Array($Create.Any); +const $$createType4 = GenericPerson.createFrom($$createType3); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Array($Create.Any); +const $$createType8 = GenericPerson.createFrom($$createType7); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js new file mode 100644 index 000000000..f6c839720 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js new file mode 100644 index 000000000..42219054f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod() { + return $Call.ByID(2241101727); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js new file mode 100644 index 000000000..5e27fbc9e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod2() { + return $Call.ByID(1556848345); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js new file mode 100644 index 000000000..fa634943d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {$models.Person} person + * @param {$models.Embedded1} emb + * @returns {$CancellablePromise} + */ +export function Greet(person, emb) { + return $Call.ByID(1411160069, person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js new file mode 100644 index 000000000..ab78e5ea3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Embedded1, + Person, + Title +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Embedded3} Embedded3 + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js new file mode 100644 index 000000000..7a0edf1c7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js @@ -0,0 +1,272 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Embedded1 { + /** + * Creates a new Embedded1 instance. + * @param {Partial} [$$source = {}] - The source object to create the Embedded1. + */ + constructor($$source = {}) { + if (!("Friends" in $$source)) { + /** + * Friends should be shadowed in Person by a field of lesser depth + * @member + * @type {number} + */ + this["Friends"] = 0; + } + if (!("Vanish" in $$source)) { + /** + * Vanish should be omitted from Person because there is another field with same depth and no tag + * @member + * @type {number} + */ + this["Vanish"] = 0; + } + if (!("StillThere" in $$source)) { + /** + * StillThere should be shadowed in Person by other field with same depth and a json tag + * @member + * @type {string} + */ + this["StillThere"] = ""; + } + if (!("NamingThingsIsHard" in $$source)) { + /** + * NamingThingsIsHard is a law of programming + * @member + * @type {`${boolean}`} + */ + this["NamingThingsIsHard"] = "false"; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Embedded1 instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Embedded1} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Embedded1(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * @typedef {string} Embedded3 + */ + +/** + * Person represents a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (/** @type {any} */(false)) { + /** + * Titles is optional in JSON + * @member + * @type {Title[] | undefined} + */ + this["Titles"] = undefined; + } + if (!("Names" in $$source)) { + /** + * Names has a + * multiline comment + * @member + * @type {string[]} + */ + this["Names"] = []; + } + if (!("Partner" in $$source)) { + /** + * Partner has a custom and complex JSON key + * @member + * @type {Person | null} + */ + this["Partner"] = null; + } + if (!("Friends" in $$source)) { + /** + * @member + * @type {(Person | null)[]} + */ + this["Friends"] = []; + } + if (!("NamingThingsIsHard" in $$source)) { + /** + * NamingThingsIsHard is a law of programming + * @member + * @type {`${boolean}`} + */ + this["NamingThingsIsHard"] = "false"; + } + if (!("StillThere" in $$source)) { + /** + * StillThereButRenamed should shadow in Person the other field with same depth and no json tag + * @member + * @type {Embedded3 | null} + */ + this["StillThere"] = null; + } + if (!("-" in $$source)) { + /** + * StrangeNumber maps to "-" + * @member + * @type {number} + */ + this["-"] = 0; + } + if (!("Embedded3" in $$source)) { + /** + * Embedded3 should appear with key "Embedded3" + * @member + * @type {Embedded3} + */ + this["Embedded3"] = ""; + } + if (!("StrangerNumber" in $$source)) { + /** + * StrangerNumber is serialized as a string + * @member + * @type {`${number}`} + */ + this["StrangerNumber"] = "0"; + } + if (/** @type {any} */(false)) { + /** + * StrangestString is optional and serialized as a JSON string + * @member + * @type {`"${string}"` | undefined} + */ + this["StrangestString"] = undefined; + } + if (/** @type {any} */(false)) { + /** + * StringStrangest is serialized as a JSON string and optional + * @member + * @type {`"${string}"` | undefined} + */ + this["StringStrangest"] = undefined; + } + if (/** @type {any} */(false)) { + /** + * embedded4 should be optional and appear with key "emb4" + * @member + * @type {embedded4 | undefined} + */ + this["emb4"] = undefined; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType3; + const $$createField3_0 = $$createType4; + const $$createField11_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Titles" in $$parsedSource) { + $$parsedSource["Titles"] = $$createField0_0($$parsedSource["Titles"]); + } + if ("Names" in $$parsedSource) { + $$parsedSource["Names"] = $$createField1_0($$parsedSource["Names"]); + } + if ("Partner" in $$parsedSource) { + $$parsedSource["Partner"] = $$createField2_0($$parsedSource["Partner"]); + } + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField3_0($$parsedSource["Friends"]); + } + if ("emb4" in $$parsedSource) { + $$parsedSource["emb4"] = $$createField11_0($$parsedSource["emb4"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +export class embedded4 { + /** + * Creates a new embedded4 instance. + * @param {Partial} [$$source = {}] - The source object to create the embedded4. + */ + constructor($$source = {}) { + if (!("NamingThingsIsHard" in $$source)) { + /** + * NamingThingsIsHard is a law of programming + * @member + * @type {`${boolean}`} + */ + this["NamingThingsIsHard"] = "false"; + } + if (!("Friends" in $$source)) { + /** + * Friends should not be shadowed in Person as embedded4 is not embedded + * from encoding/json's point of view; + * however, it should be shadowed in Embedded1 + * @member + * @type {boolean} + */ + this["Friends"] = false; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new embedded4 instance from a string or object. + * @param {any} [$$source = {}] + * @returns {embedded4} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new embedded4(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = Person.createFrom; +const $$createType3 = $Create.Nullable($$createType2); +const $$createType4 = $Create.Array($$createType3); +const $$createType5 = embedded4.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js new file mode 100644 index 000000000..35fac10f2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + * @param {string} str + * @param {$models.Person[]} people + * @param {{"AnotherCount": number, "AnotherOne": $models.Person | null}} $2 + * @param {{ [_: `${number}`]: boolean | null }} assoc + * @param {(number | null)[]} $4 + * @param {string[]} other + * @returns {$CancellablePromise<[$models.Person, any, number[]]>} + */ +export function Greet(str, people, $2, assoc, $4, ...other) { + return $Call.ByID(1411160069, str, people, $2, assoc, $4, other).then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[2] = $$createType1($result[2]); + return $result; + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Array($Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js new file mode 100644 index 000000000..82af81baf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js @@ -0,0 +1,38 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Person represents a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person(/** @type {Partial} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js new file mode 100644 index 000000000..29255dd9c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.StructA, $models.StructC]>} + */ +export function MakeCycles() { + return $Call.ByID(440020721).then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + return $result; + })); +} + +// Private type creation functions +const $$createType0 = $models.StructA.createFrom; +const $$createType1 = $models.StructC.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js new file mode 100644 index 000000000..0ad0efb4e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + StructA, + StructC, + StructE +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js new file mode 100644 index 000000000..f24f5a2c9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js @@ -0,0 +1,164 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class StructA { + /** + * Creates a new StructA instance. + * @param {Partial} [$$source = {}] - The source object to create the StructA. + */ + constructor($$source = {}) { + if (!("B" in $$source)) { + /** + * @member + * @type {structB | null} + */ + this["B"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructA instance from a string or object. + * @param {any} [$$source = {}] + * @returns {StructA} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("B" in $$parsedSource) { + $$parsedSource["B"] = $$createField0_0($$parsedSource["B"]); + } + return new StructA(/** @type {Partial} */($$parsedSource)); + } +} + +export class StructC { + /** + * Creates a new StructC instance. + * @param {Partial} [$$source = {}] - The source object to create the StructC. + */ + constructor($$source = {}) { + if (!("D" in $$source)) { + /** + * @member + * @type {structD} + */ + this["D"] = (new structD()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructC instance from a string or object. + * @param {any} [$$source = {}] + * @returns {StructC} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType2; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("D" in $$parsedSource) { + $$parsedSource["D"] = $$createField0_0($$parsedSource["D"]); + } + return new StructC(/** @type {Partial} */($$parsedSource)); + } +} + +export class StructE { + /** + * Creates a new StructE instance. + * @param {Partial} [$$source = {}] - The source object to create the StructE. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new StructE instance from a string or object. + * @param {any} [$$source = {}] + * @returns {StructE} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new StructE(/** @type {Partial} */($$parsedSource)); + } +} + +export class structB { + /** + * Creates a new structB instance. + * @param {Partial} [$$source = {}] - The source object to create the structB. + */ + constructor($$source = {}) { + if (!("A" in $$source)) { + /** + * @member + * @type {StructA | null} + */ + this["A"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structB instance from a string or object. + * @param {any} [$$source = {}] + * @returns {structB} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType4; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField0_0($$parsedSource["A"]); + } + return new structB(/** @type {Partial} */($$parsedSource)); + } +} + +export class structD { + /** + * Creates a new structD instance. + * @param {Partial} [$$source = {}] - The source object to create the structD. + */ + constructor($$source = {}) { + if (!("E" in $$source)) { + /** + * @member + * @type {StructE} + */ + this["E"] = (new StructE()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structD instance from a string or object. + * @param {any} [$$source = {}] + * @returns {structD} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("E" in $$parsedSource) { + $$parsedSource["E"] = $$createField0_0($$parsedSource["E"]); + } + return new structD(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = structB.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = structD.createFrom; +const $$createType3 = StructA.createFrom; +const $$createType4 = $Create.Nullable($$createType3); +const $$createType5 = StructE.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js new file mode 100644 index 000000000..faf090884 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js @@ -0,0 +1,65 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]>} + */ +export function MakeCycles() { + return $Call.ByID(440020721).then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType9($result[1]); + return $result; + })); +} + +// Private type creation functions +var $$createType0 = /** @type {(...args: any[]) => any} */(function $$initCreateType0(...args) { + if ($$createType0 === $$initCreateType0) { + $$createType0 = $$createType3; + } + return $$createType0(...args); +}); +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = $Create.Map($Create.Any, $$createType1); +const $$createType3 = $Create.Array($$createType2); +var $$createType4 = /** @type {(...args: any[]) => any} */(function $$initCreateType4(...args) { + if ($$createType4 === $$initCreateType4) { + $$createType4 = $$createType8; + } + return $$createType4(...args); +}); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Array($Create.Any); +const $$createType7 = $Create.Struct({ + "X": $$createType5, + "Y": $$createType6, +}); +const $$createType8 = $Create.Array($$createType7); +var $$createType9 = /** @type {(...args: any[]) => any} */(function $$initCreateType9(...args) { + if ($$createType9 === $$initCreateType9) { + $$createType9 = $$createType13; + } + return $$createType9(...args); +}); +const $$createType10 = $Create.Nullable($$createType9); +const $$createType11 = $Create.Array($$createType4); +const $$createType12 = $Create.Struct({ + "X": $$createType10, + "Y": $$createType11, +}); +const $$createType13 = $Create.Array($$createType12); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js new file mode 100644 index 000000000..9fc31bf7c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Alias} Alias + */ + +/** + * @typedef {$models.Cyclic} Cyclic + */ + +/** + * @template T + * @typedef {$models.GenericCyclic} GenericCyclic + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js new file mode 100644 index 000000000..47d41f572 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * @typedef {Cyclic | null} Alias + */ + +/** + * @typedef {{ [_: string]: Alias }[]} Cyclic + */ + +/** + * @template T + * @typedef {{"X": GenericCyclic | null, "Y": T[]}[]} GenericCyclic + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js new file mode 100644 index 000000000..972196ce3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Classes!"); +console.log("Hello JS!"); +console.log("Hello JS Classes!"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js new file mode 100644 index 000000000..fce17fb1d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.InternalModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByID(538079117, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js new file mode 100644 index 000000000..291a3cecf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js @@ -0,0 +1,69 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * An exported but internal model. + */ +export class InternalModel { + /** + * Creates a new InternalModel instance. + * @param {Partial} [$$source = {}] - The source object to create the InternalModel. + */ + constructor($$source = {}) { + if (!("Field" in $$source)) { + /** + * @member + * @type {string} + */ + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new InternalModel instance from a string or object. + * @param {any} [$$source = {}] + * @returns {InternalModel} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new InternalModel(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * An unexported model. + */ +export class unexportedModel { + /** + * Creates a new unexportedModel instance. + * @param {Partial} [$$source = {}] - The source object to create the unexportedModel. + */ + constructor($$source = {}) { + if (!("Field" in $$source)) { + /** + * @member + * @type {string} + */ + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new unexportedModel instance from a string or object. + * @param {any} [$$source = {}] + * @returns {unexportedModel} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new unexportedModel(/** @type {Partial} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js new file mode 100644 index 000000000..b7e83cfd4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Dummy +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js new file mode 100644 index 000000000..274f4eed4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js @@ -0,0 +1,28 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Dummy { + /** + * Creates a new Dummy instance. + * @param {Partial} [$$source = {}] - The source object to create the Dummy. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new Dummy instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Dummy} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Dummy(/** @type {Partial} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_j.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_j.js new file mode 100644 index 000000000..2166d33b6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_jc.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_jc.js new file mode 100644 index 000000000..338898726 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_jc.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("JS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js new file mode 100644 index 000000000..cc7ed89d3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js @@ -0,0 +1,35 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +/** + * @param {string} $0 + * @returns {$CancellablePromise} + */ +function InternalMethod($0) { + return $Call.ByID(3518775569, $0); +} + +/** + * @param {otherpackage$0.Dummy} $0 + * @returns {$CancellablePromise} + */ +export function VisibleMethod($0) { + return $Call.ByID(474018228, $0); +} + +/** + * @param {string} arg + * @returns {Promise} + */ +export async function CustomMethod(arg) { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js new file mode 100644 index 000000000..724e79e12 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Classes"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js new file mode 100644 index 000000000..b2f9c5edb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_jc.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_jc.js new file mode 100644 index 000000000..ddf4920e5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_jc.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js new file mode 100644 index 000000000..d3f53be87 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.unexportedModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByID(37626172, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js new file mode 100644 index 000000000..a178744b9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js @@ -0,0 +1,53 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Comment 1. + * @returns {$CancellablePromise} + */ +export function Method1() { + return $Call.ByID(841558284); +} + +/** + * Comment 2. + * @returns {$CancellablePromise} + */ +export function Method2() { + return $Call.ByID(891891141); +} + +/** + * Comment 3a. + * Comment 3b. + * @returns {$CancellablePromise} + */ +export function Method3() { + return $Call.ByID(875113522); +} + +/** + * Comment 4. + * @returns {$CancellablePromise} + */ +export function Method4() { + return $Call.ByID(791225427); +} + +/** + * Comment 5. + * @returns {$CancellablePromise} + */ +export function Method5() { + return $Call.ByID(774447808); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js new file mode 100644 index 000000000..ed402a8b2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js @@ -0,0 +1,41 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {$models.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByID(1411160069, name, title); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js new file mode 100644 index 000000000..d5d66d4cb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Person, + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js new file mode 100644 index 000000000..2c5df9ee7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js @@ -0,0 +1,98 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Age is an integer with some predefined values + * @typedef {number} Age + */ + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Title" in $$source)) { + /** + * @member + * @type {Title} + */ + this["Title"] = Title.$zero; + } + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Age" in $$source)) { + /** + * @member + * @type {Age} + */ + this["Age"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js new file mode 100644 index 000000000..fbc2294e9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {services$0.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByID(1411160069, name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js new file mode 100644 index 000000000..089a8b685 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js new file mode 100644 index 000000000..65ebfa2f7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js @@ -0,0 +1,27 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.js new file mode 100644 index 000000000..ebcbd137f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + SomeClass +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.js new file mode 100644 index 000000000..498ff20b5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.js @@ -0,0 +1,56 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * SomeClass renders as a TS class. + */ +export class SomeClass { + /** + * Creates a new SomeClass instance. + * @param {Partial} [$$source = {}] - The source object to create the SomeClass. + */ + constructor($$source = {}) { + if (!("Field" in $$source)) { + /** + * @member + * @type {string} + */ + this["Field"] = ""; + } + if (!("Meadow" in $$source)) { + /** + * @member + * @type {nobindingshere$0.HowDifferent} + */ + this["Meadow"] = (new nobindingshere$0.HowDifferent()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new SomeClass instance from a string or object. + * @param {any} [$$source = {}] + * @returns {SomeClass} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Meadow" in $$parsedSource) { + $$parsedSource["Meadow"] = $$createField1_0($$parsedSource["Meadow"]); + } + return new SomeClass(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = nobindingshere$0.HowDifferent.createFrom($Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js new file mode 100644 index 000000000..50737a34b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js new file mode 100644 index 000000000..0ab295133 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js @@ -0,0 +1,57 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {services$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
        } [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
        } */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js new file mode 100644 index 000000000..1866aca09 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(2007737399).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js new file mode 100644 index 000000000..50737a34b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js new file mode 100644 index 000000000..29a95e11e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js @@ -0,0 +1,54 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {other$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = other$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
        } [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
        } */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js new file mode 100644 index 000000000..293a2f0bb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(2447353446).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js new file mode 100644 index 000000000..e50a4a6ab --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js new file mode 100644 index 000000000..9dfe48511 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js new file mode 100644 index 000000000..1bc7cb45b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js @@ -0,0 +1,30 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function GreetWithContext(name) { + return $Call.ByID(1310150960, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js new file mode 100644 index 000000000..9dfe48511 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js new file mode 100644 index 000000000..9fd9745c1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js @@ -0,0 +1,97 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Maps +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * @template S + * @typedef {$models.BasicCstrAlias} BasicCstrAlias + */ + +/** + * @template R + * @typedef {$models.ComparableCstrAlias} ComparableCstrAlias + */ + +/** + * @typedef {$models.EmbeddedCustomInterface} EmbeddedCustomInterface + */ + +/** + * @typedef {$models.EmbeddedOriginalInterface} EmbeddedOriginalInterface + */ + +/** + * @typedef {$models.EmbeddedPointer} EmbeddedPointer + */ + +/** + * @typedef {$models.EmbeddedPointerPtr} EmbeddedPointerPtr + */ + +/** + * @typedef {$models.EmbeddedValue} EmbeddedValue + */ + +/** + * @typedef {$models.EmbeddedValuePtr} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {$models.GoodTildeCstrAlias} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {$models.InterfaceCstrAlias} InterfaceCstrAlias + */ + +/** + * @template X + * @typedef {$models.MixedCstrAlias} MixedCstrAlias + */ + +/** + * @template V + * @typedef {$models.NonBasicCstrAlias} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {$models.PointableCstrAlias} PointableCstrAlias + */ + +/** + * @typedef {$models.PointerAlias} PointerAlias + */ + +/** + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * @typedef {$models.StringAlias} StringAlias + */ + +/** + * @typedef {$models.StringType} StringType + */ + +/** + * @typedef {$models.ValueAlias} ValueAlias + */ + +/** + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js new file mode 100644 index 000000000..1dc5bfc38 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js @@ -0,0 +1,1957 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * @template S + * @typedef {S} BasicCstrAlias + */ + +/** + * @template R + * @typedef {R} ComparableCstrAlias + */ + +/** + * @typedef {string} EmbeddedCustomInterface + */ + +/** + * @typedef {string} EmbeddedOriginalInterface + */ + +/** + * @typedef {string} EmbeddedPointer + */ + +/** + * @typedef {string} EmbeddedPointerPtr + */ + +/** + * @typedef {string} EmbeddedValue + */ + +/** + * @typedef {string} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {U} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {Y} InterfaceCstrAlias + */ + +/** + * @template R,S,T,U,V,W,X,Y,Z + */ +export class Maps { + /** + * Creates a new Maps instance. + * @param {Partial>} [$$source = {}] - The source object to create the Maps. + */ + constructor($$source = {}) { + if (!("Bool" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["Bool"] = {}; + } + if (!("Int" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Int"] = {}; + } + if (!("Uint" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Uint"] = {}; + } + if (!("Float" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["Float"] = {}; + } + if (!("Complex" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["Complex"] = {}; + } + if (!("Byte" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Byte"] = {}; + } + if (!("Rune" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Rune"] = {}; + } + if (!("String" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: string]: number }} + */ + this["String"] = {}; + } + if (!("IntPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IntPtr"] = {}; + } + if (!("UintPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["UintPtr"] = {}; + } + if (!("FloatPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["FloatPtr"] = {}; + } + if (!("ComplexPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["ComplexPtr"] = {}; + } + if (!("StringPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["StringPtr"] = {}; + } + if (!("NTM" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["NTM"] = {}; + } + if (!("NTMPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["NTMPtr"] = {}; + } + if (!("VTM" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueTextMarshaler]: number }} + */ + this["VTM"] = {}; + } + if (!("VTMPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueTextMarshaler]: number }} + */ + this["VTMPtr"] = {}; + } + if (!("PTM" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PTM"] = {}; + } + if (!("PTMPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointerTextMarshaler]: number }} + */ + this["PTMPtr"] = {}; + } + if (!("JTM" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["JTM"] = {}; + } + if (!("JTMPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["JTMPtr"] = {}; + } + if (!("A" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["A"] = {}; + } + if (!("APtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["APtr"] = {}; + } + if (!("TM" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TM"] = {}; + } + if (!("TMPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["TMPtr"] = {}; + } + if (!("CI" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["CI"] = {}; + } + if (!("CIPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["CIPtr"] = {}; + } + if (!("EI" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["EI"] = {}; + } + if (!("EIPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["EIPtr"] = {}; + } + if (!("EV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValue]: number }} + */ + this["EV"] = {}; + } + if (!("EVPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValue]: number }} + */ + this["EVPtr"] = {}; + } + if (!("EVP" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValuePtr]: number }} + */ + this["EVP"] = {}; + } + if (!("EVPPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValuePtr]: number }} + */ + this["EVPPtr"] = {}; + } + if (!("EP" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["EP"] = {}; + } + if (!("EPPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedPointer]: number }} + */ + this["EPPtr"] = {}; + } + if (!("EPP" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedPointerPtr]: number }} + */ + this["EPP"] = {}; + } + if (!("EPPPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedPointerPtr]: number }} + */ + this["EPPPtr"] = {}; + } + if (!("ECI" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedCustomInterface]: number }} + */ + this["ECI"] = {}; + } + if (!("ECIPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedCustomInterface]: number }} + */ + this["ECIPtr"] = {}; + } + if (!("EOI" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedOriginalInterface]: number }} + */ + this["EOI"] = {}; + } + if (!("EOIPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedOriginalInterface]: number }} + */ + this["EOIPtr"] = {}; + } + if (!("WT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["WT"] = {}; + } + if (!("WA" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["WA"] = {}; + } + if (!("ST" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: StringType]: number }} + */ + this["ST"] = {}; + } + if (!("SA" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: StringAlias]: number }} + */ + this["SA"] = {}; + } + if (!("IntT" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["IntT"] = {}; + } + if (!("IntA" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["IntA"] = {}; + } + if (!("VT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VT"] = {}; + } + if (!("VTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VTPtr"] = {}; + } + if (!("VPT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VPT"] = {}; + } + if (!("VPTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VPTPtr"] = {}; + } + if (!("VA" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueAlias]: number }} + */ + this["VA"] = {}; + } + if (!("VAPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueAlias]: number }} + */ + this["VAPtr"] = {}; + } + if (!("VPA" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["VPA"] = {}; + } + if (!("VPAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VPAPtr"] = {}; + } + if (!("PT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PT"] = {}; + } + if (!("PTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PTPtr"] = {}; + } + if (!("PPT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PPT"] = {}; + } + if (!("PPTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PPTPtr"] = {}; + } + if (!("PA" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PA"] = {}; + } + if (!("PAPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointerAlias]: number }} + */ + this["PAPtr"] = {}; + } + if (!("PPA" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["PPA"] = {}; + } + if (!("PPAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PPAPtr"] = {}; + } + if (!("IT" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["IT"] = {}; + } + if (!("ITPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["ITPtr"] = {}; + } + if (!("IPT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPT"] = {}; + } + if (!("IPTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPTPtr"] = {}; + } + if (!("IA" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["IA"] = {}; + } + if (!("IAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IAPtr"] = {}; + } + if (!("IPA" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPA"] = {}; + } + if (!("IPAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPAPtr"] = {}; + } + if (!("TPR" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPR"] = {}; + } + if (!("TPRPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPRPtr"] = {}; + } + if (!("TPS" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPS"] = {}; + } + if (!("TPSPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPSPtr"] = {}; + } + if (!("TPT" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPT"] = {}; + } + if (!("TPTPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPTPtr"] = {}; + } + if (!("TPU" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPU"] = {}; + } + if (!("TPUPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPUPtr"] = {}; + } + if (!("TPV" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPV"] = {}; + } + if (!("TPVPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPVPtr"] = {}; + } + if (!("TPW" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPW"] = {}; + } + if (!("TPWPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPWPtr"] = {}; + } + if (!("TPX" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPX"] = {}; + } + if (!("TPXPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPXPtr"] = {}; + } + if (!("TPY" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPY"] = {}; + } + if (!("TPYPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPYPtr"] = {}; + } + if (!("TPZ" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPZ"] = {}; + } + if (!("TPZPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPZPtr"] = {}; + } + if (!("GAR" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAR"] = {}; + } + if (!("GARPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GARPtr"] = {}; + } + if (!("GAS" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAS"] = {}; + } + if (!("GASPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GASPtr"] = {}; + } + if (!("GAT" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAT"] = {}; + } + if (!("GATPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GATPtr"] = {}; + } + if (!("GAU" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAU"] = {}; + } + if (!("GAUPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAUPtr"] = {}; + } + if (!("GAV" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAV"] = {}; + } + if (!("GAVPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAVPtr"] = {}; + } + if (!("GAW" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAW"] = {}; + } + if (!("GAWPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAWPtr"] = {}; + } + if (!("GAX" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAX"] = {}; + } + if (!("GAXPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAXPtr"] = {}; + } + if (!("GAY" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAY"] = {}; + } + if (!("GAYPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAYPtr"] = {}; + } + if (!("GAZ" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAZ"] = {}; + } + if (!("GAZPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAZPtr"] = {}; + } + if (!("GACi" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["GACi"] = {}; + } + if (!("GACV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ComparableCstrAlias]: number }} + */ + this["GACV"] = {}; + } + if (!("GACP" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GACP"] = {}; + } + if (!("GACiPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GACiPtr"] = {}; + } + if (!("GACVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GACVPtr"] = {}; + } + if (!("GACPPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GACPPtr"] = {}; + } + if (!("GABi" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["GABi"] = {}; + } + if (!("GABs" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: BasicCstrAlias]: number }} + */ + this["GABs"] = {}; + } + if (!("GABiPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GABiPtr"] = {}; + } + if (!("GABT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GABT"] = {}; + } + if (!("GABTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GABTPtr"] = {}; + } + if (!("GAGT" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: GoodTildeCstrAlias]: number }} + */ + this["GAGT"] = {}; + } + if (!("GAGTPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAGTPtr"] = {}; + } + if (!("GANBV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: NonBasicCstrAlias]: number }} + */ + this["GANBV"] = {}; + } + if (!("GANBP" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GANBP"] = {}; + } + if (!("GANBVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GANBVPtr"] = {}; + } + if (!("GANBPPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GANBPPtr"] = {}; + } + if (!("GAPlV1" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointableCstrAlias]: number }} + */ + this["GAPlV1"] = {}; + } + if (!("GAPlV2" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointableCstrAlias]: number }} + */ + this["GAPlV2"] = {}; + } + if (!("GAPlP1" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAPlP1"] = {}; + } + if (!("GAPlP2" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointableCstrAlias]: number }} + */ + this["GAPlP2"] = {}; + } + if (!("GAPlVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPlVPtr"] = {}; + } + if (!("GAPlPPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPlPPtr"] = {}; + } + if (!("GAMi" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["GAMi"] = {}; + } + if (!("GAMS" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: MixedCstrAlias]: number }} + */ + this["GAMS"] = {}; + } + if (!("GAMV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: MixedCstrAlias]: number }} + */ + this["GAMV"] = {}; + } + if (!("GAMSPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAMSPtr"] = {}; + } + if (!("GAMVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAMVPtr"] = {}; + } + if (!("GAII" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAII"] = {}; + } + if (!("GAIV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: InterfaceCstrAlias]: number }} + */ + this["GAIV"] = {}; + } + if (!("GAIP" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAIP"] = {}; + } + if (!("GAIIPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAIIPtr"] = {}; + } + if (!("GAIVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAIVPtr"] = {}; + } + if (!("GAIPPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAIPPtr"] = {}; + } + if (!("GAPrV" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrV"] = {}; + } + if (!("GAPrP" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrP"] = {}; + } + if (!("GAPrVPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrVPtr"] = {}; + } + if (!("GAPrPPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrPPtr"] = {}; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class Maps. + * @template [R=any] + * @template [S=any] + * @template [T=any] + * @template [U=any] + * @template [V=any] + * @template [W=any] + * @template [X=any] + * @template [Y=any] + * @template [Z=any] + * @param {(source: any) => R} $$createParamR + * @param {(source: any) => S} $$createParamS + * @param {(source: any) => T} $$createParamT + * @param {(source: any) => U} $$createParamU + * @param {(source: any) => V} $$createParamV + * @param {(source: any) => W} $$createParamW + * @param {(source: any) => X} $$createParamX + * @param {(source: any) => Y} $$createParamY + * @param {(source: any) => Z} $$createParamZ + * @returns {($$source?: any) => Maps} + */ + static createFrom($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType3; + const $$createField4_0 = $$createType4; + const $$createField5_0 = $$createType5; + const $$createField6_0 = $$createType6; + const $$createField7_0 = $$createType7; + const $$createField8_0 = $$createType8; + const $$createField9_0 = $$createType9; + const $$createField10_0 = $$createType10; + const $$createField11_0 = $$createType11; + const $$createField12_0 = $$createType12; + const $$createField13_0 = $$createType13; + const $$createField14_0 = $$createType14; + const $$createField15_0 = $$createType15; + const $$createField16_0 = $$createType16; + const $$createField17_0 = $$createType17; + const $$createField18_0 = $$createType18; + const $$createField19_0 = $$createType19; + const $$createField20_0 = $$createType20; + const $$createField21_0 = $$createType21; + const $$createField22_0 = $$createType22; + const $$createField23_0 = $$createType23; + const $$createField24_0 = $$createType24; + const $$createField25_0 = $$createType25; + const $$createField26_0 = $$createType26; + const $$createField27_0 = $$createType27; + const $$createField28_0 = $$createType28; + const $$createField29_0 = $$createType29; + const $$createField30_0 = $$createType30; + const $$createField31_0 = $$createType31; + const $$createField32_0 = $$createType32; + const $$createField33_0 = $$createType33; + const $$createField34_0 = $$createType34; + const $$createField35_0 = $$createType35; + const $$createField36_0 = $$createType36; + const $$createField37_0 = $$createType37; + const $$createField38_0 = $$createType38; + const $$createField39_0 = $$createType39; + const $$createField40_0 = $$createType40; + const $$createField41_0 = $$createType41; + const $$createField42_0 = $$createType0; + const $$createField43_0 = $$createType42; + const $$createField44_0 = $$createType7; + const $$createField45_0 = $$createType43; + const $$createField46_0 = $$createType1; + const $$createField47_0 = $$createType44; + const $$createField48_0 = $$createType45; + const $$createField49_0 = $$createType46; + const $$createField50_0 = $$createType47; + const $$createField51_0 = $$createType15; + const $$createField52_0 = $$createType16; + const $$createField53_0 = $$createType16; + const $$createField54_0 = $$createType48; + const $$createField55_0 = $$createType49; + const $$createField56_0 = $$createType50; + const $$createField57_0 = $$createType51; + const $$createField58_0 = $$createType52; + const $$createField59_0 = $$createType17; + const $$createField60_0 = $$createType18; + const $$createField61_0 = $$createType18; + const $$createField62_0 = $$createType53; + const $$createField63_0 = $$createType54; + const $$createField64_0 = $$createType55; + const $$createField65_0 = $$createType56; + const $$createField66_0 = $$createType57; + const $$createField67_0 = $$createType23; + const $$createField68_0 = $$createType24; + const $$createField69_0 = $$createType24; + const $$createField70_0 = $$createType58; + const $$createField71_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField72_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField73_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField74_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField75_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField76_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField77_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField78_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField79_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField80_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField81_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField82_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField83_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField84_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField85_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField86_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField87_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField88_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField89_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField90_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField91_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField92_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField93_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField94_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField95_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField96_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField97_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField98_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField99_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField100_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField101_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField102_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField103_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField104_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField105_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField106_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField107_0 = $$createType1; + const $$createField108_0 = $$createType15; + const $$createField109_0 = $$createType17; + const $$createField110_0 = $$createType8; + const $$createField111_0 = $$createType16; + const $$createField112_0 = $$createType18; + const $$createField113_0 = $$createType1; + const $$createField114_0 = $$createType7; + const $$createField115_0 = $$createType8; + const $$createField116_0 = $$createType77; + const $$createField117_0 = $$createType78; + const $$createField118_0 = $$createType15; + const $$createField119_0 = $$createType16; + const $$createField120_0 = $$createType15; + const $$createField121_0 = $$createType18; + const $$createField122_0 = $$createType16; + const $$createField123_0 = $$createType53; + const $$createField124_0 = $$createType15; + const $$createField125_0 = $$createType16; + const $$createField126_0 = $$createType17; + const $$createField127_0 = $$createType18; + const $$createField128_0 = $$createType16; + const $$createField129_0 = $$createType18; + const $$createField130_0 = $$createType2; + const $$createField131_0 = $$createType42; + const $$createField132_0 = $$createType15; + const $$createField133_0 = $$createType79; + const $$createField134_0 = $$createType16; + const $$createField135_0 = $$createType23; + const $$createField136_0 = $$createType15; + const $$createField137_0 = $$createType18; + const $$createField138_0 = $$createType24; + const $$createField139_0 = $$createType16; + const $$createField140_0 = $$createType53; + const $$createField141_0 = $$createType16; + const $$createField142_0 = $$createType18; + const $$createField143_0 = $$createType48; + const $$createField144_0 = $$createType53; + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Bool" in $$parsedSource) { + $$parsedSource["Bool"] = $$createField0_0($$parsedSource["Bool"]); + } + if ("Int" in $$parsedSource) { + $$parsedSource["Int"] = $$createField1_0($$parsedSource["Int"]); + } + if ("Uint" in $$parsedSource) { + $$parsedSource["Uint"] = $$createField2_0($$parsedSource["Uint"]); + } + if ("Float" in $$parsedSource) { + $$parsedSource["Float"] = $$createField3_0($$parsedSource["Float"]); + } + if ("Complex" in $$parsedSource) { + $$parsedSource["Complex"] = $$createField4_0($$parsedSource["Complex"]); + } + if ("Byte" in $$parsedSource) { + $$parsedSource["Byte"] = $$createField5_0($$parsedSource["Byte"]); + } + if ("Rune" in $$parsedSource) { + $$parsedSource["Rune"] = $$createField6_0($$parsedSource["Rune"]); + } + if ("String" in $$parsedSource) { + $$parsedSource["String"] = $$createField7_0($$parsedSource["String"]); + } + if ("IntPtr" in $$parsedSource) { + $$parsedSource["IntPtr"] = $$createField8_0($$parsedSource["IntPtr"]); + } + if ("UintPtr" in $$parsedSource) { + $$parsedSource["UintPtr"] = $$createField9_0($$parsedSource["UintPtr"]); + } + if ("FloatPtr" in $$parsedSource) { + $$parsedSource["FloatPtr"] = $$createField10_0($$parsedSource["FloatPtr"]); + } + if ("ComplexPtr" in $$parsedSource) { + $$parsedSource["ComplexPtr"] = $$createField11_0($$parsedSource["ComplexPtr"]); + } + if ("StringPtr" in $$parsedSource) { + $$parsedSource["StringPtr"] = $$createField12_0($$parsedSource["StringPtr"]); + } + if ("NTM" in $$parsedSource) { + $$parsedSource["NTM"] = $$createField13_0($$parsedSource["NTM"]); + } + if ("NTMPtr" in $$parsedSource) { + $$parsedSource["NTMPtr"] = $$createField14_0($$parsedSource["NTMPtr"]); + } + if ("VTM" in $$parsedSource) { + $$parsedSource["VTM"] = $$createField15_0($$parsedSource["VTM"]); + } + if ("VTMPtr" in $$parsedSource) { + $$parsedSource["VTMPtr"] = $$createField16_0($$parsedSource["VTMPtr"]); + } + if ("PTM" in $$parsedSource) { + $$parsedSource["PTM"] = $$createField17_0($$parsedSource["PTM"]); + } + if ("PTMPtr" in $$parsedSource) { + $$parsedSource["PTMPtr"] = $$createField18_0($$parsedSource["PTMPtr"]); + } + if ("JTM" in $$parsedSource) { + $$parsedSource["JTM"] = $$createField19_0($$parsedSource["JTM"]); + } + if ("JTMPtr" in $$parsedSource) { + $$parsedSource["JTMPtr"] = $$createField20_0($$parsedSource["JTMPtr"]); + } + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField21_0($$parsedSource["A"]); + } + if ("APtr" in $$parsedSource) { + $$parsedSource["APtr"] = $$createField22_0($$parsedSource["APtr"]); + } + if ("TM" in $$parsedSource) { + $$parsedSource["TM"] = $$createField23_0($$parsedSource["TM"]); + } + if ("TMPtr" in $$parsedSource) { + $$parsedSource["TMPtr"] = $$createField24_0($$parsedSource["TMPtr"]); + } + if ("CI" in $$parsedSource) { + $$parsedSource["CI"] = $$createField25_0($$parsedSource["CI"]); + } + if ("CIPtr" in $$parsedSource) { + $$parsedSource["CIPtr"] = $$createField26_0($$parsedSource["CIPtr"]); + } + if ("EI" in $$parsedSource) { + $$parsedSource["EI"] = $$createField27_0($$parsedSource["EI"]); + } + if ("EIPtr" in $$parsedSource) { + $$parsedSource["EIPtr"] = $$createField28_0($$parsedSource["EIPtr"]); + } + if ("EV" in $$parsedSource) { + $$parsedSource["EV"] = $$createField29_0($$parsedSource["EV"]); + } + if ("EVPtr" in $$parsedSource) { + $$parsedSource["EVPtr"] = $$createField30_0($$parsedSource["EVPtr"]); + } + if ("EVP" in $$parsedSource) { + $$parsedSource["EVP"] = $$createField31_0($$parsedSource["EVP"]); + } + if ("EVPPtr" in $$parsedSource) { + $$parsedSource["EVPPtr"] = $$createField32_0($$parsedSource["EVPPtr"]); + } + if ("EP" in $$parsedSource) { + $$parsedSource["EP"] = $$createField33_0($$parsedSource["EP"]); + } + if ("EPPtr" in $$parsedSource) { + $$parsedSource["EPPtr"] = $$createField34_0($$parsedSource["EPPtr"]); + } + if ("EPP" in $$parsedSource) { + $$parsedSource["EPP"] = $$createField35_0($$parsedSource["EPP"]); + } + if ("EPPPtr" in $$parsedSource) { + $$parsedSource["EPPPtr"] = $$createField36_0($$parsedSource["EPPPtr"]); + } + if ("ECI" in $$parsedSource) { + $$parsedSource["ECI"] = $$createField37_0($$parsedSource["ECI"]); + } + if ("ECIPtr" in $$parsedSource) { + $$parsedSource["ECIPtr"] = $$createField38_0($$parsedSource["ECIPtr"]); + } + if ("EOI" in $$parsedSource) { + $$parsedSource["EOI"] = $$createField39_0($$parsedSource["EOI"]); + } + if ("EOIPtr" in $$parsedSource) { + $$parsedSource["EOIPtr"] = $$createField40_0($$parsedSource["EOIPtr"]); + } + if ("WT" in $$parsedSource) { + $$parsedSource["WT"] = $$createField41_0($$parsedSource["WT"]); + } + if ("WA" in $$parsedSource) { + $$parsedSource["WA"] = $$createField42_0($$parsedSource["WA"]); + } + if ("ST" in $$parsedSource) { + $$parsedSource["ST"] = $$createField43_0($$parsedSource["ST"]); + } + if ("SA" in $$parsedSource) { + $$parsedSource["SA"] = $$createField44_0($$parsedSource["SA"]); + } + if ("IntT" in $$parsedSource) { + $$parsedSource["IntT"] = $$createField45_0($$parsedSource["IntT"]); + } + if ("IntA" in $$parsedSource) { + $$parsedSource["IntA"] = $$createField46_0($$parsedSource["IntA"]); + } + if ("VT" in $$parsedSource) { + $$parsedSource["VT"] = $$createField47_0($$parsedSource["VT"]); + } + if ("VTPtr" in $$parsedSource) { + $$parsedSource["VTPtr"] = $$createField48_0($$parsedSource["VTPtr"]); + } + if ("VPT" in $$parsedSource) { + $$parsedSource["VPT"] = $$createField49_0($$parsedSource["VPT"]); + } + if ("VPTPtr" in $$parsedSource) { + $$parsedSource["VPTPtr"] = $$createField50_0($$parsedSource["VPTPtr"]); + } + if ("VA" in $$parsedSource) { + $$parsedSource["VA"] = $$createField51_0($$parsedSource["VA"]); + } + if ("VAPtr" in $$parsedSource) { + $$parsedSource["VAPtr"] = $$createField52_0($$parsedSource["VAPtr"]); + } + if ("VPA" in $$parsedSource) { + $$parsedSource["VPA"] = $$createField53_0($$parsedSource["VPA"]); + } + if ("VPAPtr" in $$parsedSource) { + $$parsedSource["VPAPtr"] = $$createField54_0($$parsedSource["VPAPtr"]); + } + if ("PT" in $$parsedSource) { + $$parsedSource["PT"] = $$createField55_0($$parsedSource["PT"]); + } + if ("PTPtr" in $$parsedSource) { + $$parsedSource["PTPtr"] = $$createField56_0($$parsedSource["PTPtr"]); + } + if ("PPT" in $$parsedSource) { + $$parsedSource["PPT"] = $$createField57_0($$parsedSource["PPT"]); + } + if ("PPTPtr" in $$parsedSource) { + $$parsedSource["PPTPtr"] = $$createField58_0($$parsedSource["PPTPtr"]); + } + if ("PA" in $$parsedSource) { + $$parsedSource["PA"] = $$createField59_0($$parsedSource["PA"]); + } + if ("PAPtr" in $$parsedSource) { + $$parsedSource["PAPtr"] = $$createField60_0($$parsedSource["PAPtr"]); + } + if ("PPA" in $$parsedSource) { + $$parsedSource["PPA"] = $$createField61_0($$parsedSource["PPA"]); + } + if ("PPAPtr" in $$parsedSource) { + $$parsedSource["PPAPtr"] = $$createField62_0($$parsedSource["PPAPtr"]); + } + if ("IT" in $$parsedSource) { + $$parsedSource["IT"] = $$createField63_0($$parsedSource["IT"]); + } + if ("ITPtr" in $$parsedSource) { + $$parsedSource["ITPtr"] = $$createField64_0($$parsedSource["ITPtr"]); + } + if ("IPT" in $$parsedSource) { + $$parsedSource["IPT"] = $$createField65_0($$parsedSource["IPT"]); + } + if ("IPTPtr" in $$parsedSource) { + $$parsedSource["IPTPtr"] = $$createField66_0($$parsedSource["IPTPtr"]); + } + if ("IA" in $$parsedSource) { + $$parsedSource["IA"] = $$createField67_0($$parsedSource["IA"]); + } + if ("IAPtr" in $$parsedSource) { + $$parsedSource["IAPtr"] = $$createField68_0($$parsedSource["IAPtr"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField69_0($$parsedSource["IPA"]); + } + if ("IPAPtr" in $$parsedSource) { + $$parsedSource["IPAPtr"] = $$createField70_0($$parsedSource["IPAPtr"]); + } + if ("TPR" in $$parsedSource) { + $$parsedSource["TPR"] = $$createField71_0($$parsedSource["TPR"]); + } + if ("TPRPtr" in $$parsedSource) { + $$parsedSource["TPRPtr"] = $$createField72_0($$parsedSource["TPRPtr"]); + } + if ("TPS" in $$parsedSource) { + $$parsedSource["TPS"] = $$createField73_0($$parsedSource["TPS"]); + } + if ("TPSPtr" in $$parsedSource) { + $$parsedSource["TPSPtr"] = $$createField74_0($$parsedSource["TPSPtr"]); + } + if ("TPT" in $$parsedSource) { + $$parsedSource["TPT"] = $$createField75_0($$parsedSource["TPT"]); + } + if ("TPTPtr" in $$parsedSource) { + $$parsedSource["TPTPtr"] = $$createField76_0($$parsedSource["TPTPtr"]); + } + if ("TPU" in $$parsedSource) { + $$parsedSource["TPU"] = $$createField77_0($$parsedSource["TPU"]); + } + if ("TPUPtr" in $$parsedSource) { + $$parsedSource["TPUPtr"] = $$createField78_0($$parsedSource["TPUPtr"]); + } + if ("TPV" in $$parsedSource) { + $$parsedSource["TPV"] = $$createField79_0($$parsedSource["TPV"]); + } + if ("TPVPtr" in $$parsedSource) { + $$parsedSource["TPVPtr"] = $$createField80_0($$parsedSource["TPVPtr"]); + } + if ("TPW" in $$parsedSource) { + $$parsedSource["TPW"] = $$createField81_0($$parsedSource["TPW"]); + } + if ("TPWPtr" in $$parsedSource) { + $$parsedSource["TPWPtr"] = $$createField82_0($$parsedSource["TPWPtr"]); + } + if ("TPX" in $$parsedSource) { + $$parsedSource["TPX"] = $$createField83_0($$parsedSource["TPX"]); + } + if ("TPXPtr" in $$parsedSource) { + $$parsedSource["TPXPtr"] = $$createField84_0($$parsedSource["TPXPtr"]); + } + if ("TPY" in $$parsedSource) { + $$parsedSource["TPY"] = $$createField85_0($$parsedSource["TPY"]); + } + if ("TPYPtr" in $$parsedSource) { + $$parsedSource["TPYPtr"] = $$createField86_0($$parsedSource["TPYPtr"]); + } + if ("TPZ" in $$parsedSource) { + $$parsedSource["TPZ"] = $$createField87_0($$parsedSource["TPZ"]); + } + if ("TPZPtr" in $$parsedSource) { + $$parsedSource["TPZPtr"] = $$createField88_0($$parsedSource["TPZPtr"]); + } + if ("GAR" in $$parsedSource) { + $$parsedSource["GAR"] = $$createField89_0($$parsedSource["GAR"]); + } + if ("GARPtr" in $$parsedSource) { + $$parsedSource["GARPtr"] = $$createField90_0($$parsedSource["GARPtr"]); + } + if ("GAS" in $$parsedSource) { + $$parsedSource["GAS"] = $$createField91_0($$parsedSource["GAS"]); + } + if ("GASPtr" in $$parsedSource) { + $$parsedSource["GASPtr"] = $$createField92_0($$parsedSource["GASPtr"]); + } + if ("GAT" in $$parsedSource) { + $$parsedSource["GAT"] = $$createField93_0($$parsedSource["GAT"]); + } + if ("GATPtr" in $$parsedSource) { + $$parsedSource["GATPtr"] = $$createField94_0($$parsedSource["GATPtr"]); + } + if ("GAU" in $$parsedSource) { + $$parsedSource["GAU"] = $$createField95_0($$parsedSource["GAU"]); + } + if ("GAUPtr" in $$parsedSource) { + $$parsedSource["GAUPtr"] = $$createField96_0($$parsedSource["GAUPtr"]); + } + if ("GAV" in $$parsedSource) { + $$parsedSource["GAV"] = $$createField97_0($$parsedSource["GAV"]); + } + if ("GAVPtr" in $$parsedSource) { + $$parsedSource["GAVPtr"] = $$createField98_0($$parsedSource["GAVPtr"]); + } + if ("GAW" in $$parsedSource) { + $$parsedSource["GAW"] = $$createField99_0($$parsedSource["GAW"]); + } + if ("GAWPtr" in $$parsedSource) { + $$parsedSource["GAWPtr"] = $$createField100_0($$parsedSource["GAWPtr"]); + } + if ("GAX" in $$parsedSource) { + $$parsedSource["GAX"] = $$createField101_0($$parsedSource["GAX"]); + } + if ("GAXPtr" in $$parsedSource) { + $$parsedSource["GAXPtr"] = $$createField102_0($$parsedSource["GAXPtr"]); + } + if ("GAY" in $$parsedSource) { + $$parsedSource["GAY"] = $$createField103_0($$parsedSource["GAY"]); + } + if ("GAYPtr" in $$parsedSource) { + $$parsedSource["GAYPtr"] = $$createField104_0($$parsedSource["GAYPtr"]); + } + if ("GAZ" in $$parsedSource) { + $$parsedSource["GAZ"] = $$createField105_0($$parsedSource["GAZ"]); + } + if ("GAZPtr" in $$parsedSource) { + $$parsedSource["GAZPtr"] = $$createField106_0($$parsedSource["GAZPtr"]); + } + if ("GACi" in $$parsedSource) { + $$parsedSource["GACi"] = $$createField107_0($$parsedSource["GACi"]); + } + if ("GACV" in $$parsedSource) { + $$parsedSource["GACV"] = $$createField108_0($$parsedSource["GACV"]); + } + if ("GACP" in $$parsedSource) { + $$parsedSource["GACP"] = $$createField109_0($$parsedSource["GACP"]); + } + if ("GACiPtr" in $$parsedSource) { + $$parsedSource["GACiPtr"] = $$createField110_0($$parsedSource["GACiPtr"]); + } + if ("GACVPtr" in $$parsedSource) { + $$parsedSource["GACVPtr"] = $$createField111_0($$parsedSource["GACVPtr"]); + } + if ("GACPPtr" in $$parsedSource) { + $$parsedSource["GACPPtr"] = $$createField112_0($$parsedSource["GACPPtr"]); + } + if ("GABi" in $$parsedSource) { + $$parsedSource["GABi"] = $$createField113_0($$parsedSource["GABi"]); + } + if ("GABs" in $$parsedSource) { + $$parsedSource["GABs"] = $$createField114_0($$parsedSource["GABs"]); + } + if ("GABiPtr" in $$parsedSource) { + $$parsedSource["GABiPtr"] = $$createField115_0($$parsedSource["GABiPtr"]); + } + if ("GABT" in $$parsedSource) { + $$parsedSource["GABT"] = $$createField116_0($$parsedSource["GABT"]); + } + if ("GABTPtr" in $$parsedSource) { + $$parsedSource["GABTPtr"] = $$createField117_0($$parsedSource["GABTPtr"]); + } + if ("GAGT" in $$parsedSource) { + $$parsedSource["GAGT"] = $$createField118_0($$parsedSource["GAGT"]); + } + if ("GAGTPtr" in $$parsedSource) { + $$parsedSource["GAGTPtr"] = $$createField119_0($$parsedSource["GAGTPtr"]); + } + if ("GANBV" in $$parsedSource) { + $$parsedSource["GANBV"] = $$createField120_0($$parsedSource["GANBV"]); + } + if ("GANBP" in $$parsedSource) { + $$parsedSource["GANBP"] = $$createField121_0($$parsedSource["GANBP"]); + } + if ("GANBVPtr" in $$parsedSource) { + $$parsedSource["GANBVPtr"] = $$createField122_0($$parsedSource["GANBVPtr"]); + } + if ("GANBPPtr" in $$parsedSource) { + $$parsedSource["GANBPPtr"] = $$createField123_0($$parsedSource["GANBPPtr"]); + } + if ("GAPlV1" in $$parsedSource) { + $$parsedSource["GAPlV1"] = $$createField124_0($$parsedSource["GAPlV1"]); + } + if ("GAPlV2" in $$parsedSource) { + $$parsedSource["GAPlV2"] = $$createField125_0($$parsedSource["GAPlV2"]); + } + if ("GAPlP1" in $$parsedSource) { + $$parsedSource["GAPlP1"] = $$createField126_0($$parsedSource["GAPlP1"]); + } + if ("GAPlP2" in $$parsedSource) { + $$parsedSource["GAPlP2"] = $$createField127_0($$parsedSource["GAPlP2"]); + } + if ("GAPlVPtr" in $$parsedSource) { + $$parsedSource["GAPlVPtr"] = $$createField128_0($$parsedSource["GAPlVPtr"]); + } + if ("GAPlPPtr" in $$parsedSource) { + $$parsedSource["GAPlPPtr"] = $$createField129_0($$parsedSource["GAPlPPtr"]); + } + if ("GAMi" in $$parsedSource) { + $$parsedSource["GAMi"] = $$createField130_0($$parsedSource["GAMi"]); + } + if ("GAMS" in $$parsedSource) { + $$parsedSource["GAMS"] = $$createField131_0($$parsedSource["GAMS"]); + } + if ("GAMV" in $$parsedSource) { + $$parsedSource["GAMV"] = $$createField132_0($$parsedSource["GAMV"]); + } + if ("GAMSPtr" in $$parsedSource) { + $$parsedSource["GAMSPtr"] = $$createField133_0($$parsedSource["GAMSPtr"]); + } + if ("GAMVPtr" in $$parsedSource) { + $$parsedSource["GAMVPtr"] = $$createField134_0($$parsedSource["GAMVPtr"]); + } + if ("GAII" in $$parsedSource) { + $$parsedSource["GAII"] = $$createField135_0($$parsedSource["GAII"]); + } + if ("GAIV" in $$parsedSource) { + $$parsedSource["GAIV"] = $$createField136_0($$parsedSource["GAIV"]); + } + if ("GAIP" in $$parsedSource) { + $$parsedSource["GAIP"] = $$createField137_0($$parsedSource["GAIP"]); + } + if ("GAIIPtr" in $$parsedSource) { + $$parsedSource["GAIIPtr"] = $$createField138_0($$parsedSource["GAIIPtr"]); + } + if ("GAIVPtr" in $$parsedSource) { + $$parsedSource["GAIVPtr"] = $$createField139_0($$parsedSource["GAIVPtr"]); + } + if ("GAIPPtr" in $$parsedSource) { + $$parsedSource["GAIPPtr"] = $$createField140_0($$parsedSource["GAIPPtr"]); + } + if ("GAPrV" in $$parsedSource) { + $$parsedSource["GAPrV"] = $$createField141_0($$parsedSource["GAPrV"]); + } + if ("GAPrP" in $$parsedSource) { + $$parsedSource["GAPrP"] = $$createField142_0($$parsedSource["GAPrP"]); + } + if ("GAPrVPtr" in $$parsedSource) { + $$parsedSource["GAPrVPtr"] = $$createField143_0($$parsedSource["GAPrVPtr"]); + } + if ("GAPrPPtr" in $$parsedSource) { + $$parsedSource["GAPrPPtr"] = $$createField144_0($$parsedSource["GAPrPPtr"]); + } + return new Maps(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +/** + * @template X + * @typedef {X} MixedCstrAlias + */ + +/** + * @template V + * @typedef {V} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {W} PointableCstrAlias + */ + +/** + * @typedef {PointerTextMarshaler} PointerAlias + */ + +/** + * @typedef {string} PointerTextMarshaler + */ + +/** + * @typedef {string} StringAlias + */ + +/** + * @typedef {string} StringType + */ + +/** + * @typedef {ValueTextMarshaler} ValueAlias + */ + +/** + * @typedef {string} ValueTextMarshaler + */ + +// Private type creation functions +const $$createType0 = $Create.Map($Create.Any, $Create.Any); +const $$createType1 = $Create.Map($Create.Any, $Create.Any); +const $$createType2 = $Create.Map($Create.Any, $Create.Any); +const $$createType3 = $Create.Map($Create.Any, $Create.Any); +const $$createType4 = $Create.Map($Create.Any, $Create.Any); +const $$createType5 = $Create.Map($Create.Any, $Create.Any); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Map($Create.Any, $Create.Any); +const $$createType8 = $Create.Map($Create.Any, $Create.Any); +const $$createType9 = $Create.Map($Create.Any, $Create.Any); +const $$createType10 = $Create.Map($Create.Any, $Create.Any); +const $$createType11 = $Create.Map($Create.Any, $Create.Any); +const $$createType12 = $Create.Map($Create.Any, $Create.Any); +const $$createType13 = $Create.Map($Create.Any, $Create.Any); +const $$createType14 = $Create.Map($Create.Any, $Create.Any); +const $$createType15 = $Create.Map($Create.Any, $Create.Any); +const $$createType16 = $Create.Map($Create.Any, $Create.Any); +const $$createType17 = $Create.Map($Create.Any, $Create.Any); +const $$createType18 = $Create.Map($Create.Any, $Create.Any); +const $$createType19 = $Create.Map($Create.Any, $Create.Any); +const $$createType20 = $Create.Map($Create.Any, $Create.Any); +const $$createType21 = $Create.Map($Create.Any, $Create.Any); +const $$createType22 = $Create.Map($Create.Any, $Create.Any); +const $$createType23 = $Create.Map($Create.Any, $Create.Any); +const $$createType24 = $Create.Map($Create.Any, $Create.Any); +const $$createType25 = $Create.Map($Create.Any, $Create.Any); +const $$createType26 = $Create.Map($Create.Any, $Create.Any); +const $$createType27 = $Create.Map($Create.Any, $Create.Any); +const $$createType28 = $Create.Map($Create.Any, $Create.Any); +const $$createType29 = $Create.Map($Create.Any, $Create.Any); +const $$createType30 = $Create.Map($Create.Any, $Create.Any); +const $$createType31 = $Create.Map($Create.Any, $Create.Any); +const $$createType32 = $Create.Map($Create.Any, $Create.Any); +const $$createType33 = $Create.Map($Create.Any, $Create.Any); +const $$createType34 = $Create.Map($Create.Any, $Create.Any); +const $$createType35 = $Create.Map($Create.Any, $Create.Any); +const $$createType36 = $Create.Map($Create.Any, $Create.Any); +const $$createType37 = $Create.Map($Create.Any, $Create.Any); +const $$createType38 = $Create.Map($Create.Any, $Create.Any); +const $$createType39 = $Create.Map($Create.Any, $Create.Any); +const $$createType40 = $Create.Map($Create.Any, $Create.Any); +const $$createType41 = $Create.Map($Create.Any, $Create.Any); +const $$createType42 = $Create.Map($Create.Any, $Create.Any); +const $$createType43 = $Create.Map($Create.Any, $Create.Any); +const $$createType44 = $Create.Map($Create.Any, $Create.Any); +const $$createType45 = $Create.Map($Create.Any, $Create.Any); +const $$createType46 = $Create.Map($Create.Any, $Create.Any); +const $$createType47 = $Create.Map($Create.Any, $Create.Any); +const $$createType48 = $Create.Map($Create.Any, $Create.Any); +const $$createType49 = $Create.Map($Create.Any, $Create.Any); +const $$createType50 = $Create.Map($Create.Any, $Create.Any); +const $$createType51 = $Create.Map($Create.Any, $Create.Any); +const $$createType52 = $Create.Map($Create.Any, $Create.Any); +const $$createType53 = $Create.Map($Create.Any, $Create.Any); +const $$createType54 = $Create.Map($Create.Any, $Create.Any); +const $$createType55 = $Create.Map($Create.Any, $Create.Any); +const $$createType56 = $Create.Map($Create.Any, $Create.Any); +const $$createType57 = $Create.Map($Create.Any, $Create.Any); +const $$createType58 = $Create.Map($Create.Any, $Create.Any); +const $$createType59 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType60 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType61 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType62 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType63 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType64 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType65 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType66 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType67 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType68 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType69 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType70 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType71 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType72 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType73 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType74 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType75 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType76 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType77 = $Create.Map($Create.Any, $Create.Any); +const $$createType78 = $Create.Map($Create.Any, $Create.Any); +const $$createType79 = $Create.Map($Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js new file mode 100644 index 000000000..870b2804b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>>} + */ +export function Method() { + return $Call.ByID(4021345184).then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Maps.createFrom($Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js new file mode 100644 index 000000000..8f525252e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js @@ -0,0 +1,116 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Data, + ImplicitNonMarshaler, + NonMarshaler +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * any + * @typedef {$models.AliasJsonMarshaler} AliasJsonMarshaler + */ + +/** + * any + * @typedef {$models.AliasMarshaler} AliasMarshaler + */ + +/** + * struct{} + * @typedef {$models.AliasNonMarshaler} AliasNonMarshaler + */ + +/** + * string + * @typedef {$models.AliasTextMarshaler} AliasTextMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitJsonButText} ImplicitJsonButText + */ + +/** + * any + * @typedef {$models.ImplicitJsonMarshaler} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitMarshaler} ImplicitMarshaler + */ + +/** + * string + * @typedef {$models.ImplicitNonJson} ImplicitNonJson + */ + +/** + * any + * @typedef {$models.ImplicitNonText} ImplicitNonText + */ + +/** + * any + * @typedef {$models.ImplicitTextButJson} ImplicitTextButJson + */ + +/** + * string + * @typedef {$models.ImplicitTextMarshaler} ImplicitTextMarshaler + */ + +/** + * any + * @typedef {$models.PointerJsonMarshaler} PointerJsonMarshaler + */ + +/** + * any + * @typedef {$models.PointerMarshaler} PointerMarshaler + */ + +/** + * string + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingJsonMarshaler} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingMarshaler} UnderlyingMarshaler + */ + +/** + * string + * @typedef {$models.UnderlyingTextMarshaler} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {$models.ValueJsonMarshaler} ValueJsonMarshaler + */ + +/** + * any + * @typedef {$models.ValueMarshaler} ValueMarshaler + */ + +/** + * string + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js new file mode 100644 index 000000000..77fe552ea --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js @@ -0,0 +1,630 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + * @typedef {any} AliasJsonMarshaler + */ + +/** + * any + * @typedef {any} AliasMarshaler + */ + +/** + * struct{} + * @typedef { { + * } } AliasNonMarshaler + */ + +/** + * string + * @typedef {string} AliasTextMarshaler + */ + +export class Data { + /** + * Creates a new Data instance. + * @param {Partial} [$$source = {}] - The source object to create the Data. + */ + constructor($$source = {}) { + if (!("NM" in $$source)) { + /** + * @member + * @type {NonMarshaler} + */ + this["NM"] = (new NonMarshaler()); + } + if (!("NMPtr" in $$source)) { + /** + * NonMarshaler | null + * @member + * @type {NonMarshaler | null} + */ + this["NMPtr"] = null; + } + if (!("VJM" in $$source)) { + /** + * @member + * @type {ValueJsonMarshaler} + */ + this["VJM"] = null; + } + if (!("VJMPtr" in $$source)) { + /** + * ValueJsonMarshaler | null + * @member + * @type {ValueJsonMarshaler | null} + */ + this["VJMPtr"] = null; + } + if (!("PJM" in $$source)) { + /** + * @member + * @type {PointerJsonMarshaler} + */ + this["PJM"] = null; + } + if (!("PJMPtr" in $$source)) { + /** + * PointerJsonMarshaler | null + * @member + * @type {PointerJsonMarshaler | null} + */ + this["PJMPtr"] = null; + } + if (!("VTM" in $$source)) { + /** + * @member + * @type {ValueTextMarshaler} + */ + this["VTM"] = ""; + } + if (!("VTMPtr" in $$source)) { + /** + * ValueTextMarshaler | null + * @member + * @type {ValueTextMarshaler | null} + */ + this["VTMPtr"] = null; + } + if (!("PTM" in $$source)) { + /** + * @member + * @type {PointerTextMarshaler} + */ + this["PTM"] = ""; + } + if (!("PTMPtr" in $$source)) { + /** + * PointerTextMarshaler | null + * @member + * @type {PointerTextMarshaler | null} + */ + this["PTMPtr"] = null; + } + if (!("VM" in $$source)) { + /** + * @member + * @type {ValueMarshaler} + */ + this["VM"] = null; + } + if (!("VMPtr" in $$source)) { + /** + * ValueMarshaler | null + * @member + * @type {ValueMarshaler | null} + */ + this["VMPtr"] = null; + } + if (!("PM" in $$source)) { + /** + * @member + * @type {PointerMarshaler} + */ + this["PM"] = null; + } + if (!("PMPtr" in $$source)) { + /** + * PointerMarshaler | null + * @member + * @type {PointerMarshaler | null} + */ + this["PMPtr"] = null; + } + if (!("UJM" in $$source)) { + /** + * @member + * @type {UnderlyingJsonMarshaler} + */ + this["UJM"] = null; + } + if (!("UJMPtr" in $$source)) { + /** + * UnderlyingJsonMarshaler | null + * @member + * @type {UnderlyingJsonMarshaler | null} + */ + this["UJMPtr"] = null; + } + if (!("UTM" in $$source)) { + /** + * @member + * @type {UnderlyingTextMarshaler} + */ + this["UTM"] = ""; + } + if (!("UTMPtr" in $$source)) { + /** + * UnderlyingTextMarshaler | null + * @member + * @type {UnderlyingTextMarshaler | null} + */ + this["UTMPtr"] = null; + } + if (!("UM" in $$source)) { + /** + * @member + * @type {UnderlyingMarshaler} + */ + this["UM"] = null; + } + if (!("UMPtr" in $$source)) { + /** + * UnderlyingMarshaler | null + * @member + * @type {UnderlyingMarshaler | null} + */ + this["UMPtr"] = null; + } + if (!("JM" in $$source)) { + /** + * any + * @member + * @type {any} + */ + this["JM"] = null; + } + if (!("JMPtr" in $$source)) { + /** + * any | null + * @member + * @type {any | null} + */ + this["JMPtr"] = null; + } + if (!("TM" in $$source)) { + /** + * string + * @member + * @type {string} + */ + this["TM"] = ""; + } + if (!("TMPtr" in $$source)) { + /** + * string | null + * @member + * @type {string | null} + */ + this["TMPtr"] = null; + } + if (!("CJM" in $$source)) { + /** + * any + * @member + * @type {any} + */ + this["CJM"] = null; + } + if (!("CJMPtr" in $$source)) { + /** + * any | null + * @member + * @type {any | null} + */ + this["CJMPtr"] = null; + } + if (!("CTM" in $$source)) { + /** + * string + * @member + * @type {string} + */ + this["CTM"] = ""; + } + if (!("CTMPtr" in $$source)) { + /** + * string | null + * @member + * @type {string | null} + */ + this["CTMPtr"] = null; + } + if (!("CM" in $$source)) { + /** + * any + * @member + * @type {any} + */ + this["CM"] = null; + } + if (!("CMPtr" in $$source)) { + /** + * any | null + * @member + * @type {any | null} + */ + this["CMPtr"] = null; + } + if (!("ANM" in $$source)) { + /** + * @member + * @type {AliasNonMarshaler} + */ + this["ANM"] = {}; + } + if (!("ANMPtr" in $$source)) { + /** + * AliasNonMarshaler | null + * @member + * @type {AliasNonMarshaler | null} + */ + this["ANMPtr"] = null; + } + if (!("AJM" in $$source)) { + /** + * @member + * @type {AliasJsonMarshaler} + */ + this["AJM"] = null; + } + if (!("AJMPtr" in $$source)) { + /** + * AliasJsonMarshaler | null + * @member + * @type {AliasJsonMarshaler | null} + */ + this["AJMPtr"] = null; + } + if (!("ATM" in $$source)) { + /** + * @member + * @type {AliasTextMarshaler} + */ + this["ATM"] = ""; + } + if (!("ATMPtr" in $$source)) { + /** + * AliasTextMarshaler | null + * @member + * @type {AliasTextMarshaler | null} + */ + this["ATMPtr"] = null; + } + if (!("AM" in $$source)) { + /** + * @member + * @type {AliasMarshaler} + */ + this["AM"] = null; + } + if (!("AMPtr" in $$source)) { + /** + * AliasMarshaler | null + * @member + * @type {AliasMarshaler | null} + */ + this["AMPtr"] = null; + } + if (!("ImJM" in $$source)) { + /** + * @member + * @type {ImplicitJsonMarshaler} + */ + this["ImJM"] = null; + } + if (!("ImJMPtr" in $$source)) { + /** + * ImplicitJsonMarshaler | null + * @member + * @type {ImplicitJsonMarshaler | null} + */ + this["ImJMPtr"] = null; + } + if (!("ImTM" in $$source)) { + /** + * @member + * @type {ImplicitTextMarshaler} + */ + this["ImTM"] = ""; + } + if (!("ImTMPtr" in $$source)) { + /** + * ImplicitTextMarshaler | null + * @member + * @type {ImplicitTextMarshaler | null} + */ + this["ImTMPtr"] = null; + } + if (!("ImM" in $$source)) { + /** + * @member + * @type {ImplicitMarshaler} + */ + this["ImM"] = null; + } + if (!("ImMPtr" in $$source)) { + /** + * ImplicitMarshaler | null + * @member + * @type {ImplicitMarshaler | null} + */ + this["ImMPtr"] = null; + } + if (!("ImNJ" in $$source)) { + /** + * @member + * @type {ImplicitNonJson} + */ + this["ImNJ"] = ""; + } + if (!("ImNJPtr" in $$source)) { + /** + * ImplicitNonJson | null + * @member + * @type {ImplicitNonJson | null} + */ + this["ImNJPtr"] = null; + } + if (!("ImNT" in $$source)) { + /** + * @member + * @type {ImplicitNonText} + */ + this["ImNT"] = null; + } + if (!("ImNTPtr" in $$source)) { + /** + * ImplicitNonText | null + * @member + * @type {ImplicitNonText | null} + */ + this["ImNTPtr"] = null; + } + if (!("ImNM" in $$source)) { + /** + * @member + * @type {ImplicitNonMarshaler} + */ + this["ImNM"] = (new ImplicitNonMarshaler()); + } + if (!("ImNMPtr" in $$source)) { + /** + * ImplicitNonMarshaler | null + * @member + * @type {ImplicitNonMarshaler | null} + */ + this["ImNMPtr"] = null; + } + if (!("ImJbT" in $$source)) { + /** + * @member + * @type {ImplicitJsonButText} + */ + this["ImJbT"] = null; + } + if (!("ImJbTPtr" in $$source)) { + /** + * ImplicitJsonButText | null + * @member + * @type {ImplicitJsonButText | null} + */ + this["ImJbTPtr"] = null; + } + if (!("ImTbJ" in $$source)) { + /** + * @member + * @type {ImplicitTextButJson} + */ + this["ImTbJ"] = null; + } + if (!("ImTbJPtr" in $$source)) { + /** + * ImplicitTextButJson | null + * @member + * @type {ImplicitTextButJson | null} + */ + this["ImTbJPtr"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Data instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Data} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField48_0 = $$createType2; + const $$createField49_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("NM" in $$parsedSource) { + $$parsedSource["NM"] = $$createField0_0($$parsedSource["NM"]); + } + if ("NMPtr" in $$parsedSource) { + $$parsedSource["NMPtr"] = $$createField1_0($$parsedSource["NMPtr"]); + } + if ("ImNM" in $$parsedSource) { + $$parsedSource["ImNM"] = $$createField48_0($$parsedSource["ImNM"]); + } + if ("ImNMPtr" in $$parsedSource) { + $$parsedSource["ImNMPtr"] = $$createField49_0($$parsedSource["ImNMPtr"]); + } + return new Data(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * any + * @typedef {any} ImplicitJsonButText + */ + +/** + * any + * @typedef {any} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {any} ImplicitMarshaler + */ + +/** + * string + * @typedef {string} ImplicitNonJson + */ + +/** + * class{ Marshaler, TextMarshaler } + */ +export class ImplicitNonMarshaler { + /** + * Creates a new ImplicitNonMarshaler instance. + * @param {Partial} [$$source = {}] - The source object to create the ImplicitNonMarshaler. + */ + constructor($$source = {}) { + if (!("Marshaler" in $$source)) { + /** + * @member + * @type {json$0.Marshaler} + */ + this["Marshaler"] = null; + } + if (!("TextMarshaler" in $$source)) { + /** + * @member + * @type {encoding$0.TextMarshaler} + */ + this["TextMarshaler"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new ImplicitNonMarshaler instance from a string or object. + * @param {any} [$$source = {}] + * @returns {ImplicitNonMarshaler} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new ImplicitNonMarshaler(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * any + * @typedef {any} ImplicitNonText + */ + +/** + * any + * @typedef {any} ImplicitTextButJson + */ + +/** + * string + * @typedef {string} ImplicitTextMarshaler + */ + +/** + * class {} + */ +export class NonMarshaler { + /** + * Creates a new NonMarshaler instance. + * @param {Partial} [$$source = {}] - The source object to create the NonMarshaler. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new NonMarshaler instance from a string or object. + * @param {any} [$$source = {}] + * @returns {NonMarshaler} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new NonMarshaler(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * any + * @typedef {any} PointerJsonMarshaler + */ + +/** + * any + * @typedef {any} PointerMarshaler + */ + +/** + * string + * @typedef {string} PointerTextMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingMarshaler + */ + +/** + * string + * @typedef {string} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {any} ValueJsonMarshaler + */ + +/** + * any + * @typedef {any} ValueMarshaler + */ + +/** + * string + * @typedef {string} ValueTextMarshaler + */ + +// Private type creation functions +const $$createType0 = NonMarshaler.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = ImplicitNonMarshaler.createFrom; +const $$createType3 = $Create.Nullable($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js new file mode 100644 index 000000000..5b415f83e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Data>} + */ +export function Method() { + return $Call.ByID(4021345184).then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Data.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js new file mode 100644 index 000000000..9be766c03 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +export { + HowDifferent, + Impersonator, + Person, + PrivatePerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js new file mode 100644 index 000000000..b42e223fa --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js @@ -0,0 +1,181 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + * @template How + */ +export class HowDifferent { + /** + * Creates a new HowDifferent instance. + * @param {Partial>} [$$source = {}] - The source object to create the HowDifferent. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * They have a name as well. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + /** + * But they may have many differences. + * @member + * @type {{ [_: string]: How }[]} + */ + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class HowDifferent. + * @template [How=any] + * @param {(source: any) => How} $$createParamHow + * @returns {($$source?: any) => HowDifferent} + */ + static createFrom($$createParamHow) { + const $$createField1_0 = $$createType1($$createParamHow); + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new HowDifferent(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +/** + * Impersonator gets their fields from other people. + */ +export const Impersonator = other$0.OtherPerson; + +/** + * Impersonator gets their fields from other people. + * @typedef {other$0.OtherPerson} Impersonator + */ + +/** + * Person is not a number. + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * They have a name. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + /** + * Exactly 4 sketchy friends. + * @member + * @type {Impersonator[]} + */ + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField1_0($$parsedSource["Friends"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +export class personImpl { + /** + * Creates a new personImpl instance. + * @param {Partial} [$$source = {}] - The source object to create the personImpl. + */ + constructor($$source = {}) { + if (!("Nickname" in $$source)) { + /** + * Nickname conceals a person's identity. + * @member + * @type {string} + */ + this["Nickname"] = ""; + } + if (!("Name" in $$source)) { + /** + * They have a name. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + /** + * Exactly 4 sketchy friends. + * @member + * @type {Impersonator[]} + */ + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new personImpl instance from a string or object. + * @param {any} [$$source = {}] + * @returns {personImpl} + */ + static createFrom($$source = {}) { + const $$createField2_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField2_0($$parsedSource["Friends"]); + } + return new personImpl(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export const PrivatePerson = personImpl; + +/** + * PrivatePerson gets their fields from hidden sources. + * @typedef {personImpl} PrivatePerson + */ + +// Private type creation functions +const $$createType0 = /** @type {(...args: any[]) => any} */(($$createParamHow) => $Create.Map($Create.Any, $$createParamHow)); +const $$createType1 = /** @type {(...args: any[]) => any} */(($$createParamHow) => $Create.Array($$createType0($$createParamHow))); +const $$createType2 = other$0.OtherPerson.createFrom($Create.Any); +const $$createType3 = $Create.Array($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.js new file mode 100644 index 000000000..c3eabd022 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * StringPtr is a nullable string. + * @typedef {$models.StringPtr} StringPtr + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.js new file mode 100644 index 000000000..b4fdacf8e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * StringPtr is a nullable string. + * @typedef {string | null} StringPtr + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js new file mode 100644 index 000000000..db4e64147 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +export { + OtherPerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js new file mode 100644 index 000000000..89992cacf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js @@ -0,0 +1,60 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * OtherPerson is like a person, but different. + * @template T + */ +export class OtherPerson { + /** + * Creates a new OtherPerson instance. + * @param {Partial>} [$$source = {}] - The source object to create the OtherPerson. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * They have a name as well. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + /** + * But they may have many differences. + * @member + * @type {T[]} + */ + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class OtherPerson. + * @template [T=any] + * @param {(source: any) => T} $$createParamT + * @returns {($$source?: any) => OtherPerson} + */ + static createFrom($$createParamT) { + const $$createField1_0 = $$createType0($$createParamT); + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new OtherPerson(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +// Private type creation functions +const $$createType0 = /** @type {(...args: any[]) => any} */(($$createParamT) => $Create.Array($$createParamT)); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js new file mode 100644 index 000000000..36b25c183 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(3606939272); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js new file mode 100644 index 000000000..a87ccf6c4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js @@ -0,0 +1,42 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByID(2124352079).then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + })); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(4281222271); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.HowDifferent.createFrom($Create.Any); +const $$createType2 = $models.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js new file mode 100644 index 000000000..5fb44fbc6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(3566862802); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js new file mode 100644 index 000000000..5ec6c820e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js @@ -0,0 +1,42 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByID(2590614085).then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + })); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(773650321); +} + +// Private type creation functions +const $$createType0 = nobindingshere$0.Person.createFrom; +const $$createType1 = nobindingshere$0.HowDifferent.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js new file mode 100644 index 000000000..8afbd8b3a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} $0 + * @returns {$CancellablePromise} + */ +export function Greet($0) { + return $Call.ByID(1411160069, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js new file mode 100644 index 000000000..734fb02e7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js new file mode 100644 index 000000000..e50a4a6ab --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js new file mode 100644 index 000000000..f017774d8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js new file mode 100644 index 000000000..e50a4a6ab --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js new file mode 100644 index 000000000..f017774d8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js new file mode 100644 index 000000000..50737a34b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js new file mode 100644 index 000000000..04771d2ca --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js @@ -0,0 +1,54 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {services$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
        } [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
        } */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js new file mode 100644 index 000000000..24dc0334e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(3568225479).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js new file mode 100644 index 000000000..855602e98 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js @@ -0,0 +1,379 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByID(3862002418, $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByID(2424639793, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByID(3132595881, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByID(3306292566, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByID(1754277916, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByID(1909469092, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByID(4251088558, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByID(1343888303, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByID(2205561041, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByID(572240879, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByID(2189402897, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByID(642881729, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByID(1066151743, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByID(2718999663, $in); +} + +/** + * @param {{ [_: `${number}`]: number }} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByID(2386486356, $in); +} + +/** + * @param {{ [_: `${number}`]: number | null }} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByID(2163571325, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByID(2900172572, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] }>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByID(881980169, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByID(1075577233); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByID(3589606958, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByID(224675106, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByID(2124953624, $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByID(3516977899, $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByID(229603958, $in); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByID(3678582682, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByID(319259595, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByID(383995060, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByID(1091960237, $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByID(3835643147, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType3($result); + })); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByID(2447692557, $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByID(2943477349, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType4($result); + })); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByID(3401034892, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByID(1236957573, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByID(1160383782, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByID(1739300671, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByID(793803239, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByID(1403757716, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByID(2988345717, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByID(518250834, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByID(2836661285, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByID(1367187362, $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js new file mode 100644 index 000000000..69c96370a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js @@ -0,0 +1,57 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + /** + * @member + * @type {Person | null} + */ + this["Parent"] = null; + } + if (!("Details" in $$source)) { + /** + * @member + * @type {{"Age": number, "Address": {"Street": string}}} + */ + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js new file mode 100644 index 000000000..855602e98 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js @@ -0,0 +1,379 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByID(3862002418, $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByID(2424639793, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByID(3132595881, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByID(3306292566, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByID(1754277916, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByID(1909469092, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByID(4251088558, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByID(1343888303, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByID(2205561041, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByID(572240879, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByID(2189402897, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByID(642881729, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByID(1066151743, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByID(2718999663, $in); +} + +/** + * @param {{ [_: `${number}`]: number }} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByID(2386486356, $in); +} + +/** + * @param {{ [_: `${number}`]: number | null }} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByID(2163571325, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByID(2900172572, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] }>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByID(881980169, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByID(1075577233); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByID(3589606958, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByID(224675106, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByID(2124953624, $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByID(3516977899, $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByID(229603958, $in); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByID(3678582682, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByID(319259595, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByID(383995060, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByID(1091960237, $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByID(3835643147, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType3($result); + })); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByID(2447692557, $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByID(2943477349, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType4($result); + })); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByID(3401034892, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByID(1236957573, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByID(1160383782, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByID(1739300671, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByID(793803239, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByID(1403757716, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByID(2988345717, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByID(518250834, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByID(2836661285, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByID(1367187362, $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js new file mode 100644 index 000000000..69c96370a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js @@ -0,0 +1,57 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + /** + * @member + * @type {Person | null} + */ + this["Parent"] = null; + } + if (!("Details" in $$source)) { + /** + * @member + * @type {{"Age": number, "Address": {"Street": string}}} + */ + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js new file mode 100644 index 000000000..9dfe48511 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js new file mode 100644 index 000000000..9dfe48511 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js new file mode 100644 index 000000000..50737a34b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js new file mode 100644 index 000000000..d7bfe75cf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js @@ -0,0 +1,58 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {services$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
        } [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
        } */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js new file mode 100644 index 000000000..de3c9d51d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(1491748400).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/warnings.log b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/warnings.log new file mode 100644 index 000000000..ce8369307 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/warnings.log @@ -0,0 +1,83 @@ +[warn] /testcases/complex_json/main.go:127:2: event 'collision' has one of multiple definitions here with data type map[string]int +[warn] /testcases/events_only/events.go:20:2: event 'collision' has one of multiple definitions here with data type int +[warn] /testcases/events_only/events.go:21:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:10:5: `application.RegisterEvent` is instantiated here but not called +[warn] /testcases/events_only/other.go:13:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:17:2: data type []T for event 'parametric' contains unresolved type parameters and will be ignored` +[warn] /testcases/events_only/other.go:22:2: event 'common:ApplicationStarted' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic +[warn] /testcases/marshalers/main.go:212:2: event 'collision' has one of multiple definitions here with data type *struct{Field []bool} +[warn] /testcases/marshalers/main.go:214:2: data type encoding/json.Marshaler for event 'interface' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors +[warn] dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only +[warn] event 'collision' has multiple conflicting definitions and will be ignored +[warn] events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly +[warn] generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/index.js new file mode 100644 index 000000000..cf48d86db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/index.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {$models.TextMarshaler} TextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/json/index.js new file mode 100644 index 000000000..22f1fd904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/json/index.js @@ -0,0 +1,11 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {$models.Marshaler} Marshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/json/models.js new file mode 100644 index 000000000..96abf0ef1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/json/models.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {any} Marshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/models.js new file mode 100644 index 000000000..59cfef5bd --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/models.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {any} TextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/eventcreate.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/eventcreate.js new file mode 100644 index 000000000..9cc7781aa --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/eventcreate.js @@ -0,0 +1,39 @@ +//@ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../encoding/json/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as events_only$0 from "./generator/testcases/events_only/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as more$0 from "./generator/testcases/no_bindings_here/more/models.js"; + +function configure() { + Object.freeze(Object.assign($Create.Events, { + "events_only:class": $$createType0, + "events_only:map": $$createType2, + "events_only:other": $$createType3, + "overlap": $$createType6, + })); +} + +// Private type creation functions +const $$createType0 = events_only$0.SomeClass.createFrom; +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = $Create.Map($Create.Any, $$createType1); +const $$createType3 = $Create.Array($Create.Any); +const $$createType4 = $Create.Array($Create.Any); +const $$createType5 = $Create.Struct({ + "Field": $$createType4, +}); +const $$createType6 = $Create.Nullable($$createType5); + +configure(); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/eventdata.d.ts b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/eventdata.d.ts new file mode 100644 index 000000000..d265e3adc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/eventdata.d.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type { Events } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as json$0 from "../../../../../encoding/json/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as events_only$0 from "./generator/testcases/events_only/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as more$0 from "./generator/testcases/no_bindings_here/more/models.js"; + +declare module "/wails/runtime.js" { + namespace Events { + interface CustomEvents { + "events_only:class": events_only$0.SomeClass; + "events_only:map": { [_: string]: number[] }; + "events_only:nodata": void; + "events_only:other": more$0.StringPtr[]; + "events_only:string": string; + "interface": json$0.Marshaler; + "overlap": {"Field": boolean[]} | null; + } + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js new file mode 100644 index 000000000..2352f40bc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js @@ -0,0 +1,97 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + * @param {$models.Alias} aliasValue + * @returns {$CancellablePromise<$models.Person>} + */ +export function Get(aliasValue) { + return $Call.ByName("main.GreetService.Get", aliasValue).then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +/** + * Apparently, aliases are all the rage right now. + * @param {$models.AliasedPerson} p + * @returns {$CancellablePromise<$models.StrangelyAliasedPerson>} + */ +export function GetButAliased(p) { + return $Call.ByName("main.GreetService.GetButAliased", p).then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +/** + * Get someone quite different. + * @returns {$CancellablePromise<$models.GenericPerson>} + */ +export function GetButDifferent() { + return $Call.ByName("main.GreetService.GetButDifferent").then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +/** + * @returns {$CancellablePromise} + */ +export function GetButForeignPrivateAlias() { + return $Call.ByName("main.GreetService.GetButForeignPrivateAlias").then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @returns {$CancellablePromise<$models.AliasGroup>} + */ +export function GetButGenericAliases() { + return $Call.ByName("main.GreetService.GetButGenericAliases").then(/** @type {($result: any) => any} */(($result) => { + return $$createType3($result); + })); +} + +/** + * Greet a lot of unusual things. + * @param {$models.EmptyAliasStruct} $0 + * @param {$models.EmptyStruct} $1 + * @returns {$CancellablePromise<$models.AliasStruct>} + */ +export function Greet($0, $1) { + return $Call.ByName("main.GreetService.Greet", $0, $1).then(/** @type {($result: any) => any} */(($result) => { + return $$createType7($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.GenericPerson.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; +const $$createType3 = $models.AliasGroup.createFrom; +const $$createType4 = $Create.Array($Create.Any); +const $$createType5 = $Create.Array($Create.Any); +const $$createType6 = $Create.Struct({ + "NoMoreIdeas": $$createType5, +}); +const $$createType7 = $Create.Struct({ + "Foo": $$createType4, + "Other": $$createType6, +}); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js new file mode 100644 index 000000000..4278c7958 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js @@ -0,0 +1,61 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + AliasGroup, + AliasedPerson, + EmptyStruct, + GenericPerson, + GenericPersonAlias, + IndirectPersonAlias, + Person, + StrangelyAliasedPerson, + TPIndirectPersonAlias +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * A nice type Alias. + * @typedef {$models.Alias} Alias + */ + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {$models.AliasStruct} AliasStruct + */ + +/** + * An empty struct alias. + * @typedef {$models.EmptyAliasStruct} EmptyAliasStruct + */ + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {$models.GenericAlias} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {$models.GenericMapAlias} GenericMapAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {$models.GenericPtrAlias} GenericPtrAlias + */ + +/** + * Another struct alias. + * @typedef {$models.OtherAliasStruct} OtherAliasStruct + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js new file mode 100644 index 000000000..3de57786d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js @@ -0,0 +1,334 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * A nice type Alias. + * @typedef {number} Alias + */ + +/** + * A class whose fields have various aliased types. + */ +export class AliasGroup { + /** + * Creates a new AliasGroup instance. + * @param {Partial} [$$source = {}] - The source object to create the AliasGroup. + */ + constructor($$source = {}) { + if (!("GAi" in $$source)) { + /** + * @member + * @type {GenericAlias} + */ + this["GAi"] = 0; + } + if (!("GAP" in $$source)) { + /** + * @member + * @type {GenericAlias>} + */ + this["GAP"] = (new GenericPerson()); + } + if (!("GPAs" in $$source)) { + /** + * @member + * @type {GenericPtrAlias} + */ + this["GPAs"] = null; + } + if (!("GPAP" in $$source)) { + /** + * @member + * @type {GenericPtrAlias>} + */ + this["GPAP"] = null; + } + if (!("GMA" in $$source)) { + /** + * @member + * @type {GenericMapAlias} + */ + this["GMA"] = {}; + } + if (!("GPA" in $$source)) { + /** + * @member + * @type {GenericPersonAlias} + */ + this["GPA"] = (new GenericPersonAlias()); + } + if (!("IPA" in $$source)) { + /** + * @member + * @type {IndirectPersonAlias} + */ + this["IPA"] = (new IndirectPersonAlias()); + } + if (!("TPIPA" in $$source)) { + /** + * @member + * @type {TPIndirectPersonAlias} + */ + this["TPIPA"] = (new TPIndirectPersonAlias()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new AliasGroup instance from a string or object. + * @param {any} [$$source = {}] + * @returns {AliasGroup} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType0; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType5; + const $$createField4_0 = $$createType6; + const $$createField5_0 = $$createType8; + const $$createField6_0 = $$createType8; + const $$createField7_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("GAP" in $$parsedSource) { + $$parsedSource["GAP"] = $$createField1_0($$parsedSource["GAP"]); + } + if ("GPAs" in $$parsedSource) { + $$parsedSource["GPAs"] = $$createField2_0($$parsedSource["GPAs"]); + } + if ("GPAP" in $$parsedSource) { + $$parsedSource["GPAP"] = $$createField3_0($$parsedSource["GPAP"]); + } + if ("GMA" in $$parsedSource) { + $$parsedSource["GMA"] = $$createField4_0($$parsedSource["GMA"]); + } + if ("GPA" in $$parsedSource) { + $$parsedSource["GPA"] = $$createField5_0($$parsedSource["GPA"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField6_0($$parsedSource["IPA"]); + } + if ("TPIPA" in $$parsedSource) { + $$parsedSource["TPIPA"] = $$createField7_0($$parsedSource["TPIPA"]); + } + return new AliasGroup(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {Object} AliasStruct + * @property {number[]} Foo - A field with a comment. + * @property {string} [Bar] - Definitely not Foo. + * @property {string} [Baz] - Definitely not Foo. + * @property {OtherAliasStruct} Other - A nested alias struct. + */ + +/** + * An empty struct alias. + * @typedef { { + * } } EmptyAliasStruct + */ + +/** + * An empty struct. + */ +export class EmptyStruct { + /** + * Creates a new EmptyStruct instance. + * @param {Partial} [$$source = {}] - The source object to create the EmptyStruct. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new EmptyStruct instance from a string or object. + * @param {any} [$$source = {}] + * @returns {EmptyStruct} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new EmptyStruct(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {T} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {{ [_: string]: U }} GenericMapAlias + */ + +/** + * A generic struct containing an alias. + * @template T + */ +export class GenericPerson { + /** + * Creates a new GenericPerson instance. + * @param {Partial>} [$$source = {}] - The source object to create the GenericPerson. + */ + constructor($$source = {}) { + if (/** @type {any} */(false)) { + /** + * @member + * @type {T | undefined} + */ + this["Name"] = undefined; + } + if (!("AliasedField" in $$source)) { + /** + * @member + * @type {Alias} + */ + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class GenericPerson. + * @template [T=any] + * @param {(source: any) => T} $$createParamT + * @returns {($$source?: any) => GenericPerson} + */ + static createFrom($$createParamT) { + const $$createField0_0 = $$createParamT; + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Name" in $$parsedSource) { + $$parsedSource["Name"] = $$createField0_0($$parsedSource["Name"]); + } + return new GenericPerson(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +/** + * A generic alias that wraps a generic struct. + */ +export const GenericPersonAlias = GenericPerson; + +/** + * A generic alias that wraps a generic struct. + * @template T + * @typedef {GenericPerson[]>} GenericPersonAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {GenericAlias | null} GenericPtrAlias + */ + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export const IndirectPersonAlias = GenericPersonAlias; + +/** + * An alias that wraps a class through a non-typeparam alias. + * @typedef {GenericPersonAlias} IndirectPersonAlias + */ + +/** + * Another struct alias. + * @typedef {Object} OtherAliasStruct + * @property {number[]} NoMoreIdeas + */ + +/** + * A non-generic struct containing an alias. + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * The Person's name. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("AliasedField" in $$source)) { + /** + * A random alias field. + * @member + * @type {Alias} + */ + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * A class alias. + */ +export const AliasedPerson = Person; + +/** + * A class alias. + * @typedef {Person} AliasedPerson + */ + +/** + * Another class alias, but ordered after its aliased class. + */ +export const StrangelyAliasedPerson = Person; + +/** + * Another class alias, but ordered after its aliased class. + * @typedef {Person} StrangelyAliasedPerson + */ + +/** + * An alias that wraps a class through a typeparam alias. + */ +export const TPIndirectPersonAlias = GenericPerson; + +/** + * An alias that wraps a class through a typeparam alias. + * @typedef {GenericAlias>} TPIndirectPersonAlias + */ + +// Private type creation functions +const $$createType0 = GenericPerson.createFrom($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = $Create.Nullable($$createType1); +const $$createType3 = $Create.Array($Create.Any); +const $$createType4 = GenericPerson.createFrom($$createType3); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Array($Create.Any); +const $$createType8 = GenericPerson.createFrom($$createType7); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js new file mode 100644 index 000000000..f6c839720 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js new file mode 100644 index 000000000..54e794649 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service7.TestMethod"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js new file mode 100644 index 000000000..f6e8901a9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod2() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service9.TestMethod2"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js new file mode 100644 index 000000000..b5c320f3d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {$models.Person} person + * @param {$models.Embedded1} emb + * @returns {$CancellablePromise} + */ +export function Greet(person, emb) { + return $Call.ByName("main.GreetService.Greet", person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js new file mode 100644 index 000000000..ab78e5ea3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Embedded1, + Person, + Title +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Embedded3} Embedded3 + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js new file mode 100644 index 000000000..7a0edf1c7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js @@ -0,0 +1,272 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Embedded1 { + /** + * Creates a new Embedded1 instance. + * @param {Partial} [$$source = {}] - The source object to create the Embedded1. + */ + constructor($$source = {}) { + if (!("Friends" in $$source)) { + /** + * Friends should be shadowed in Person by a field of lesser depth + * @member + * @type {number} + */ + this["Friends"] = 0; + } + if (!("Vanish" in $$source)) { + /** + * Vanish should be omitted from Person because there is another field with same depth and no tag + * @member + * @type {number} + */ + this["Vanish"] = 0; + } + if (!("StillThere" in $$source)) { + /** + * StillThere should be shadowed in Person by other field with same depth and a json tag + * @member + * @type {string} + */ + this["StillThere"] = ""; + } + if (!("NamingThingsIsHard" in $$source)) { + /** + * NamingThingsIsHard is a law of programming + * @member + * @type {`${boolean}`} + */ + this["NamingThingsIsHard"] = "false"; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Embedded1 instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Embedded1} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Embedded1(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * @typedef {string} Embedded3 + */ + +/** + * Person represents a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (/** @type {any} */(false)) { + /** + * Titles is optional in JSON + * @member + * @type {Title[] | undefined} + */ + this["Titles"] = undefined; + } + if (!("Names" in $$source)) { + /** + * Names has a + * multiline comment + * @member + * @type {string[]} + */ + this["Names"] = []; + } + if (!("Partner" in $$source)) { + /** + * Partner has a custom and complex JSON key + * @member + * @type {Person | null} + */ + this["Partner"] = null; + } + if (!("Friends" in $$source)) { + /** + * @member + * @type {(Person | null)[]} + */ + this["Friends"] = []; + } + if (!("NamingThingsIsHard" in $$source)) { + /** + * NamingThingsIsHard is a law of programming + * @member + * @type {`${boolean}`} + */ + this["NamingThingsIsHard"] = "false"; + } + if (!("StillThere" in $$source)) { + /** + * StillThereButRenamed should shadow in Person the other field with same depth and no json tag + * @member + * @type {Embedded3 | null} + */ + this["StillThere"] = null; + } + if (!("-" in $$source)) { + /** + * StrangeNumber maps to "-" + * @member + * @type {number} + */ + this["-"] = 0; + } + if (!("Embedded3" in $$source)) { + /** + * Embedded3 should appear with key "Embedded3" + * @member + * @type {Embedded3} + */ + this["Embedded3"] = ""; + } + if (!("StrangerNumber" in $$source)) { + /** + * StrangerNumber is serialized as a string + * @member + * @type {`${number}`} + */ + this["StrangerNumber"] = "0"; + } + if (/** @type {any} */(false)) { + /** + * StrangestString is optional and serialized as a JSON string + * @member + * @type {`"${string}"` | undefined} + */ + this["StrangestString"] = undefined; + } + if (/** @type {any} */(false)) { + /** + * StringStrangest is serialized as a JSON string and optional + * @member + * @type {`"${string}"` | undefined} + */ + this["StringStrangest"] = undefined; + } + if (/** @type {any} */(false)) { + /** + * embedded4 should be optional and appear with key "emb4" + * @member + * @type {embedded4 | undefined} + */ + this["emb4"] = undefined; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType3; + const $$createField3_0 = $$createType4; + const $$createField11_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Titles" in $$parsedSource) { + $$parsedSource["Titles"] = $$createField0_0($$parsedSource["Titles"]); + } + if ("Names" in $$parsedSource) { + $$parsedSource["Names"] = $$createField1_0($$parsedSource["Names"]); + } + if ("Partner" in $$parsedSource) { + $$parsedSource["Partner"] = $$createField2_0($$parsedSource["Partner"]); + } + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField3_0($$parsedSource["Friends"]); + } + if ("emb4" in $$parsedSource) { + $$parsedSource["emb4"] = $$createField11_0($$parsedSource["emb4"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +export class embedded4 { + /** + * Creates a new embedded4 instance. + * @param {Partial} [$$source = {}] - The source object to create the embedded4. + */ + constructor($$source = {}) { + if (!("NamingThingsIsHard" in $$source)) { + /** + * NamingThingsIsHard is a law of programming + * @member + * @type {`${boolean}`} + */ + this["NamingThingsIsHard"] = "false"; + } + if (!("Friends" in $$source)) { + /** + * Friends should not be shadowed in Person as embedded4 is not embedded + * from encoding/json's point of view; + * however, it should be shadowed in Embedded1 + * @member + * @type {boolean} + */ + this["Friends"] = false; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new embedded4 instance from a string or object. + * @param {any} [$$source = {}] + * @returns {embedded4} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new embedded4(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = Person.createFrom; +const $$createType3 = $Create.Nullable($$createType2); +const $$createType4 = $Create.Array($$createType3); +const $$createType5 = embedded4.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js new file mode 100644 index 000000000..fae9496c1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + * @param {string} str + * @param {$models.Person[]} people + * @param {{"AnotherCount": number, "AnotherOne": $models.Person | null}} $2 + * @param {{ [_: `${number}`]: boolean | null }} assoc + * @param {(number | null)[]} $4 + * @param {string[]} other + * @returns {$CancellablePromise<[$models.Person, any, number[]]>} + */ +export function Greet(str, people, $2, assoc, $4, ...other) { + return $Call.ByName("main.GreetService.Greet", str, people, $2, assoc, $4, other).then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[2] = $$createType1($result[2]); + return $result; + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Array($Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js new file mode 100644 index 000000000..82af81baf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js @@ -0,0 +1,38 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Person represents a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person(/** @type {Partial} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js new file mode 100644 index 000000000..651fd2d97 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.StructA, $models.StructC]>} + */ +export function MakeCycles() { + return $Call.ByName("main.GreetService.MakeCycles").then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + return $result; + })); +} + +// Private type creation functions +const $$createType0 = $models.StructA.createFrom; +const $$createType1 = $models.StructC.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js new file mode 100644 index 000000000..0ad0efb4e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + StructA, + StructC, + StructE +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js new file mode 100644 index 000000000..f24f5a2c9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js @@ -0,0 +1,164 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class StructA { + /** + * Creates a new StructA instance. + * @param {Partial} [$$source = {}] - The source object to create the StructA. + */ + constructor($$source = {}) { + if (!("B" in $$source)) { + /** + * @member + * @type {structB | null} + */ + this["B"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructA instance from a string or object. + * @param {any} [$$source = {}] + * @returns {StructA} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("B" in $$parsedSource) { + $$parsedSource["B"] = $$createField0_0($$parsedSource["B"]); + } + return new StructA(/** @type {Partial} */($$parsedSource)); + } +} + +export class StructC { + /** + * Creates a new StructC instance. + * @param {Partial} [$$source = {}] - The source object to create the StructC. + */ + constructor($$source = {}) { + if (!("D" in $$source)) { + /** + * @member + * @type {structD} + */ + this["D"] = (new structD()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructC instance from a string or object. + * @param {any} [$$source = {}] + * @returns {StructC} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType2; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("D" in $$parsedSource) { + $$parsedSource["D"] = $$createField0_0($$parsedSource["D"]); + } + return new StructC(/** @type {Partial} */($$parsedSource)); + } +} + +export class StructE { + /** + * Creates a new StructE instance. + * @param {Partial} [$$source = {}] - The source object to create the StructE. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new StructE instance from a string or object. + * @param {any} [$$source = {}] + * @returns {StructE} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new StructE(/** @type {Partial} */($$parsedSource)); + } +} + +export class structB { + /** + * Creates a new structB instance. + * @param {Partial} [$$source = {}] - The source object to create the structB. + */ + constructor($$source = {}) { + if (!("A" in $$source)) { + /** + * @member + * @type {StructA | null} + */ + this["A"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structB instance from a string or object. + * @param {any} [$$source = {}] + * @returns {structB} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType4; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField0_0($$parsedSource["A"]); + } + return new structB(/** @type {Partial} */($$parsedSource)); + } +} + +export class structD { + /** + * Creates a new structD instance. + * @param {Partial} [$$source = {}] - The source object to create the structD. + */ + constructor($$source = {}) { + if (!("E" in $$source)) { + /** + * @member + * @type {StructE} + */ + this["E"] = (new StructE()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structD instance from a string or object. + * @param {any} [$$source = {}] + * @returns {structD} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("E" in $$parsedSource) { + $$parsedSource["E"] = $$createField0_0($$parsedSource["E"]); + } + return new structD(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = structB.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = structD.createFrom; +const $$createType3 = StructA.createFrom; +const $$createType4 = $Create.Nullable($$createType3); +const $$createType5 = StructE.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js new file mode 100644 index 000000000..b4cbaa216 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js @@ -0,0 +1,65 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]>} + */ +export function MakeCycles() { + return $Call.ByName("main.GreetService.MakeCycles").then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType9($result[1]); + return $result; + })); +} + +// Private type creation functions +var $$createType0 = /** @type {(...args: any[]) => any} */(function $$initCreateType0(...args) { + if ($$createType0 === $$initCreateType0) { + $$createType0 = $$createType3; + } + return $$createType0(...args); +}); +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = $Create.Map($Create.Any, $$createType1); +const $$createType3 = $Create.Array($$createType2); +var $$createType4 = /** @type {(...args: any[]) => any} */(function $$initCreateType4(...args) { + if ($$createType4 === $$initCreateType4) { + $$createType4 = $$createType8; + } + return $$createType4(...args); +}); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Array($Create.Any); +const $$createType7 = $Create.Struct({ + "X": $$createType5, + "Y": $$createType6, +}); +const $$createType8 = $Create.Array($$createType7); +var $$createType9 = /** @type {(...args: any[]) => any} */(function $$initCreateType9(...args) { + if ($$createType9 === $$initCreateType9) { + $$createType9 = $$createType13; + } + return $$createType9(...args); +}); +const $$createType10 = $Create.Nullable($$createType9); +const $$createType11 = $Create.Array($$createType4); +const $$createType12 = $Create.Struct({ + "X": $$createType10, + "Y": $$createType11, +}); +const $$createType13 = $Create.Array($$createType12); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js new file mode 100644 index 000000000..9fc31bf7c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Alias} Alias + */ + +/** + * @typedef {$models.Cyclic} Cyclic + */ + +/** + * @template T + * @typedef {$models.GenericCyclic} GenericCyclic + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js new file mode 100644 index 000000000..47d41f572 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * @typedef {Cyclic | null} Alias + */ + +/** + * @typedef {{ [_: string]: Alias }[]} Cyclic + */ + +/** + * @template T + * @typedef {{"X": GenericCyclic | null, "Y": T[]}[]} GenericCyclic + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js new file mode 100644 index 000000000..972196ce3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Classes!"); +console.log("Hello JS!"); +console.log("Hello JS Classes!"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js new file mode 100644 index 000000000..51a9c6be9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.InternalModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByName("main.InternalService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js new file mode 100644 index 000000000..291a3cecf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js @@ -0,0 +1,69 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * An exported but internal model. + */ +export class InternalModel { + /** + * Creates a new InternalModel instance. + * @param {Partial} [$$source = {}] - The source object to create the InternalModel. + */ + constructor($$source = {}) { + if (!("Field" in $$source)) { + /** + * @member + * @type {string} + */ + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new InternalModel instance from a string or object. + * @param {any} [$$source = {}] + * @returns {InternalModel} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new InternalModel(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * An unexported model. + */ +export class unexportedModel { + /** + * Creates a new unexportedModel instance. + * @param {Partial} [$$source = {}] - The source object to create the unexportedModel. + */ + constructor($$source = {}) { + if (!("Field" in $$source)) { + /** + * @member + * @type {string} + */ + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new unexportedModel instance from a string or object. + * @param {any} [$$source = {}] + * @returns {unexportedModel} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new unexportedModel(/** @type {Partial} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js new file mode 100644 index 000000000..b7e83cfd4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Dummy +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js new file mode 100644 index 000000000..274f4eed4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js @@ -0,0 +1,28 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Dummy { + /** + * Creates a new Dummy instance. + * @param {Partial} [$$source = {}] - The source object to create the Dummy. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new Dummy instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Dummy} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Dummy(/** @type {Partial} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_j.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_j.js new file mode 100644 index 000000000..2166d33b6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_jc.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_jc.js new file mode 100644 index 000000000..338898726 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_jc.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("JS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js new file mode 100644 index 000000000..c59f8e184 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js @@ -0,0 +1,35 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +/** + * @param {string} $0 + * @returns {$CancellablePromise} + */ +function InternalMethod($0) { + return $Call.ByName("main.Service.InternalMethod", $0); +} + +/** + * @param {otherpackage$0.Dummy} $0 + * @returns {$CancellablePromise} + */ +export function VisibleMethod($0) { + return $Call.ByName("main.Service.VisibleMethod", $0); +} + +/** + * @param {string} arg + * @returns {Promise} + */ +export async function CustomMethod(arg) { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js new file mode 100644 index 000000000..724e79e12 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Classes"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js new file mode 100644 index 000000000..b2f9c5edb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_jc.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_jc.js new file mode 100644 index 000000000..ddf4920e5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_jc.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js new file mode 100644 index 000000000..8a93f316f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.unexportedModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByName("main.unexportedService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js new file mode 100644 index 000000000..494240982 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js @@ -0,0 +1,53 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Comment 1. + * @returns {$CancellablePromise} + */ +export function Method1() { + return $Call.ByName("main.GreetService.Method1"); +} + +/** + * Comment 2. + * @returns {$CancellablePromise} + */ +export function Method2() { + return $Call.ByName("main.GreetService.Method2"); +} + +/** + * Comment 3a. + * Comment 3b. + * @returns {$CancellablePromise} + */ +export function Method3() { + return $Call.ByName("main.GreetService.Method3"); +} + +/** + * Comment 4. + * @returns {$CancellablePromise} + */ +export function Method4() { + return $Call.ByName("main.GreetService.Method4"); +} + +/** + * Comment 5. + * @returns {$CancellablePromise} + */ +export function Method5() { + return $Call.ByName("main.GreetService.Method5"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js new file mode 100644 index 000000000..fd18bf8a3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js @@ -0,0 +1,41 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {$models.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByName("main.GreetService.Greet", name, title); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js new file mode 100644 index 000000000..d5d66d4cb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Person, + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js new file mode 100644 index 000000000..2c5df9ee7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js @@ -0,0 +1,98 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Age is an integer with some predefined values + * @typedef {number} Age + */ + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Title" in $$source)) { + /** + * @member + * @type {Title} + */ + this["Title"] = Title.$zero; + } + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Age" in $$source)) { + /** + * @member + * @type {Age} + */ + this["Age"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js new file mode 100644 index 000000000..125c76d13 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {services$0.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByName("main.GreetService.Greet", name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js new file mode 100644 index 000000000..089a8b685 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js new file mode 100644 index 000000000..65ebfa2f7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js @@ -0,0 +1,27 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.js new file mode 100644 index 000000000..ebcbd137f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + SomeClass +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.js new file mode 100644 index 000000000..498ff20b5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.js @@ -0,0 +1,56 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * SomeClass renders as a TS class. + */ +export class SomeClass { + /** + * Creates a new SomeClass instance. + * @param {Partial} [$$source = {}] - The source object to create the SomeClass. + */ + constructor($$source = {}) { + if (!("Field" in $$source)) { + /** + * @member + * @type {string} + */ + this["Field"] = ""; + } + if (!("Meadow" in $$source)) { + /** + * @member + * @type {nobindingshere$0.HowDifferent} + */ + this["Meadow"] = (new nobindingshere$0.HowDifferent()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new SomeClass instance from a string or object. + * @param {any} [$$source = {}] + * @returns {SomeClass} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Meadow" in $$parsedSource) { + $$parsedSource["Meadow"] = $$createField1_0($$parsedSource["Meadow"]); + } + return new SomeClass(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = nobindingshere$0.HowDifferent.createFrom($Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js new file mode 100644 index 000000000..16a94df37 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js new file mode 100644 index 000000000..0ab295133 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js @@ -0,0 +1,57 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {services$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
        } [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
        } */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js new file mode 100644 index 000000000..ba2c614cd --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services.OtherService.Yay").then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js new file mode 100644 index 000000000..16a94df37 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js new file mode 100644 index 000000000..29a95e11e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js @@ -0,0 +1,54 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {other$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = other$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
        } [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
        } */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js new file mode 100644 index 000000000..f6269f0b5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other.OtherService.Yay").then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js new file mode 100644 index 000000000..e6a0e3a74 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js new file mode 100644 index 000000000..6364fa8f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js new file mode 100644 index 000000000..e91d18592 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js @@ -0,0 +1,30 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function GreetWithContext(name) { + return $Call.ByName("main.GreetService.GreetWithContext", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js new file mode 100644 index 000000000..6364fa8f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js new file mode 100644 index 000000000..9fd9745c1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js @@ -0,0 +1,97 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Maps +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * @template S + * @typedef {$models.BasicCstrAlias} BasicCstrAlias + */ + +/** + * @template R + * @typedef {$models.ComparableCstrAlias} ComparableCstrAlias + */ + +/** + * @typedef {$models.EmbeddedCustomInterface} EmbeddedCustomInterface + */ + +/** + * @typedef {$models.EmbeddedOriginalInterface} EmbeddedOriginalInterface + */ + +/** + * @typedef {$models.EmbeddedPointer} EmbeddedPointer + */ + +/** + * @typedef {$models.EmbeddedPointerPtr} EmbeddedPointerPtr + */ + +/** + * @typedef {$models.EmbeddedValue} EmbeddedValue + */ + +/** + * @typedef {$models.EmbeddedValuePtr} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {$models.GoodTildeCstrAlias} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {$models.InterfaceCstrAlias} InterfaceCstrAlias + */ + +/** + * @template X + * @typedef {$models.MixedCstrAlias} MixedCstrAlias + */ + +/** + * @template V + * @typedef {$models.NonBasicCstrAlias} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {$models.PointableCstrAlias} PointableCstrAlias + */ + +/** + * @typedef {$models.PointerAlias} PointerAlias + */ + +/** + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * @typedef {$models.StringAlias} StringAlias + */ + +/** + * @typedef {$models.StringType} StringType + */ + +/** + * @typedef {$models.ValueAlias} ValueAlias + */ + +/** + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js new file mode 100644 index 000000000..1dc5bfc38 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js @@ -0,0 +1,1957 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * @template S + * @typedef {S} BasicCstrAlias + */ + +/** + * @template R + * @typedef {R} ComparableCstrAlias + */ + +/** + * @typedef {string} EmbeddedCustomInterface + */ + +/** + * @typedef {string} EmbeddedOriginalInterface + */ + +/** + * @typedef {string} EmbeddedPointer + */ + +/** + * @typedef {string} EmbeddedPointerPtr + */ + +/** + * @typedef {string} EmbeddedValue + */ + +/** + * @typedef {string} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {U} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {Y} InterfaceCstrAlias + */ + +/** + * @template R,S,T,U,V,W,X,Y,Z + */ +export class Maps { + /** + * Creates a new Maps instance. + * @param {Partial>} [$$source = {}] - The source object to create the Maps. + */ + constructor($$source = {}) { + if (!("Bool" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["Bool"] = {}; + } + if (!("Int" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Int"] = {}; + } + if (!("Uint" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Uint"] = {}; + } + if (!("Float" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["Float"] = {}; + } + if (!("Complex" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["Complex"] = {}; + } + if (!("Byte" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Byte"] = {}; + } + if (!("Rune" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Rune"] = {}; + } + if (!("String" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: string]: number }} + */ + this["String"] = {}; + } + if (!("IntPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IntPtr"] = {}; + } + if (!("UintPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["UintPtr"] = {}; + } + if (!("FloatPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["FloatPtr"] = {}; + } + if (!("ComplexPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["ComplexPtr"] = {}; + } + if (!("StringPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["StringPtr"] = {}; + } + if (!("NTM" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["NTM"] = {}; + } + if (!("NTMPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["NTMPtr"] = {}; + } + if (!("VTM" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueTextMarshaler]: number }} + */ + this["VTM"] = {}; + } + if (!("VTMPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueTextMarshaler]: number }} + */ + this["VTMPtr"] = {}; + } + if (!("PTM" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PTM"] = {}; + } + if (!("PTMPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointerTextMarshaler]: number }} + */ + this["PTMPtr"] = {}; + } + if (!("JTM" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["JTM"] = {}; + } + if (!("JTMPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["JTMPtr"] = {}; + } + if (!("A" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["A"] = {}; + } + if (!("APtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["APtr"] = {}; + } + if (!("TM" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TM"] = {}; + } + if (!("TMPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["TMPtr"] = {}; + } + if (!("CI" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["CI"] = {}; + } + if (!("CIPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["CIPtr"] = {}; + } + if (!("EI" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["EI"] = {}; + } + if (!("EIPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["EIPtr"] = {}; + } + if (!("EV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValue]: number }} + */ + this["EV"] = {}; + } + if (!("EVPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValue]: number }} + */ + this["EVPtr"] = {}; + } + if (!("EVP" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValuePtr]: number }} + */ + this["EVP"] = {}; + } + if (!("EVPPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValuePtr]: number }} + */ + this["EVPPtr"] = {}; + } + if (!("EP" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["EP"] = {}; + } + if (!("EPPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedPointer]: number }} + */ + this["EPPtr"] = {}; + } + if (!("EPP" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedPointerPtr]: number }} + */ + this["EPP"] = {}; + } + if (!("EPPPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedPointerPtr]: number }} + */ + this["EPPPtr"] = {}; + } + if (!("ECI" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedCustomInterface]: number }} + */ + this["ECI"] = {}; + } + if (!("ECIPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedCustomInterface]: number }} + */ + this["ECIPtr"] = {}; + } + if (!("EOI" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedOriginalInterface]: number }} + */ + this["EOI"] = {}; + } + if (!("EOIPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedOriginalInterface]: number }} + */ + this["EOIPtr"] = {}; + } + if (!("WT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["WT"] = {}; + } + if (!("WA" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["WA"] = {}; + } + if (!("ST" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: StringType]: number }} + */ + this["ST"] = {}; + } + if (!("SA" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: StringAlias]: number }} + */ + this["SA"] = {}; + } + if (!("IntT" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["IntT"] = {}; + } + if (!("IntA" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["IntA"] = {}; + } + if (!("VT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VT"] = {}; + } + if (!("VTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VTPtr"] = {}; + } + if (!("VPT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VPT"] = {}; + } + if (!("VPTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VPTPtr"] = {}; + } + if (!("VA" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueAlias]: number }} + */ + this["VA"] = {}; + } + if (!("VAPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueAlias]: number }} + */ + this["VAPtr"] = {}; + } + if (!("VPA" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["VPA"] = {}; + } + if (!("VPAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VPAPtr"] = {}; + } + if (!("PT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PT"] = {}; + } + if (!("PTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PTPtr"] = {}; + } + if (!("PPT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PPT"] = {}; + } + if (!("PPTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PPTPtr"] = {}; + } + if (!("PA" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PA"] = {}; + } + if (!("PAPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointerAlias]: number }} + */ + this["PAPtr"] = {}; + } + if (!("PPA" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["PPA"] = {}; + } + if (!("PPAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PPAPtr"] = {}; + } + if (!("IT" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["IT"] = {}; + } + if (!("ITPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["ITPtr"] = {}; + } + if (!("IPT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPT"] = {}; + } + if (!("IPTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPTPtr"] = {}; + } + if (!("IA" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["IA"] = {}; + } + if (!("IAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IAPtr"] = {}; + } + if (!("IPA" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPA"] = {}; + } + if (!("IPAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPAPtr"] = {}; + } + if (!("TPR" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPR"] = {}; + } + if (!("TPRPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPRPtr"] = {}; + } + if (!("TPS" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPS"] = {}; + } + if (!("TPSPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPSPtr"] = {}; + } + if (!("TPT" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPT"] = {}; + } + if (!("TPTPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPTPtr"] = {}; + } + if (!("TPU" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPU"] = {}; + } + if (!("TPUPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPUPtr"] = {}; + } + if (!("TPV" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPV"] = {}; + } + if (!("TPVPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPVPtr"] = {}; + } + if (!("TPW" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPW"] = {}; + } + if (!("TPWPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPWPtr"] = {}; + } + if (!("TPX" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPX"] = {}; + } + if (!("TPXPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPXPtr"] = {}; + } + if (!("TPY" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPY"] = {}; + } + if (!("TPYPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPYPtr"] = {}; + } + if (!("TPZ" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPZ"] = {}; + } + if (!("TPZPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPZPtr"] = {}; + } + if (!("GAR" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAR"] = {}; + } + if (!("GARPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GARPtr"] = {}; + } + if (!("GAS" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAS"] = {}; + } + if (!("GASPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GASPtr"] = {}; + } + if (!("GAT" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAT"] = {}; + } + if (!("GATPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GATPtr"] = {}; + } + if (!("GAU" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAU"] = {}; + } + if (!("GAUPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAUPtr"] = {}; + } + if (!("GAV" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAV"] = {}; + } + if (!("GAVPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAVPtr"] = {}; + } + if (!("GAW" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAW"] = {}; + } + if (!("GAWPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAWPtr"] = {}; + } + if (!("GAX" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAX"] = {}; + } + if (!("GAXPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAXPtr"] = {}; + } + if (!("GAY" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAY"] = {}; + } + if (!("GAYPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAYPtr"] = {}; + } + if (!("GAZ" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAZ"] = {}; + } + if (!("GAZPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAZPtr"] = {}; + } + if (!("GACi" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["GACi"] = {}; + } + if (!("GACV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ComparableCstrAlias]: number }} + */ + this["GACV"] = {}; + } + if (!("GACP" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GACP"] = {}; + } + if (!("GACiPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GACiPtr"] = {}; + } + if (!("GACVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GACVPtr"] = {}; + } + if (!("GACPPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GACPPtr"] = {}; + } + if (!("GABi" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["GABi"] = {}; + } + if (!("GABs" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: BasicCstrAlias]: number }} + */ + this["GABs"] = {}; + } + if (!("GABiPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GABiPtr"] = {}; + } + if (!("GABT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GABT"] = {}; + } + if (!("GABTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GABTPtr"] = {}; + } + if (!("GAGT" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: GoodTildeCstrAlias]: number }} + */ + this["GAGT"] = {}; + } + if (!("GAGTPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAGTPtr"] = {}; + } + if (!("GANBV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: NonBasicCstrAlias]: number }} + */ + this["GANBV"] = {}; + } + if (!("GANBP" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GANBP"] = {}; + } + if (!("GANBVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GANBVPtr"] = {}; + } + if (!("GANBPPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GANBPPtr"] = {}; + } + if (!("GAPlV1" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointableCstrAlias]: number }} + */ + this["GAPlV1"] = {}; + } + if (!("GAPlV2" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointableCstrAlias]: number }} + */ + this["GAPlV2"] = {}; + } + if (!("GAPlP1" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAPlP1"] = {}; + } + if (!("GAPlP2" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointableCstrAlias]: number }} + */ + this["GAPlP2"] = {}; + } + if (!("GAPlVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPlVPtr"] = {}; + } + if (!("GAPlPPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPlPPtr"] = {}; + } + if (!("GAMi" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["GAMi"] = {}; + } + if (!("GAMS" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: MixedCstrAlias]: number }} + */ + this["GAMS"] = {}; + } + if (!("GAMV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: MixedCstrAlias]: number }} + */ + this["GAMV"] = {}; + } + if (!("GAMSPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAMSPtr"] = {}; + } + if (!("GAMVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAMVPtr"] = {}; + } + if (!("GAII" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAII"] = {}; + } + if (!("GAIV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: InterfaceCstrAlias]: number }} + */ + this["GAIV"] = {}; + } + if (!("GAIP" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAIP"] = {}; + } + if (!("GAIIPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAIIPtr"] = {}; + } + if (!("GAIVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAIVPtr"] = {}; + } + if (!("GAIPPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAIPPtr"] = {}; + } + if (!("GAPrV" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrV"] = {}; + } + if (!("GAPrP" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrP"] = {}; + } + if (!("GAPrVPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrVPtr"] = {}; + } + if (!("GAPrPPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrPPtr"] = {}; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class Maps. + * @template [R=any] + * @template [S=any] + * @template [T=any] + * @template [U=any] + * @template [V=any] + * @template [W=any] + * @template [X=any] + * @template [Y=any] + * @template [Z=any] + * @param {(source: any) => R} $$createParamR + * @param {(source: any) => S} $$createParamS + * @param {(source: any) => T} $$createParamT + * @param {(source: any) => U} $$createParamU + * @param {(source: any) => V} $$createParamV + * @param {(source: any) => W} $$createParamW + * @param {(source: any) => X} $$createParamX + * @param {(source: any) => Y} $$createParamY + * @param {(source: any) => Z} $$createParamZ + * @returns {($$source?: any) => Maps} + */ + static createFrom($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType3; + const $$createField4_0 = $$createType4; + const $$createField5_0 = $$createType5; + const $$createField6_0 = $$createType6; + const $$createField7_0 = $$createType7; + const $$createField8_0 = $$createType8; + const $$createField9_0 = $$createType9; + const $$createField10_0 = $$createType10; + const $$createField11_0 = $$createType11; + const $$createField12_0 = $$createType12; + const $$createField13_0 = $$createType13; + const $$createField14_0 = $$createType14; + const $$createField15_0 = $$createType15; + const $$createField16_0 = $$createType16; + const $$createField17_0 = $$createType17; + const $$createField18_0 = $$createType18; + const $$createField19_0 = $$createType19; + const $$createField20_0 = $$createType20; + const $$createField21_0 = $$createType21; + const $$createField22_0 = $$createType22; + const $$createField23_0 = $$createType23; + const $$createField24_0 = $$createType24; + const $$createField25_0 = $$createType25; + const $$createField26_0 = $$createType26; + const $$createField27_0 = $$createType27; + const $$createField28_0 = $$createType28; + const $$createField29_0 = $$createType29; + const $$createField30_0 = $$createType30; + const $$createField31_0 = $$createType31; + const $$createField32_0 = $$createType32; + const $$createField33_0 = $$createType33; + const $$createField34_0 = $$createType34; + const $$createField35_0 = $$createType35; + const $$createField36_0 = $$createType36; + const $$createField37_0 = $$createType37; + const $$createField38_0 = $$createType38; + const $$createField39_0 = $$createType39; + const $$createField40_0 = $$createType40; + const $$createField41_0 = $$createType41; + const $$createField42_0 = $$createType0; + const $$createField43_0 = $$createType42; + const $$createField44_0 = $$createType7; + const $$createField45_0 = $$createType43; + const $$createField46_0 = $$createType1; + const $$createField47_0 = $$createType44; + const $$createField48_0 = $$createType45; + const $$createField49_0 = $$createType46; + const $$createField50_0 = $$createType47; + const $$createField51_0 = $$createType15; + const $$createField52_0 = $$createType16; + const $$createField53_0 = $$createType16; + const $$createField54_0 = $$createType48; + const $$createField55_0 = $$createType49; + const $$createField56_0 = $$createType50; + const $$createField57_0 = $$createType51; + const $$createField58_0 = $$createType52; + const $$createField59_0 = $$createType17; + const $$createField60_0 = $$createType18; + const $$createField61_0 = $$createType18; + const $$createField62_0 = $$createType53; + const $$createField63_0 = $$createType54; + const $$createField64_0 = $$createType55; + const $$createField65_0 = $$createType56; + const $$createField66_0 = $$createType57; + const $$createField67_0 = $$createType23; + const $$createField68_0 = $$createType24; + const $$createField69_0 = $$createType24; + const $$createField70_0 = $$createType58; + const $$createField71_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField72_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField73_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField74_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField75_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField76_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField77_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField78_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField79_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField80_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField81_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField82_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField83_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField84_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField85_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField86_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField87_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField88_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField89_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField90_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField91_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField92_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField93_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField94_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField95_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField96_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField97_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField98_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField99_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField100_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField101_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField102_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField103_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField104_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField105_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField106_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField107_0 = $$createType1; + const $$createField108_0 = $$createType15; + const $$createField109_0 = $$createType17; + const $$createField110_0 = $$createType8; + const $$createField111_0 = $$createType16; + const $$createField112_0 = $$createType18; + const $$createField113_0 = $$createType1; + const $$createField114_0 = $$createType7; + const $$createField115_0 = $$createType8; + const $$createField116_0 = $$createType77; + const $$createField117_0 = $$createType78; + const $$createField118_0 = $$createType15; + const $$createField119_0 = $$createType16; + const $$createField120_0 = $$createType15; + const $$createField121_0 = $$createType18; + const $$createField122_0 = $$createType16; + const $$createField123_0 = $$createType53; + const $$createField124_0 = $$createType15; + const $$createField125_0 = $$createType16; + const $$createField126_0 = $$createType17; + const $$createField127_0 = $$createType18; + const $$createField128_0 = $$createType16; + const $$createField129_0 = $$createType18; + const $$createField130_0 = $$createType2; + const $$createField131_0 = $$createType42; + const $$createField132_0 = $$createType15; + const $$createField133_0 = $$createType79; + const $$createField134_0 = $$createType16; + const $$createField135_0 = $$createType23; + const $$createField136_0 = $$createType15; + const $$createField137_0 = $$createType18; + const $$createField138_0 = $$createType24; + const $$createField139_0 = $$createType16; + const $$createField140_0 = $$createType53; + const $$createField141_0 = $$createType16; + const $$createField142_0 = $$createType18; + const $$createField143_0 = $$createType48; + const $$createField144_0 = $$createType53; + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Bool" in $$parsedSource) { + $$parsedSource["Bool"] = $$createField0_0($$parsedSource["Bool"]); + } + if ("Int" in $$parsedSource) { + $$parsedSource["Int"] = $$createField1_0($$parsedSource["Int"]); + } + if ("Uint" in $$parsedSource) { + $$parsedSource["Uint"] = $$createField2_0($$parsedSource["Uint"]); + } + if ("Float" in $$parsedSource) { + $$parsedSource["Float"] = $$createField3_0($$parsedSource["Float"]); + } + if ("Complex" in $$parsedSource) { + $$parsedSource["Complex"] = $$createField4_0($$parsedSource["Complex"]); + } + if ("Byte" in $$parsedSource) { + $$parsedSource["Byte"] = $$createField5_0($$parsedSource["Byte"]); + } + if ("Rune" in $$parsedSource) { + $$parsedSource["Rune"] = $$createField6_0($$parsedSource["Rune"]); + } + if ("String" in $$parsedSource) { + $$parsedSource["String"] = $$createField7_0($$parsedSource["String"]); + } + if ("IntPtr" in $$parsedSource) { + $$parsedSource["IntPtr"] = $$createField8_0($$parsedSource["IntPtr"]); + } + if ("UintPtr" in $$parsedSource) { + $$parsedSource["UintPtr"] = $$createField9_0($$parsedSource["UintPtr"]); + } + if ("FloatPtr" in $$parsedSource) { + $$parsedSource["FloatPtr"] = $$createField10_0($$parsedSource["FloatPtr"]); + } + if ("ComplexPtr" in $$parsedSource) { + $$parsedSource["ComplexPtr"] = $$createField11_0($$parsedSource["ComplexPtr"]); + } + if ("StringPtr" in $$parsedSource) { + $$parsedSource["StringPtr"] = $$createField12_0($$parsedSource["StringPtr"]); + } + if ("NTM" in $$parsedSource) { + $$parsedSource["NTM"] = $$createField13_0($$parsedSource["NTM"]); + } + if ("NTMPtr" in $$parsedSource) { + $$parsedSource["NTMPtr"] = $$createField14_0($$parsedSource["NTMPtr"]); + } + if ("VTM" in $$parsedSource) { + $$parsedSource["VTM"] = $$createField15_0($$parsedSource["VTM"]); + } + if ("VTMPtr" in $$parsedSource) { + $$parsedSource["VTMPtr"] = $$createField16_0($$parsedSource["VTMPtr"]); + } + if ("PTM" in $$parsedSource) { + $$parsedSource["PTM"] = $$createField17_0($$parsedSource["PTM"]); + } + if ("PTMPtr" in $$parsedSource) { + $$parsedSource["PTMPtr"] = $$createField18_0($$parsedSource["PTMPtr"]); + } + if ("JTM" in $$parsedSource) { + $$parsedSource["JTM"] = $$createField19_0($$parsedSource["JTM"]); + } + if ("JTMPtr" in $$parsedSource) { + $$parsedSource["JTMPtr"] = $$createField20_0($$parsedSource["JTMPtr"]); + } + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField21_0($$parsedSource["A"]); + } + if ("APtr" in $$parsedSource) { + $$parsedSource["APtr"] = $$createField22_0($$parsedSource["APtr"]); + } + if ("TM" in $$parsedSource) { + $$parsedSource["TM"] = $$createField23_0($$parsedSource["TM"]); + } + if ("TMPtr" in $$parsedSource) { + $$parsedSource["TMPtr"] = $$createField24_0($$parsedSource["TMPtr"]); + } + if ("CI" in $$parsedSource) { + $$parsedSource["CI"] = $$createField25_0($$parsedSource["CI"]); + } + if ("CIPtr" in $$parsedSource) { + $$parsedSource["CIPtr"] = $$createField26_0($$parsedSource["CIPtr"]); + } + if ("EI" in $$parsedSource) { + $$parsedSource["EI"] = $$createField27_0($$parsedSource["EI"]); + } + if ("EIPtr" in $$parsedSource) { + $$parsedSource["EIPtr"] = $$createField28_0($$parsedSource["EIPtr"]); + } + if ("EV" in $$parsedSource) { + $$parsedSource["EV"] = $$createField29_0($$parsedSource["EV"]); + } + if ("EVPtr" in $$parsedSource) { + $$parsedSource["EVPtr"] = $$createField30_0($$parsedSource["EVPtr"]); + } + if ("EVP" in $$parsedSource) { + $$parsedSource["EVP"] = $$createField31_0($$parsedSource["EVP"]); + } + if ("EVPPtr" in $$parsedSource) { + $$parsedSource["EVPPtr"] = $$createField32_0($$parsedSource["EVPPtr"]); + } + if ("EP" in $$parsedSource) { + $$parsedSource["EP"] = $$createField33_0($$parsedSource["EP"]); + } + if ("EPPtr" in $$parsedSource) { + $$parsedSource["EPPtr"] = $$createField34_0($$parsedSource["EPPtr"]); + } + if ("EPP" in $$parsedSource) { + $$parsedSource["EPP"] = $$createField35_0($$parsedSource["EPP"]); + } + if ("EPPPtr" in $$parsedSource) { + $$parsedSource["EPPPtr"] = $$createField36_0($$parsedSource["EPPPtr"]); + } + if ("ECI" in $$parsedSource) { + $$parsedSource["ECI"] = $$createField37_0($$parsedSource["ECI"]); + } + if ("ECIPtr" in $$parsedSource) { + $$parsedSource["ECIPtr"] = $$createField38_0($$parsedSource["ECIPtr"]); + } + if ("EOI" in $$parsedSource) { + $$parsedSource["EOI"] = $$createField39_0($$parsedSource["EOI"]); + } + if ("EOIPtr" in $$parsedSource) { + $$parsedSource["EOIPtr"] = $$createField40_0($$parsedSource["EOIPtr"]); + } + if ("WT" in $$parsedSource) { + $$parsedSource["WT"] = $$createField41_0($$parsedSource["WT"]); + } + if ("WA" in $$parsedSource) { + $$parsedSource["WA"] = $$createField42_0($$parsedSource["WA"]); + } + if ("ST" in $$parsedSource) { + $$parsedSource["ST"] = $$createField43_0($$parsedSource["ST"]); + } + if ("SA" in $$parsedSource) { + $$parsedSource["SA"] = $$createField44_0($$parsedSource["SA"]); + } + if ("IntT" in $$parsedSource) { + $$parsedSource["IntT"] = $$createField45_0($$parsedSource["IntT"]); + } + if ("IntA" in $$parsedSource) { + $$parsedSource["IntA"] = $$createField46_0($$parsedSource["IntA"]); + } + if ("VT" in $$parsedSource) { + $$parsedSource["VT"] = $$createField47_0($$parsedSource["VT"]); + } + if ("VTPtr" in $$parsedSource) { + $$parsedSource["VTPtr"] = $$createField48_0($$parsedSource["VTPtr"]); + } + if ("VPT" in $$parsedSource) { + $$parsedSource["VPT"] = $$createField49_0($$parsedSource["VPT"]); + } + if ("VPTPtr" in $$parsedSource) { + $$parsedSource["VPTPtr"] = $$createField50_0($$parsedSource["VPTPtr"]); + } + if ("VA" in $$parsedSource) { + $$parsedSource["VA"] = $$createField51_0($$parsedSource["VA"]); + } + if ("VAPtr" in $$parsedSource) { + $$parsedSource["VAPtr"] = $$createField52_0($$parsedSource["VAPtr"]); + } + if ("VPA" in $$parsedSource) { + $$parsedSource["VPA"] = $$createField53_0($$parsedSource["VPA"]); + } + if ("VPAPtr" in $$parsedSource) { + $$parsedSource["VPAPtr"] = $$createField54_0($$parsedSource["VPAPtr"]); + } + if ("PT" in $$parsedSource) { + $$parsedSource["PT"] = $$createField55_0($$parsedSource["PT"]); + } + if ("PTPtr" in $$parsedSource) { + $$parsedSource["PTPtr"] = $$createField56_0($$parsedSource["PTPtr"]); + } + if ("PPT" in $$parsedSource) { + $$parsedSource["PPT"] = $$createField57_0($$parsedSource["PPT"]); + } + if ("PPTPtr" in $$parsedSource) { + $$parsedSource["PPTPtr"] = $$createField58_0($$parsedSource["PPTPtr"]); + } + if ("PA" in $$parsedSource) { + $$parsedSource["PA"] = $$createField59_0($$parsedSource["PA"]); + } + if ("PAPtr" in $$parsedSource) { + $$parsedSource["PAPtr"] = $$createField60_0($$parsedSource["PAPtr"]); + } + if ("PPA" in $$parsedSource) { + $$parsedSource["PPA"] = $$createField61_0($$parsedSource["PPA"]); + } + if ("PPAPtr" in $$parsedSource) { + $$parsedSource["PPAPtr"] = $$createField62_0($$parsedSource["PPAPtr"]); + } + if ("IT" in $$parsedSource) { + $$parsedSource["IT"] = $$createField63_0($$parsedSource["IT"]); + } + if ("ITPtr" in $$parsedSource) { + $$parsedSource["ITPtr"] = $$createField64_0($$parsedSource["ITPtr"]); + } + if ("IPT" in $$parsedSource) { + $$parsedSource["IPT"] = $$createField65_0($$parsedSource["IPT"]); + } + if ("IPTPtr" in $$parsedSource) { + $$parsedSource["IPTPtr"] = $$createField66_0($$parsedSource["IPTPtr"]); + } + if ("IA" in $$parsedSource) { + $$parsedSource["IA"] = $$createField67_0($$parsedSource["IA"]); + } + if ("IAPtr" in $$parsedSource) { + $$parsedSource["IAPtr"] = $$createField68_0($$parsedSource["IAPtr"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField69_0($$parsedSource["IPA"]); + } + if ("IPAPtr" in $$parsedSource) { + $$parsedSource["IPAPtr"] = $$createField70_0($$parsedSource["IPAPtr"]); + } + if ("TPR" in $$parsedSource) { + $$parsedSource["TPR"] = $$createField71_0($$parsedSource["TPR"]); + } + if ("TPRPtr" in $$parsedSource) { + $$parsedSource["TPRPtr"] = $$createField72_0($$parsedSource["TPRPtr"]); + } + if ("TPS" in $$parsedSource) { + $$parsedSource["TPS"] = $$createField73_0($$parsedSource["TPS"]); + } + if ("TPSPtr" in $$parsedSource) { + $$parsedSource["TPSPtr"] = $$createField74_0($$parsedSource["TPSPtr"]); + } + if ("TPT" in $$parsedSource) { + $$parsedSource["TPT"] = $$createField75_0($$parsedSource["TPT"]); + } + if ("TPTPtr" in $$parsedSource) { + $$parsedSource["TPTPtr"] = $$createField76_0($$parsedSource["TPTPtr"]); + } + if ("TPU" in $$parsedSource) { + $$parsedSource["TPU"] = $$createField77_0($$parsedSource["TPU"]); + } + if ("TPUPtr" in $$parsedSource) { + $$parsedSource["TPUPtr"] = $$createField78_0($$parsedSource["TPUPtr"]); + } + if ("TPV" in $$parsedSource) { + $$parsedSource["TPV"] = $$createField79_0($$parsedSource["TPV"]); + } + if ("TPVPtr" in $$parsedSource) { + $$parsedSource["TPVPtr"] = $$createField80_0($$parsedSource["TPVPtr"]); + } + if ("TPW" in $$parsedSource) { + $$parsedSource["TPW"] = $$createField81_0($$parsedSource["TPW"]); + } + if ("TPWPtr" in $$parsedSource) { + $$parsedSource["TPWPtr"] = $$createField82_0($$parsedSource["TPWPtr"]); + } + if ("TPX" in $$parsedSource) { + $$parsedSource["TPX"] = $$createField83_0($$parsedSource["TPX"]); + } + if ("TPXPtr" in $$parsedSource) { + $$parsedSource["TPXPtr"] = $$createField84_0($$parsedSource["TPXPtr"]); + } + if ("TPY" in $$parsedSource) { + $$parsedSource["TPY"] = $$createField85_0($$parsedSource["TPY"]); + } + if ("TPYPtr" in $$parsedSource) { + $$parsedSource["TPYPtr"] = $$createField86_0($$parsedSource["TPYPtr"]); + } + if ("TPZ" in $$parsedSource) { + $$parsedSource["TPZ"] = $$createField87_0($$parsedSource["TPZ"]); + } + if ("TPZPtr" in $$parsedSource) { + $$parsedSource["TPZPtr"] = $$createField88_0($$parsedSource["TPZPtr"]); + } + if ("GAR" in $$parsedSource) { + $$parsedSource["GAR"] = $$createField89_0($$parsedSource["GAR"]); + } + if ("GARPtr" in $$parsedSource) { + $$parsedSource["GARPtr"] = $$createField90_0($$parsedSource["GARPtr"]); + } + if ("GAS" in $$parsedSource) { + $$parsedSource["GAS"] = $$createField91_0($$parsedSource["GAS"]); + } + if ("GASPtr" in $$parsedSource) { + $$parsedSource["GASPtr"] = $$createField92_0($$parsedSource["GASPtr"]); + } + if ("GAT" in $$parsedSource) { + $$parsedSource["GAT"] = $$createField93_0($$parsedSource["GAT"]); + } + if ("GATPtr" in $$parsedSource) { + $$parsedSource["GATPtr"] = $$createField94_0($$parsedSource["GATPtr"]); + } + if ("GAU" in $$parsedSource) { + $$parsedSource["GAU"] = $$createField95_0($$parsedSource["GAU"]); + } + if ("GAUPtr" in $$parsedSource) { + $$parsedSource["GAUPtr"] = $$createField96_0($$parsedSource["GAUPtr"]); + } + if ("GAV" in $$parsedSource) { + $$parsedSource["GAV"] = $$createField97_0($$parsedSource["GAV"]); + } + if ("GAVPtr" in $$parsedSource) { + $$parsedSource["GAVPtr"] = $$createField98_0($$parsedSource["GAVPtr"]); + } + if ("GAW" in $$parsedSource) { + $$parsedSource["GAW"] = $$createField99_0($$parsedSource["GAW"]); + } + if ("GAWPtr" in $$parsedSource) { + $$parsedSource["GAWPtr"] = $$createField100_0($$parsedSource["GAWPtr"]); + } + if ("GAX" in $$parsedSource) { + $$parsedSource["GAX"] = $$createField101_0($$parsedSource["GAX"]); + } + if ("GAXPtr" in $$parsedSource) { + $$parsedSource["GAXPtr"] = $$createField102_0($$parsedSource["GAXPtr"]); + } + if ("GAY" in $$parsedSource) { + $$parsedSource["GAY"] = $$createField103_0($$parsedSource["GAY"]); + } + if ("GAYPtr" in $$parsedSource) { + $$parsedSource["GAYPtr"] = $$createField104_0($$parsedSource["GAYPtr"]); + } + if ("GAZ" in $$parsedSource) { + $$parsedSource["GAZ"] = $$createField105_0($$parsedSource["GAZ"]); + } + if ("GAZPtr" in $$parsedSource) { + $$parsedSource["GAZPtr"] = $$createField106_0($$parsedSource["GAZPtr"]); + } + if ("GACi" in $$parsedSource) { + $$parsedSource["GACi"] = $$createField107_0($$parsedSource["GACi"]); + } + if ("GACV" in $$parsedSource) { + $$parsedSource["GACV"] = $$createField108_0($$parsedSource["GACV"]); + } + if ("GACP" in $$parsedSource) { + $$parsedSource["GACP"] = $$createField109_0($$parsedSource["GACP"]); + } + if ("GACiPtr" in $$parsedSource) { + $$parsedSource["GACiPtr"] = $$createField110_0($$parsedSource["GACiPtr"]); + } + if ("GACVPtr" in $$parsedSource) { + $$parsedSource["GACVPtr"] = $$createField111_0($$parsedSource["GACVPtr"]); + } + if ("GACPPtr" in $$parsedSource) { + $$parsedSource["GACPPtr"] = $$createField112_0($$parsedSource["GACPPtr"]); + } + if ("GABi" in $$parsedSource) { + $$parsedSource["GABi"] = $$createField113_0($$parsedSource["GABi"]); + } + if ("GABs" in $$parsedSource) { + $$parsedSource["GABs"] = $$createField114_0($$parsedSource["GABs"]); + } + if ("GABiPtr" in $$parsedSource) { + $$parsedSource["GABiPtr"] = $$createField115_0($$parsedSource["GABiPtr"]); + } + if ("GABT" in $$parsedSource) { + $$parsedSource["GABT"] = $$createField116_0($$parsedSource["GABT"]); + } + if ("GABTPtr" in $$parsedSource) { + $$parsedSource["GABTPtr"] = $$createField117_0($$parsedSource["GABTPtr"]); + } + if ("GAGT" in $$parsedSource) { + $$parsedSource["GAGT"] = $$createField118_0($$parsedSource["GAGT"]); + } + if ("GAGTPtr" in $$parsedSource) { + $$parsedSource["GAGTPtr"] = $$createField119_0($$parsedSource["GAGTPtr"]); + } + if ("GANBV" in $$parsedSource) { + $$parsedSource["GANBV"] = $$createField120_0($$parsedSource["GANBV"]); + } + if ("GANBP" in $$parsedSource) { + $$parsedSource["GANBP"] = $$createField121_0($$parsedSource["GANBP"]); + } + if ("GANBVPtr" in $$parsedSource) { + $$parsedSource["GANBVPtr"] = $$createField122_0($$parsedSource["GANBVPtr"]); + } + if ("GANBPPtr" in $$parsedSource) { + $$parsedSource["GANBPPtr"] = $$createField123_0($$parsedSource["GANBPPtr"]); + } + if ("GAPlV1" in $$parsedSource) { + $$parsedSource["GAPlV1"] = $$createField124_0($$parsedSource["GAPlV1"]); + } + if ("GAPlV2" in $$parsedSource) { + $$parsedSource["GAPlV2"] = $$createField125_0($$parsedSource["GAPlV2"]); + } + if ("GAPlP1" in $$parsedSource) { + $$parsedSource["GAPlP1"] = $$createField126_0($$parsedSource["GAPlP1"]); + } + if ("GAPlP2" in $$parsedSource) { + $$parsedSource["GAPlP2"] = $$createField127_0($$parsedSource["GAPlP2"]); + } + if ("GAPlVPtr" in $$parsedSource) { + $$parsedSource["GAPlVPtr"] = $$createField128_0($$parsedSource["GAPlVPtr"]); + } + if ("GAPlPPtr" in $$parsedSource) { + $$parsedSource["GAPlPPtr"] = $$createField129_0($$parsedSource["GAPlPPtr"]); + } + if ("GAMi" in $$parsedSource) { + $$parsedSource["GAMi"] = $$createField130_0($$parsedSource["GAMi"]); + } + if ("GAMS" in $$parsedSource) { + $$parsedSource["GAMS"] = $$createField131_0($$parsedSource["GAMS"]); + } + if ("GAMV" in $$parsedSource) { + $$parsedSource["GAMV"] = $$createField132_0($$parsedSource["GAMV"]); + } + if ("GAMSPtr" in $$parsedSource) { + $$parsedSource["GAMSPtr"] = $$createField133_0($$parsedSource["GAMSPtr"]); + } + if ("GAMVPtr" in $$parsedSource) { + $$parsedSource["GAMVPtr"] = $$createField134_0($$parsedSource["GAMVPtr"]); + } + if ("GAII" in $$parsedSource) { + $$parsedSource["GAII"] = $$createField135_0($$parsedSource["GAII"]); + } + if ("GAIV" in $$parsedSource) { + $$parsedSource["GAIV"] = $$createField136_0($$parsedSource["GAIV"]); + } + if ("GAIP" in $$parsedSource) { + $$parsedSource["GAIP"] = $$createField137_0($$parsedSource["GAIP"]); + } + if ("GAIIPtr" in $$parsedSource) { + $$parsedSource["GAIIPtr"] = $$createField138_0($$parsedSource["GAIIPtr"]); + } + if ("GAIVPtr" in $$parsedSource) { + $$parsedSource["GAIVPtr"] = $$createField139_0($$parsedSource["GAIVPtr"]); + } + if ("GAIPPtr" in $$parsedSource) { + $$parsedSource["GAIPPtr"] = $$createField140_0($$parsedSource["GAIPPtr"]); + } + if ("GAPrV" in $$parsedSource) { + $$parsedSource["GAPrV"] = $$createField141_0($$parsedSource["GAPrV"]); + } + if ("GAPrP" in $$parsedSource) { + $$parsedSource["GAPrP"] = $$createField142_0($$parsedSource["GAPrP"]); + } + if ("GAPrVPtr" in $$parsedSource) { + $$parsedSource["GAPrVPtr"] = $$createField143_0($$parsedSource["GAPrVPtr"]); + } + if ("GAPrPPtr" in $$parsedSource) { + $$parsedSource["GAPrPPtr"] = $$createField144_0($$parsedSource["GAPrPPtr"]); + } + return new Maps(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +/** + * @template X + * @typedef {X} MixedCstrAlias + */ + +/** + * @template V + * @typedef {V} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {W} PointableCstrAlias + */ + +/** + * @typedef {PointerTextMarshaler} PointerAlias + */ + +/** + * @typedef {string} PointerTextMarshaler + */ + +/** + * @typedef {string} StringAlias + */ + +/** + * @typedef {string} StringType + */ + +/** + * @typedef {ValueTextMarshaler} ValueAlias + */ + +/** + * @typedef {string} ValueTextMarshaler + */ + +// Private type creation functions +const $$createType0 = $Create.Map($Create.Any, $Create.Any); +const $$createType1 = $Create.Map($Create.Any, $Create.Any); +const $$createType2 = $Create.Map($Create.Any, $Create.Any); +const $$createType3 = $Create.Map($Create.Any, $Create.Any); +const $$createType4 = $Create.Map($Create.Any, $Create.Any); +const $$createType5 = $Create.Map($Create.Any, $Create.Any); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Map($Create.Any, $Create.Any); +const $$createType8 = $Create.Map($Create.Any, $Create.Any); +const $$createType9 = $Create.Map($Create.Any, $Create.Any); +const $$createType10 = $Create.Map($Create.Any, $Create.Any); +const $$createType11 = $Create.Map($Create.Any, $Create.Any); +const $$createType12 = $Create.Map($Create.Any, $Create.Any); +const $$createType13 = $Create.Map($Create.Any, $Create.Any); +const $$createType14 = $Create.Map($Create.Any, $Create.Any); +const $$createType15 = $Create.Map($Create.Any, $Create.Any); +const $$createType16 = $Create.Map($Create.Any, $Create.Any); +const $$createType17 = $Create.Map($Create.Any, $Create.Any); +const $$createType18 = $Create.Map($Create.Any, $Create.Any); +const $$createType19 = $Create.Map($Create.Any, $Create.Any); +const $$createType20 = $Create.Map($Create.Any, $Create.Any); +const $$createType21 = $Create.Map($Create.Any, $Create.Any); +const $$createType22 = $Create.Map($Create.Any, $Create.Any); +const $$createType23 = $Create.Map($Create.Any, $Create.Any); +const $$createType24 = $Create.Map($Create.Any, $Create.Any); +const $$createType25 = $Create.Map($Create.Any, $Create.Any); +const $$createType26 = $Create.Map($Create.Any, $Create.Any); +const $$createType27 = $Create.Map($Create.Any, $Create.Any); +const $$createType28 = $Create.Map($Create.Any, $Create.Any); +const $$createType29 = $Create.Map($Create.Any, $Create.Any); +const $$createType30 = $Create.Map($Create.Any, $Create.Any); +const $$createType31 = $Create.Map($Create.Any, $Create.Any); +const $$createType32 = $Create.Map($Create.Any, $Create.Any); +const $$createType33 = $Create.Map($Create.Any, $Create.Any); +const $$createType34 = $Create.Map($Create.Any, $Create.Any); +const $$createType35 = $Create.Map($Create.Any, $Create.Any); +const $$createType36 = $Create.Map($Create.Any, $Create.Any); +const $$createType37 = $Create.Map($Create.Any, $Create.Any); +const $$createType38 = $Create.Map($Create.Any, $Create.Any); +const $$createType39 = $Create.Map($Create.Any, $Create.Any); +const $$createType40 = $Create.Map($Create.Any, $Create.Any); +const $$createType41 = $Create.Map($Create.Any, $Create.Any); +const $$createType42 = $Create.Map($Create.Any, $Create.Any); +const $$createType43 = $Create.Map($Create.Any, $Create.Any); +const $$createType44 = $Create.Map($Create.Any, $Create.Any); +const $$createType45 = $Create.Map($Create.Any, $Create.Any); +const $$createType46 = $Create.Map($Create.Any, $Create.Any); +const $$createType47 = $Create.Map($Create.Any, $Create.Any); +const $$createType48 = $Create.Map($Create.Any, $Create.Any); +const $$createType49 = $Create.Map($Create.Any, $Create.Any); +const $$createType50 = $Create.Map($Create.Any, $Create.Any); +const $$createType51 = $Create.Map($Create.Any, $Create.Any); +const $$createType52 = $Create.Map($Create.Any, $Create.Any); +const $$createType53 = $Create.Map($Create.Any, $Create.Any); +const $$createType54 = $Create.Map($Create.Any, $Create.Any); +const $$createType55 = $Create.Map($Create.Any, $Create.Any); +const $$createType56 = $Create.Map($Create.Any, $Create.Any); +const $$createType57 = $Create.Map($Create.Any, $Create.Any); +const $$createType58 = $Create.Map($Create.Any, $Create.Any); +const $$createType59 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType60 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType61 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType62 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType63 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType64 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType65 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType66 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType67 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType68 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType69 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType70 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType71 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType72 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType73 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType74 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType75 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType76 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType77 = $Create.Map($Create.Any, $Create.Any); +const $$createType78 = $Create.Map($Create.Any, $Create.Any); +const $$createType79 = $Create.Map($Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js new file mode 100644 index 000000000..cc98f3a89 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>>} + */ +export function Method() { + return $Call.ByName("main.Service.Method").then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Maps.createFrom($Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js new file mode 100644 index 000000000..8f525252e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js @@ -0,0 +1,116 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Data, + ImplicitNonMarshaler, + NonMarshaler +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * any + * @typedef {$models.AliasJsonMarshaler} AliasJsonMarshaler + */ + +/** + * any + * @typedef {$models.AliasMarshaler} AliasMarshaler + */ + +/** + * struct{} + * @typedef {$models.AliasNonMarshaler} AliasNonMarshaler + */ + +/** + * string + * @typedef {$models.AliasTextMarshaler} AliasTextMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitJsonButText} ImplicitJsonButText + */ + +/** + * any + * @typedef {$models.ImplicitJsonMarshaler} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitMarshaler} ImplicitMarshaler + */ + +/** + * string + * @typedef {$models.ImplicitNonJson} ImplicitNonJson + */ + +/** + * any + * @typedef {$models.ImplicitNonText} ImplicitNonText + */ + +/** + * any + * @typedef {$models.ImplicitTextButJson} ImplicitTextButJson + */ + +/** + * string + * @typedef {$models.ImplicitTextMarshaler} ImplicitTextMarshaler + */ + +/** + * any + * @typedef {$models.PointerJsonMarshaler} PointerJsonMarshaler + */ + +/** + * any + * @typedef {$models.PointerMarshaler} PointerMarshaler + */ + +/** + * string + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingJsonMarshaler} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingMarshaler} UnderlyingMarshaler + */ + +/** + * string + * @typedef {$models.UnderlyingTextMarshaler} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {$models.ValueJsonMarshaler} ValueJsonMarshaler + */ + +/** + * any + * @typedef {$models.ValueMarshaler} ValueMarshaler + */ + +/** + * string + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js new file mode 100644 index 000000000..77fe552ea --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js @@ -0,0 +1,630 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + * @typedef {any} AliasJsonMarshaler + */ + +/** + * any + * @typedef {any} AliasMarshaler + */ + +/** + * struct{} + * @typedef { { + * } } AliasNonMarshaler + */ + +/** + * string + * @typedef {string} AliasTextMarshaler + */ + +export class Data { + /** + * Creates a new Data instance. + * @param {Partial} [$$source = {}] - The source object to create the Data. + */ + constructor($$source = {}) { + if (!("NM" in $$source)) { + /** + * @member + * @type {NonMarshaler} + */ + this["NM"] = (new NonMarshaler()); + } + if (!("NMPtr" in $$source)) { + /** + * NonMarshaler | null + * @member + * @type {NonMarshaler | null} + */ + this["NMPtr"] = null; + } + if (!("VJM" in $$source)) { + /** + * @member + * @type {ValueJsonMarshaler} + */ + this["VJM"] = null; + } + if (!("VJMPtr" in $$source)) { + /** + * ValueJsonMarshaler | null + * @member + * @type {ValueJsonMarshaler | null} + */ + this["VJMPtr"] = null; + } + if (!("PJM" in $$source)) { + /** + * @member + * @type {PointerJsonMarshaler} + */ + this["PJM"] = null; + } + if (!("PJMPtr" in $$source)) { + /** + * PointerJsonMarshaler | null + * @member + * @type {PointerJsonMarshaler | null} + */ + this["PJMPtr"] = null; + } + if (!("VTM" in $$source)) { + /** + * @member + * @type {ValueTextMarshaler} + */ + this["VTM"] = ""; + } + if (!("VTMPtr" in $$source)) { + /** + * ValueTextMarshaler | null + * @member + * @type {ValueTextMarshaler | null} + */ + this["VTMPtr"] = null; + } + if (!("PTM" in $$source)) { + /** + * @member + * @type {PointerTextMarshaler} + */ + this["PTM"] = ""; + } + if (!("PTMPtr" in $$source)) { + /** + * PointerTextMarshaler | null + * @member + * @type {PointerTextMarshaler | null} + */ + this["PTMPtr"] = null; + } + if (!("VM" in $$source)) { + /** + * @member + * @type {ValueMarshaler} + */ + this["VM"] = null; + } + if (!("VMPtr" in $$source)) { + /** + * ValueMarshaler | null + * @member + * @type {ValueMarshaler | null} + */ + this["VMPtr"] = null; + } + if (!("PM" in $$source)) { + /** + * @member + * @type {PointerMarshaler} + */ + this["PM"] = null; + } + if (!("PMPtr" in $$source)) { + /** + * PointerMarshaler | null + * @member + * @type {PointerMarshaler | null} + */ + this["PMPtr"] = null; + } + if (!("UJM" in $$source)) { + /** + * @member + * @type {UnderlyingJsonMarshaler} + */ + this["UJM"] = null; + } + if (!("UJMPtr" in $$source)) { + /** + * UnderlyingJsonMarshaler | null + * @member + * @type {UnderlyingJsonMarshaler | null} + */ + this["UJMPtr"] = null; + } + if (!("UTM" in $$source)) { + /** + * @member + * @type {UnderlyingTextMarshaler} + */ + this["UTM"] = ""; + } + if (!("UTMPtr" in $$source)) { + /** + * UnderlyingTextMarshaler | null + * @member + * @type {UnderlyingTextMarshaler | null} + */ + this["UTMPtr"] = null; + } + if (!("UM" in $$source)) { + /** + * @member + * @type {UnderlyingMarshaler} + */ + this["UM"] = null; + } + if (!("UMPtr" in $$source)) { + /** + * UnderlyingMarshaler | null + * @member + * @type {UnderlyingMarshaler | null} + */ + this["UMPtr"] = null; + } + if (!("JM" in $$source)) { + /** + * any + * @member + * @type {any} + */ + this["JM"] = null; + } + if (!("JMPtr" in $$source)) { + /** + * any | null + * @member + * @type {any | null} + */ + this["JMPtr"] = null; + } + if (!("TM" in $$source)) { + /** + * string + * @member + * @type {string} + */ + this["TM"] = ""; + } + if (!("TMPtr" in $$source)) { + /** + * string | null + * @member + * @type {string | null} + */ + this["TMPtr"] = null; + } + if (!("CJM" in $$source)) { + /** + * any + * @member + * @type {any} + */ + this["CJM"] = null; + } + if (!("CJMPtr" in $$source)) { + /** + * any | null + * @member + * @type {any | null} + */ + this["CJMPtr"] = null; + } + if (!("CTM" in $$source)) { + /** + * string + * @member + * @type {string} + */ + this["CTM"] = ""; + } + if (!("CTMPtr" in $$source)) { + /** + * string | null + * @member + * @type {string | null} + */ + this["CTMPtr"] = null; + } + if (!("CM" in $$source)) { + /** + * any + * @member + * @type {any} + */ + this["CM"] = null; + } + if (!("CMPtr" in $$source)) { + /** + * any | null + * @member + * @type {any | null} + */ + this["CMPtr"] = null; + } + if (!("ANM" in $$source)) { + /** + * @member + * @type {AliasNonMarshaler} + */ + this["ANM"] = {}; + } + if (!("ANMPtr" in $$source)) { + /** + * AliasNonMarshaler | null + * @member + * @type {AliasNonMarshaler | null} + */ + this["ANMPtr"] = null; + } + if (!("AJM" in $$source)) { + /** + * @member + * @type {AliasJsonMarshaler} + */ + this["AJM"] = null; + } + if (!("AJMPtr" in $$source)) { + /** + * AliasJsonMarshaler | null + * @member + * @type {AliasJsonMarshaler | null} + */ + this["AJMPtr"] = null; + } + if (!("ATM" in $$source)) { + /** + * @member + * @type {AliasTextMarshaler} + */ + this["ATM"] = ""; + } + if (!("ATMPtr" in $$source)) { + /** + * AliasTextMarshaler | null + * @member + * @type {AliasTextMarshaler | null} + */ + this["ATMPtr"] = null; + } + if (!("AM" in $$source)) { + /** + * @member + * @type {AliasMarshaler} + */ + this["AM"] = null; + } + if (!("AMPtr" in $$source)) { + /** + * AliasMarshaler | null + * @member + * @type {AliasMarshaler | null} + */ + this["AMPtr"] = null; + } + if (!("ImJM" in $$source)) { + /** + * @member + * @type {ImplicitJsonMarshaler} + */ + this["ImJM"] = null; + } + if (!("ImJMPtr" in $$source)) { + /** + * ImplicitJsonMarshaler | null + * @member + * @type {ImplicitJsonMarshaler | null} + */ + this["ImJMPtr"] = null; + } + if (!("ImTM" in $$source)) { + /** + * @member + * @type {ImplicitTextMarshaler} + */ + this["ImTM"] = ""; + } + if (!("ImTMPtr" in $$source)) { + /** + * ImplicitTextMarshaler | null + * @member + * @type {ImplicitTextMarshaler | null} + */ + this["ImTMPtr"] = null; + } + if (!("ImM" in $$source)) { + /** + * @member + * @type {ImplicitMarshaler} + */ + this["ImM"] = null; + } + if (!("ImMPtr" in $$source)) { + /** + * ImplicitMarshaler | null + * @member + * @type {ImplicitMarshaler | null} + */ + this["ImMPtr"] = null; + } + if (!("ImNJ" in $$source)) { + /** + * @member + * @type {ImplicitNonJson} + */ + this["ImNJ"] = ""; + } + if (!("ImNJPtr" in $$source)) { + /** + * ImplicitNonJson | null + * @member + * @type {ImplicitNonJson | null} + */ + this["ImNJPtr"] = null; + } + if (!("ImNT" in $$source)) { + /** + * @member + * @type {ImplicitNonText} + */ + this["ImNT"] = null; + } + if (!("ImNTPtr" in $$source)) { + /** + * ImplicitNonText | null + * @member + * @type {ImplicitNonText | null} + */ + this["ImNTPtr"] = null; + } + if (!("ImNM" in $$source)) { + /** + * @member + * @type {ImplicitNonMarshaler} + */ + this["ImNM"] = (new ImplicitNonMarshaler()); + } + if (!("ImNMPtr" in $$source)) { + /** + * ImplicitNonMarshaler | null + * @member + * @type {ImplicitNonMarshaler | null} + */ + this["ImNMPtr"] = null; + } + if (!("ImJbT" in $$source)) { + /** + * @member + * @type {ImplicitJsonButText} + */ + this["ImJbT"] = null; + } + if (!("ImJbTPtr" in $$source)) { + /** + * ImplicitJsonButText | null + * @member + * @type {ImplicitJsonButText | null} + */ + this["ImJbTPtr"] = null; + } + if (!("ImTbJ" in $$source)) { + /** + * @member + * @type {ImplicitTextButJson} + */ + this["ImTbJ"] = null; + } + if (!("ImTbJPtr" in $$source)) { + /** + * ImplicitTextButJson | null + * @member + * @type {ImplicitTextButJson | null} + */ + this["ImTbJPtr"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Data instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Data} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField48_0 = $$createType2; + const $$createField49_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("NM" in $$parsedSource) { + $$parsedSource["NM"] = $$createField0_0($$parsedSource["NM"]); + } + if ("NMPtr" in $$parsedSource) { + $$parsedSource["NMPtr"] = $$createField1_0($$parsedSource["NMPtr"]); + } + if ("ImNM" in $$parsedSource) { + $$parsedSource["ImNM"] = $$createField48_0($$parsedSource["ImNM"]); + } + if ("ImNMPtr" in $$parsedSource) { + $$parsedSource["ImNMPtr"] = $$createField49_0($$parsedSource["ImNMPtr"]); + } + return new Data(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * any + * @typedef {any} ImplicitJsonButText + */ + +/** + * any + * @typedef {any} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {any} ImplicitMarshaler + */ + +/** + * string + * @typedef {string} ImplicitNonJson + */ + +/** + * class{ Marshaler, TextMarshaler } + */ +export class ImplicitNonMarshaler { + /** + * Creates a new ImplicitNonMarshaler instance. + * @param {Partial} [$$source = {}] - The source object to create the ImplicitNonMarshaler. + */ + constructor($$source = {}) { + if (!("Marshaler" in $$source)) { + /** + * @member + * @type {json$0.Marshaler} + */ + this["Marshaler"] = null; + } + if (!("TextMarshaler" in $$source)) { + /** + * @member + * @type {encoding$0.TextMarshaler} + */ + this["TextMarshaler"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new ImplicitNonMarshaler instance from a string or object. + * @param {any} [$$source = {}] + * @returns {ImplicitNonMarshaler} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new ImplicitNonMarshaler(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * any + * @typedef {any} ImplicitNonText + */ + +/** + * any + * @typedef {any} ImplicitTextButJson + */ + +/** + * string + * @typedef {string} ImplicitTextMarshaler + */ + +/** + * class {} + */ +export class NonMarshaler { + /** + * Creates a new NonMarshaler instance. + * @param {Partial} [$$source = {}] - The source object to create the NonMarshaler. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new NonMarshaler instance from a string or object. + * @param {any} [$$source = {}] + * @returns {NonMarshaler} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new NonMarshaler(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * any + * @typedef {any} PointerJsonMarshaler + */ + +/** + * any + * @typedef {any} PointerMarshaler + */ + +/** + * string + * @typedef {string} PointerTextMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingMarshaler + */ + +/** + * string + * @typedef {string} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {any} ValueJsonMarshaler + */ + +/** + * any + * @typedef {any} ValueMarshaler + */ + +/** + * string + * @typedef {string} ValueTextMarshaler + */ + +// Private type creation functions +const $$createType0 = NonMarshaler.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = ImplicitNonMarshaler.createFrom; +const $$createType3 = $Create.Nullable($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js new file mode 100644 index 000000000..e6c23e4f0 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Data>} + */ +export function Method() { + return $Call.ByName("main.Service.Method").then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Data.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js new file mode 100644 index 000000000..9be766c03 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +export { + HowDifferent, + Impersonator, + Person, + PrivatePerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js new file mode 100644 index 000000000..b42e223fa --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js @@ -0,0 +1,181 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + * @template How + */ +export class HowDifferent { + /** + * Creates a new HowDifferent instance. + * @param {Partial>} [$$source = {}] - The source object to create the HowDifferent. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * They have a name as well. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + /** + * But they may have many differences. + * @member + * @type {{ [_: string]: How }[]} + */ + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class HowDifferent. + * @template [How=any] + * @param {(source: any) => How} $$createParamHow + * @returns {($$source?: any) => HowDifferent} + */ + static createFrom($$createParamHow) { + const $$createField1_0 = $$createType1($$createParamHow); + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new HowDifferent(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +/** + * Impersonator gets their fields from other people. + */ +export const Impersonator = other$0.OtherPerson; + +/** + * Impersonator gets their fields from other people. + * @typedef {other$0.OtherPerson} Impersonator + */ + +/** + * Person is not a number. + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * They have a name. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + /** + * Exactly 4 sketchy friends. + * @member + * @type {Impersonator[]} + */ + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField1_0($$parsedSource["Friends"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +export class personImpl { + /** + * Creates a new personImpl instance. + * @param {Partial} [$$source = {}] - The source object to create the personImpl. + */ + constructor($$source = {}) { + if (!("Nickname" in $$source)) { + /** + * Nickname conceals a person's identity. + * @member + * @type {string} + */ + this["Nickname"] = ""; + } + if (!("Name" in $$source)) { + /** + * They have a name. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + /** + * Exactly 4 sketchy friends. + * @member + * @type {Impersonator[]} + */ + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new personImpl instance from a string or object. + * @param {any} [$$source = {}] + * @returns {personImpl} + */ + static createFrom($$source = {}) { + const $$createField2_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField2_0($$parsedSource["Friends"]); + } + return new personImpl(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export const PrivatePerson = personImpl; + +/** + * PrivatePerson gets their fields from hidden sources. + * @typedef {personImpl} PrivatePerson + */ + +// Private type creation functions +const $$createType0 = /** @type {(...args: any[]) => any} */(($$createParamHow) => $Create.Map($Create.Any, $$createParamHow)); +const $$createType1 = /** @type {(...args: any[]) => any} */(($$createParamHow) => $Create.Array($$createType0($$createParamHow))); +const $$createType2 = other$0.OtherPerson.createFrom($Create.Any); +const $$createType3 = $Create.Array($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.js new file mode 100644 index 000000000..c3eabd022 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * StringPtr is a nullable string. + * @typedef {$models.StringPtr} StringPtr + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.js new file mode 100644 index 000000000..b4fdacf8e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * StringPtr is a nullable string. + * @typedef {string | null} StringPtr + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js new file mode 100644 index 000000000..db4e64147 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +export { + OtherPerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js new file mode 100644 index 000000000..89992cacf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js @@ -0,0 +1,60 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * OtherPerson is like a person, but different. + * @template T + */ +export class OtherPerson { + /** + * Creates a new OtherPerson instance. + * @param {Partial>} [$$source = {}] - The source object to create the OtherPerson. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * They have a name as well. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + /** + * But they may have many differences. + * @member + * @type {T[]} + */ + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class OtherPerson. + * @template [T=any] + * @param {(source: any) => T} $$createParamT + * @returns {($$source?: any) => OtherPerson} + */ + static createFrom($$createParamT) { + const $$createField1_0 = $$createType0($$createParamT); + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new OtherPerson(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +// Private type creation functions +const $$createType0 = /** @type {(...args: any[]) => any} */(($$createParamT) => $Create.Array($$createParamT)); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js new file mode 100644 index 000000000..d2819bc4b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other.OtherMethods.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js new file mode 100644 index 000000000..35bc3579d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js @@ -0,0 +1,42 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOne").then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + })); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOtherOne"); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.HowDifferent.createFrom($Create.Any); +const $$createType2 = $models.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js new file mode 100644 index 000000000..0497b16bc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("main.EmbedOther.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js new file mode 100644 index 000000000..903e4153f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js @@ -0,0 +1,42 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByName("main.EmbedService.LikeThisOne").then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + })); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("main.EmbedService.LikeThisOtherOne"); +} + +// Private type creation functions +const $$createType0 = nobindingshere$0.Person.createFrom; +const $$createType1 = nobindingshere$0.HowDifferent.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js new file mode 100644 index 000000000..fe683f548 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} $0 + * @returns {$CancellablePromise} + */ +export function Greet($0) { + return $Call.ByName("main.GreetService.Greet", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js new file mode 100644 index 000000000..734fb02e7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js new file mode 100644 index 000000000..e6a0e3a74 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js new file mode 100644 index 000000000..69652f626 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js new file mode 100644 index 000000000..e6a0e3a74 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js new file mode 100644 index 000000000..69652f626 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js new file mode 100644 index 000000000..16a94df37 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js new file mode 100644 index 000000000..04771d2ca --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js @@ -0,0 +1,54 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {services$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
        } [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
        } */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js new file mode 100644 index 000000000..327b15c9c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services.OtherService.Yay").then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js new file mode 100644 index 000000000..d811d97ce --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js @@ -0,0 +1,379 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +/** + * @param {{ [_: `${number}`]: number }} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number | null }} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] }>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType3($result); + })); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType4($result); + })); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js new file mode 100644 index 000000000..69c96370a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js @@ -0,0 +1,57 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + /** + * @member + * @type {Person | null} + */ + this["Parent"] = null; + } + if (!("Details" in $$source)) { + /** + * @member + * @type {{"Age": number, "Address": {"Street": string}}} + */ + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js new file mode 100644 index 000000000..d811d97ce --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js @@ -0,0 +1,379 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +/** + * @param {{ [_: `${number}`]: number }} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number | null }} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] }>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType3($result); + })); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType4($result); + })); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js new file mode 100644 index 000000000..69c96370a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js @@ -0,0 +1,57 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + /** + * @member + * @type {Person | null} + */ + this["Parent"] = null; + } + if (!("Details" in $$source)) { + /** + * @member + * @type {{"Age": number, "Address": {"Street": string}}} + */ + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js new file mode 100644 index 000000000..6364fa8f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js new file mode 100644 index 000000000..6364fa8f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js new file mode 100644 index 000000000..16a94df37 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js new file mode 100644 index 000000000..d7bfe75cf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js @@ -0,0 +1,58 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {services$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
        } [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
        } */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js new file mode 100644 index 000000000..d413366d6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services.OtherService.Yay").then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/warnings.log b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/warnings.log new file mode 100644 index 000000000..ce8369307 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/warnings.log @@ -0,0 +1,83 @@ +[warn] /testcases/complex_json/main.go:127:2: event 'collision' has one of multiple definitions here with data type map[string]int +[warn] /testcases/events_only/events.go:20:2: event 'collision' has one of multiple definitions here with data type int +[warn] /testcases/events_only/events.go:21:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:10:5: `application.RegisterEvent` is instantiated here but not called +[warn] /testcases/events_only/other.go:13:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:17:2: data type []T for event 'parametric' contains unresolved type parameters and will be ignored` +[warn] /testcases/events_only/other.go:22:2: event 'common:ApplicationStarted' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic +[warn] /testcases/marshalers/main.go:212:2: event 'collision' has one of multiple definitions here with data type *struct{Field []bool} +[warn] /testcases/marshalers/main.go:214:2: data type encoding/json.Marshaler for event 'interface' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors +[warn] dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only +[warn] event 'collision' has multiple conflicting definitions and will be ignored +[warn] events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly +[warn] generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/index.js new file mode 100644 index 000000000..cf48d86db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/index.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {$models.TextMarshaler} TextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/json/index.js new file mode 100644 index 000000000..22f1fd904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/json/index.js @@ -0,0 +1,11 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {$models.Marshaler} Marshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/json/models.js new file mode 100644 index 000000000..5dce4eea6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/json/models.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {any} Marshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/models.js new file mode 100644 index 000000000..db89bafbc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/models.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {any} TextMarshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/eventcreate.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/eventcreate.js new file mode 100644 index 000000000..71a208a3d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/eventcreate.js @@ -0,0 +1,9 @@ +//@ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +Object.freeze($Create.Events); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/eventdata.d.ts b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/eventdata.d.ts new file mode 100644 index 000000000..4384cef06 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/eventdata.d.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type { Events } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as json$0 from "../../../../../encoding/json/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as events_only$0 from "./generator/testcases/events_only/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as more$0 from "./generator/testcases/no_bindings_here/more/models.js"; + +declare module "/wails/runtime.js" { + namespace Events { + interface CustomEvents { + "events_only:class": events_only$0.SomeClass; + "events_only:map": { [_: string]: number[] | null } | null; + "events_only:nodata": void; + "events_only:other": more$0.StringPtr[] | null; + "events_only:string": string; + "interface": json$0.Marshaler; + "overlap": {"Field": boolean[] | null} | null; + } + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js new file mode 100644 index 000000000..16b76f94c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js @@ -0,0 +1,70 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + * @param {$models.Alias} aliasValue + * @returns {$CancellablePromise<$models.Person>} + */ +export function Get(aliasValue) { + return $Call.ByID(1928502664, aliasValue); +} + +/** + * Apparently, aliases are all the rage right now. + * @param {$models.AliasedPerson} p + * @returns {$CancellablePromise<$models.StrangelyAliasedPerson>} + */ +export function GetButAliased(p) { + return $Call.ByID(1896499664, p); +} + +/** + * Get someone quite different. + * @returns {$CancellablePromise<$models.GenericPerson>} + */ +export function GetButDifferent() { + return $Call.ByID(2240931744); +} + +/** + * @returns {$CancellablePromise} + */ +export function GetButForeignPrivateAlias() { + return $Call.ByID(643456960); +} + +/** + * @returns {$CancellablePromise<$models.AliasGroup>} + */ +export function GetButGenericAliases() { + return $Call.ByID(914093800); +} + +/** + * Greet a lot of unusual things. + * @param {$models.EmptyAliasStruct} $0 + * @param {$models.EmptyStruct} $1 + * @returns {$CancellablePromise<$models.AliasStruct>} + */ +export function Greet($0, $1) { + return $Call.ByID(1411160069, $0, $1); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js new file mode 100644 index 000000000..5a5c62644 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js @@ -0,0 +1,96 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * A nice type Alias. + * @typedef {$models.Alias} Alias + */ + +/** + * A class whose fields have various aliased types. + * @typedef {$models.AliasGroup} AliasGroup + */ + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {$models.AliasStruct} AliasStruct + */ + +/** + * A class alias. + * @typedef {$models.AliasedPerson} AliasedPerson + */ + +/** + * An empty struct alias. + * @typedef {$models.EmptyAliasStruct} EmptyAliasStruct + */ + +/** + * An empty struct. + * @typedef {$models.EmptyStruct} EmptyStruct + */ + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {$models.GenericAlias} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {$models.GenericMapAlias} GenericMapAlias + */ + +/** + * A generic struct containing an alias. + * @template T + * @typedef {$models.GenericPerson} GenericPerson + */ + +/** + * A generic alias that wraps a generic struct. + * @template T + * @typedef {$models.GenericPersonAlias} GenericPersonAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {$models.GenericPtrAlias} GenericPtrAlias + */ + +/** + * An alias that wraps a class through a non-typeparam alias. + * @typedef {$models.IndirectPersonAlias} IndirectPersonAlias + */ + +/** + * Another struct alias. + * @typedef {$models.OtherAliasStruct} OtherAliasStruct + */ + +/** + * A non-generic struct containing an alias. + * @typedef {$models.Person} Person + */ + +/** + * Another class alias, but ordered after its aliased class. + * @typedef {$models.StrangelyAliasedPerson} StrangelyAliasedPerson + */ + +/** + * An alias that wraps a class through a typeparam alias. + * @typedef {$models.TPIndirectPersonAlias} TPIndirectPersonAlias + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js new file mode 100644 index 000000000..1ba2af395 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js @@ -0,0 +1,112 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * A nice type Alias. + * @typedef {number} Alias + */ + +/** + * A class whose fields have various aliased types. + * @typedef {Object} AliasGroup + * @property {GenericAlias} GAi + * @property {GenericAlias>} GAP + * @property {GenericPtrAlias} GPAs + * @property {GenericPtrAlias>} GPAP + * @property {GenericMapAlias} GMA + * @property {GenericPersonAlias} GPA + * @property {IndirectPersonAlias} IPA + * @property {TPIndirectPersonAlias} TPIPA + */ + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {Object} AliasStruct + * @property {number[] | null} Foo - A field with a comment. + * @property {string} [Bar] - Definitely not Foo. + * @property {string} [Baz] - Definitely not Foo. + * @property {OtherAliasStruct} Other - A nested alias struct. + */ + +/** + * A class alias. + * @typedef {Person} AliasedPerson + */ + +/** + * An empty struct alias. + * @typedef { { + * } } EmptyAliasStruct + */ + +/** + * An empty struct. + * @typedef { { + * } } EmptyStruct + */ + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {T} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {{ [_: string]: U } | null} GenericMapAlias + */ + +/** + * A generic struct containing an alias. + * @template T + * @typedef {Object} GenericPerson + * @property {T} Name + * @property {Alias} AliasedField + */ + +/** + * A generic alias that wraps a generic struct. + * @template T + * @typedef {GenericPerson[] | null>} GenericPersonAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {GenericAlias | null} GenericPtrAlias + */ + +/** + * An alias that wraps a class through a non-typeparam alias. + * @typedef {GenericPersonAlias} IndirectPersonAlias + */ + +/** + * Another struct alias. + * @typedef {Object} OtherAliasStruct + * @property {number[] | null} NoMoreIdeas + */ + +/** + * A non-generic struct containing an alias. + * @typedef {Object} Person + * @property {string} Name - The Person's name. + * @property {Alias} AliasedField - A random alias field. + */ + +/** + * Another class alias, but ordered after its aliased class. + * @typedef {Person} StrangelyAliasedPerson + */ + +/** + * An alias that wraps a class through a typeparam alias. + * @typedef {GenericAlias>} TPIndirectPersonAlias + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js new file mode 100644 index 000000000..f6c839720 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js new file mode 100644 index 000000000..2a4188bce --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod() { + return $Call.ByID(2241101727); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js new file mode 100644 index 000000000..ead6f5da4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod2() { + return $Call.ByID(1556848345); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js new file mode 100644 index 000000000..995d46c13 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {$models.Person} person + * @param {$models.Embedded1} emb + * @returns {$CancellablePromise} + */ +export function Greet(person, emb) { + return $Call.ByID(1411160069, person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js new file mode 100644 index 000000000..f21130b86 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js @@ -0,0 +1,27 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Title +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Embedded1} Embedded1 + */ + +/** + * @typedef {$models.Embedded3} Embedded3 + */ + +/** + * Person represents a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js new file mode 100644 index 000000000..b30630777 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js @@ -0,0 +1,64 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Embedded1 + * @property {number} Friends - Friends should be shadowed in Person by a field of lesser depth + * @property {number} Vanish - Vanish should be omitted from Person because there is another field with same depth and no tag + * @property {string} StillThere - StillThere should be shadowed in Person by other field with same depth and a json tag + * @property {`${boolean}`} NamingThingsIsHard - NamingThingsIsHard is a law of programming + */ + +/** + * @typedef {string} Embedded3 + */ + +/** + * Person represents a person + * @typedef { { + * "Titles"?: Title[] | null, + * "Names": string[] | null, + * "Partner": Person | null, + * "Friends": (Person | null)[] | null, + * "NamingThingsIsHard": `${boolean}`, + * "StillThere": Embedded3 | null, + * "-": number, + * "Embedded3": Embedded3, + * "StrangerNumber": `${number}`, + * "StrangestString"?: `"${string}"`, + * "StringStrangest"?: `"${string}"`, + * "emb4"?: embedded4, + * } } Person + */ + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +/** + * @typedef {Object} embedded4 + * @property {`${boolean}`} NamingThingsIsHard - NamingThingsIsHard is a law of programming + * @property {boolean} Friends - Friends should not be shadowed in Person as embedded4 is not embedded from encoding/json's point of view; however, it should be shadowed in Embedded1 + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js new file mode 100644 index 000000000..0285f6ca4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + * @param {string} str + * @param {$models.Person[] | null} people + * @param {{"AnotherCount": number, "AnotherOne": $models.Person | null}} $2 + * @param {{ [_: `${number}`]: boolean | null } | null} assoc + * @param {(number | null)[] | null} $4 + * @param {string[]} other + * @returns {$CancellablePromise<[$models.Person, any, number[] | null]>} + */ +export function Greet(str, people, $2, assoc, $4, ...other) { + return $Call.ByID(1411160069, str, people, $2, assoc, $4, other); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js new file mode 100644 index 000000000..88af1203b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * Person represents a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js new file mode 100644 index 000000000..035bc0792 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Person represents a person + * @typedef {Object} Person + * @property {string} Name + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js new file mode 100644 index 000000000..53027124b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.StructA, $models.StructC]>} + */ +export function MakeCycles() { + return $Call.ByID(440020721); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js new file mode 100644 index 000000000..9b49a9172 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js @@ -0,0 +1,22 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.StructA} StructA + */ + +/** + * @typedef {$models.StructC} StructC + */ + +/** + * @typedef {$models.StructE} StructE + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js new file mode 100644 index 000000000..50e0d0fc0 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} StructA + * @property {structB | null} B + */ + +/** + * @typedef {Object} StructC + * @property {structD} D + */ + +/** + * @typedef { { + * } } StructE + */ + +/** + * @typedef {Object} structB + * @property {StructA | null} A + */ + +/** + * @typedef {Object} structD + * @property {StructE} E + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js new file mode 100644 index 000000000..382e6bac3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]>} + */ +export function MakeCycles() { + return $Call.ByID(440020721); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js new file mode 100644 index 000000000..9fc31bf7c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Alias} Alias + */ + +/** + * @typedef {$models.Cyclic} Cyclic + */ + +/** + * @template T + * @typedef {$models.GenericCyclic} GenericCyclic + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js new file mode 100644 index 000000000..2413995ac --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Cyclic | null} Alias + */ + +/** + * @typedef {({ [_: string]: Alias } | null)[] | null} Cyclic + */ + +/** + * @template T + * @typedef {{"X": GenericCyclic | null, "Y": T[] | null}[] | null} GenericCyclic + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js new file mode 100644 index 000000000..40d68bf85 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Interfaces!"); +console.log("Hello JS!"); +console.log("Hello JS Interfaces!"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js new file mode 100644 index 000000000..f96a46e77 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.InternalModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByID(538079117, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js new file mode 100644 index 000000000..a4c233c8c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js @@ -0,0 +1,19 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal model. + * @typedef {Object} InternalModel + * @property {string} Field + */ + +/** + * An unexported model. + * @typedef {Object} unexportedModel + * @property {string} Field + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js new file mode 100644 index 000000000..c93da8f05 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js @@ -0,0 +1,9 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Dummy} Dummy + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js new file mode 100644 index 000000000..6b6f5401f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef { { + * } } Dummy + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js new file mode 100644 index 000000000..0d869be84 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js @@ -0,0 +1,35 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +/** + * @param {string} $0 + * @returns {$CancellablePromise} + */ +function InternalMethod($0) { + return $Call.ByID(3518775569, $0); +} + +/** + * @param {otherpackage$0.Dummy} $0 + * @returns {$CancellablePromise} + */ +export function VisibleMethod($0) { + return $Call.ByID(474018228, $0); +} + +/** + * @param {string} arg + * @returns {Promise} + */ +export async function CustomMethod(arg) { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js new file mode 100644 index 000000000..442f20472 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js new file mode 100644 index 000000000..b2f9c5edb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ji.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ji.js new file mode 100644 index 000000000..36e28f09b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ji.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js new file mode 100644 index 000000000..573852d5a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.unexportedModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByID(37626172, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js new file mode 100644 index 000000000..d364009d6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js @@ -0,0 +1,53 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Comment 1. + * @returns {$CancellablePromise} + */ +export function Method1() { + return $Call.ByID(841558284); +} + +/** + * Comment 2. + * @returns {$CancellablePromise} + */ +export function Method2() { + return $Call.ByID(891891141); +} + +/** + * Comment 3a. + * Comment 3b. + * @returns {$CancellablePromise} + */ +export function Method3() { + return $Call.ByID(875113522); +} + +/** + * Comment 4. + * @returns {$CancellablePromise} + */ +export function Method4() { + return $Call.ByID(791225427); +} + +/** + * Comment 5. + * @returns {$CancellablePromise} + */ +export function Method5() { + return $Call.ByID(774447808); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js new file mode 100644 index 000000000..f14b3b6b2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js @@ -0,0 +1,35 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {$models.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByID(1411160069, name, title); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js new file mode 100644 index 000000000..649d8d016 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Title +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * Person represents a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js new file mode 100644 index 000000000..e7c70729c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js @@ -0,0 +1,61 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Age is an integer with some predefined values + * @typedef {number} Age + */ + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + * @typedef {Object} Person + * @property {Title} Title + * @property {string} Name + * @property {Age} Age + */ + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js new file mode 100644 index 000000000..508c12b72 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {services$0.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByID(1411160069, name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js new file mode 100644 index 000000000..089a8b685 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js new file mode 100644 index 000000000..e0e2d3014 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js @@ -0,0 +1,27 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.js new file mode 100644 index 000000000..14b4d5da8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * SomeClass renders as a TS class. + * @typedef {$models.SomeClass} SomeClass + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.js new file mode 100644 index 000000000..b99dbf18d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * SomeClass renders as a TS class. + * @typedef {Object} SomeClass + * @property {string} Field + * @property {nobindingshere$0.HowDifferent} Meadow + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js new file mode 100644 index 000000000..40d2245b9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js new file mode 100644 index 000000000..26922b7eb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * Person is a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js new file mode 100644 index 000000000..5743a9055 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + * @typedef {Object} Person + * @property {string} Name + * @property {services$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js new file mode 100644 index 000000000..dff4e0d5f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(2007737399); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js new file mode 100644 index 000000000..40d2245b9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js new file mode 100644 index 000000000..8a9890617 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js @@ -0,0 +1,17 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {other$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js new file mode 100644 index 000000000..182a9e091 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(2447353446); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js new file mode 100644 index 000000000..9343d5531 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js new file mode 100644 index 000000000..6c1448d81 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js new file mode 100644 index 000000000..be0cf5ce8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js @@ -0,0 +1,30 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function GreetWithContext(name) { + return $Call.ByID(1310150960, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js new file mode 100644 index 000000000..6c1448d81 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js new file mode 100644 index 000000000..80fdcd24c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js @@ -0,0 +1,98 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +import * as $models from "./models.js"; + +/** + * @template S + * @typedef {$models.BasicCstrAlias} BasicCstrAlias + */ + +/** + * @template R + * @typedef {$models.ComparableCstrAlias} ComparableCstrAlias + */ + +/** + * @typedef {$models.EmbeddedCustomInterface} EmbeddedCustomInterface + */ + +/** + * @typedef {$models.EmbeddedOriginalInterface} EmbeddedOriginalInterface + */ + +/** + * @typedef {$models.EmbeddedPointer} EmbeddedPointer + */ + +/** + * @typedef {$models.EmbeddedPointerPtr} EmbeddedPointerPtr + */ + +/** + * @typedef {$models.EmbeddedValue} EmbeddedValue + */ + +/** + * @typedef {$models.EmbeddedValuePtr} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {$models.GoodTildeCstrAlias} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {$models.InterfaceCstrAlias} InterfaceCstrAlias + */ + +/** + * @template R,S,T,U,V,W,X,Y,Z + * @typedef {$models.Maps} Maps + */ + +/** + * @template X + * @typedef {$models.MixedCstrAlias} MixedCstrAlias + */ + +/** + * @template V + * @typedef {$models.NonBasicCstrAlias} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {$models.PointableCstrAlias} PointableCstrAlias + */ + +/** + * @typedef {$models.PointerAlias} PointerAlias + */ + +/** + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * @typedef {$models.StringAlias} StringAlias + */ + +/** + * @typedef {$models.StringType} StringType + */ + +/** + * @typedef {$models.ValueAlias} ValueAlias + */ + +/** + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js new file mode 100644 index 000000000..cee61a5e4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js @@ -0,0 +1,240 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @template S + * @typedef {S} BasicCstrAlias + */ + +/** + * @template R + * @typedef {R} ComparableCstrAlias + */ + +/** + * @typedef {string} EmbeddedCustomInterface + */ + +/** + * @typedef {string} EmbeddedOriginalInterface + */ + +/** + * @typedef {string} EmbeddedPointer + */ + +/** + * @typedef {string} EmbeddedPointerPtr + */ + +/** + * @typedef {string} EmbeddedValue + */ + +/** + * @typedef {string} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {U} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {Y} InterfaceCstrAlias + */ + +/** + * @template R,S,T,U,V,W,X,Y,Z + * @typedef {Object} Maps + * @property {{ [_: string]: number } | null} Bool - Reject + * @property {{ [_: `${number}`]: number } | null} Int - Accept + * @property {{ [_: `${number}`]: number } | null} Uint - Accept + * @property {{ [_: string]: number } | null} Float - Reject + * @property {{ [_: string]: number } | null} Complex - Reject + * @property {{ [_: `${number}`]: number } | null} Byte - Accept + * @property {{ [_: `${number}`]: number } | null} Rune - Accept + * @property {{ [_: string]: number } | null} String - Accept + * @property {{ [_: string]: number } | null} IntPtr - Reject + * @property {{ [_: string]: number } | null} UintPtr - Reject + * @property {{ [_: string]: number } | null} FloatPtr - Reject + * @property {{ [_: string]: number } | null} ComplexPtr - Reject + * @property {{ [_: string]: number } | null} StringPtr - Reject + * @property {{ [_: string]: number } | null} NTM - Reject + * @property {{ [_: string]: number } | null} NTMPtr - Reject + * @property {{ [_: ValueTextMarshaler]: number } | null} VTM - Accept + * @property {{ [_: ValueTextMarshaler]: number } | null} VTMPtr - Accept + * @property {{ [_: string]: number } | null} PTM - Reject + * @property {{ [_: PointerTextMarshaler]: number } | null} PTMPtr - Accept + * @property {{ [_: string]: number } | null} JTM - Accept, hide + * @property {{ [_: string]: number } | null} JTMPtr - Accept, hide + * @property {{ [_: string]: number } | null} A - Reject + * @property {{ [_: string]: number } | null} APtr - Reject + * @property {{ [_: string]: number } | null} TM - Accept, hide + * @property {{ [_: string]: number } | null} TMPtr - Reject + * @property {{ [_: string]: number } | null} CI - Accept, hide + * @property {{ [_: string]: number } | null} CIPtr - Reject + * @property {{ [_: string]: number } | null} EI - Accept, hide + * @property {{ [_: string]: number } | null} EIPtr - Reject + * @property {{ [_: EmbeddedValue]: number } | null} EV - Accept + * @property {{ [_: EmbeddedValue]: number } | null} EVPtr - Accept + * @property {{ [_: EmbeddedValuePtr]: number } | null} EVP - Accept + * @property {{ [_: EmbeddedValuePtr]: number } | null} EVPPtr - Accept + * @property {{ [_: string]: number } | null} EP - Reject + * @property {{ [_: EmbeddedPointer]: number } | null} EPPtr - Accept + * @property {{ [_: EmbeddedPointerPtr]: number } | null} EPP - Accept + * @property {{ [_: EmbeddedPointerPtr]: number } | null} EPPPtr - Accept + * @property {{ [_: EmbeddedCustomInterface]: number } | null} ECI - Accept + * @property {{ [_: EmbeddedCustomInterface]: number } | null} ECIPtr - Accept + * @property {{ [_: EmbeddedOriginalInterface]: number } | null} EOI - Accept + * @property {{ [_: EmbeddedOriginalInterface]: number } | null} EOIPtr - Accept + * @property {{ [_: string]: number } | null} WT - Reject + * @property {{ [_: string]: number } | null} WA - Reject + * @property {{ [_: StringType]: number } | null} ST - Accept + * @property {{ [_: StringAlias]: number } | null} SA - Accept + * @property {{ [_: `${number}`]: number } | null} IntT - Accept + * @property {{ [_: `${number}`]: number } | null} IntA - Accept + * @property {{ [_: string]: number } | null} VT - Reject + * @property {{ [_: string]: number } | null} VTPtr - Reject + * @property {{ [_: string]: number } | null} VPT - Reject + * @property {{ [_: string]: number } | null} VPTPtr - Reject + * @property {{ [_: ValueAlias]: number } | null} VA - Accept + * @property {{ [_: ValueAlias]: number } | null} VAPtr - Accept + * @property {{ [_: string]: number } | null} VPA - Accept, hide + * @property {{ [_: string]: number } | null} VPAPtr - Reject + * @property {{ [_: string]: number } | null} PT - Reject + * @property {{ [_: string]: number } | null} PTPtr - Reject + * @property {{ [_: string]: number } | null} PPT - Reject + * @property {{ [_: string]: number } | null} PPTPtr - Reject + * @property {{ [_: string]: number } | null} PA - Reject + * @property {{ [_: PointerAlias]: number } | null} PAPtr - Accept + * @property {{ [_: string]: number } | null} PPA - Accept, hide + * @property {{ [_: string]: number } | null} PPAPtr - Reject + * @property {{ [_: string]: number } | null} IT - Accept, hide + * @property {{ [_: string]: number } | null} ITPtr - Reject + * @property {{ [_: string]: number } | null} IPT - Reject + * @property {{ [_: string]: number } | null} IPTPtr - Reject + * @property {{ [_: string]: number } | null} IA - Accept, hide + * @property {{ [_: string]: number } | null} IAPtr - Reject + * @property {{ [_: string]: number } | null} IPA - Reject + * @property {{ [_: string]: number } | null} IPAPtr - Reject + * @property {{ [_: string]: number } | null} TPR - Soft reject + * @property {{ [_: string]: number } | null} TPRPtr - Soft reject + * @property {{ [_: string]: number } | null} TPS - Accept, hide + * @property {{ [_: string]: number } | null} TPSPtr - Soft reject + * @property {{ [_: string]: number } | null} TPT - Soft reject + * @property {{ [_: string]: number } | null} TPTPtr - Soft reject + * @property {{ [_: string]: number } | null} TPU - Accept, hide + * @property {{ [_: string]: number } | null} TPUPtr - Soft reject + * @property {{ [_: string]: number } | null} TPV - Accept, hide + * @property {{ [_: string]: number } | null} TPVPtr - Soft reject + * @property {{ [_: string]: number } | null} TPW - Soft reject + * @property {{ [_: string]: number } | null} TPWPtr - Accept, hide + * @property {{ [_: string]: number } | null} TPX - Accept, hide + * @property {{ [_: string]: number } | null} TPXPtr - Soft reject + * @property {{ [_: string]: number } | null} TPY - Accept, hide + * @property {{ [_: string]: number } | null} TPYPtr - Soft reject + * @property {{ [_: string]: number } | null} TPZ - Accept, hide + * @property {{ [_: string]: number } | null} TPZPtr - Soft reject + * @property {{ [_: string]: number } | null} GAR - Soft reject + * @property {{ [_: string]: number } | null} GARPtr - Soft reject + * @property {{ [_: string]: number } | null} GAS - Accept, hide + * @property {{ [_: string]: number } | null} GASPtr - Soft reject + * @property {{ [_: string]: number } | null} GAT - Soft reject + * @property {{ [_: string]: number } | null} GATPtr - Soft reject + * @property {{ [_: string]: number } | null} GAU - Accept, hide + * @property {{ [_: string]: number } | null} GAUPtr - Soft reject + * @property {{ [_: string]: number } | null} GAV - Accept, hide + * @property {{ [_: string]: number } | null} GAVPtr - Soft reject + * @property {{ [_: string]: number } | null} GAW - Soft reject + * @property {{ [_: string]: number } | null} GAWPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAX - Accept, hide + * @property {{ [_: string]: number } | null} GAXPtr - Soft reject + * @property {{ [_: string]: number } | null} GAY - Accept, hide + * @property {{ [_: string]: number } | null} GAYPtr - Soft reject + * @property {{ [_: string]: number } | null} GAZ - Accept, hide + * @property {{ [_: string]: number } | null} GAZPtr - Soft reject + * @property {{ [_: `${number}`]: number } | null} GACi - Accept, hide + * @property {{ [_: ComparableCstrAlias]: number } | null} GACV - Accept + * @property {{ [_: string]: number } | null} GACP - Reject + * @property {{ [_: string]: number } | null} GACiPtr - Reject + * @property {{ [_: string]: number } | null} GACVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GACPPtr - Accept, hide + * @property {{ [_: `${number}`]: number } | null} GABi - Accept, hide + * @property {{ [_: BasicCstrAlias]: number } | null} GABs - Accept + * @property {{ [_: string]: number } | null} GABiPtr - Reject + * @property {{ [_: string]: number } | null} GABT - Reject + * @property {{ [_: string]: number } | null} GABTPtr - Reject + * @property {{ [_: GoodTildeCstrAlias]: number } | null} GAGT - Accept + * @property {{ [_: string]: number } | null} GAGTPtr - Accept, hide + * @property {{ [_: NonBasicCstrAlias]: number } | null} GANBV - Accept + * @property {{ [_: string]: number } | null} GANBP - Accept, hide + * @property {{ [_: string]: number } | null} GANBVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GANBPPtr - Reject + * @property {{ [_: PointableCstrAlias]: number } | null} GAPlV1 - Accept + * @property {{ [_: PointableCstrAlias]: number } | null} GAPlV2 - Accept + * @property {{ [_: string]: number } | null} GAPlP1 - Reject + * @property {{ [_: PointableCstrAlias]: number } | null} GAPlP2 - Accept + * @property {{ [_: string]: number } | null} GAPlVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAPlPPtr - Accept, hide + * @property {{ [_: `${number}`]: number } | null} GAMi - Accept, hide + * @property {{ [_: MixedCstrAlias]: number } | null} GAMS - Accept + * @property {{ [_: MixedCstrAlias]: number } | null} GAMV - Accept + * @property {{ [_: string]: number } | null} GAMSPtr - Reject + * @property {{ [_: string]: number } | null} GAMVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAII - Accept, hide + * @property {{ [_: InterfaceCstrAlias]: number } | null} GAIV - Accept + * @property {{ [_: string]: number } | null} GAIP - Accept, hide + * @property {{ [_: string]: number } | null} GAIIPtr - Reject + * @property {{ [_: string]: number } | null} GAIVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAIPPtr - Reject + * @property {{ [_: string]: number } | null} GAPrV - Accept, hide + * @property {{ [_: string]: number } | null} GAPrP - Accept, hide + * @property {{ [_: string]: number } | null} GAPrVPtr - Reject + * @property {{ [_: string]: number } | null} GAPrPPtr - Reject + */ + +/** + * @template X + * @typedef {X} MixedCstrAlias + */ + +/** + * @template V + * @typedef {V} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {W} PointableCstrAlias + */ + +/** + * @typedef {PointerTextMarshaler} PointerAlias + */ + +/** + * @typedef {string} PointerTextMarshaler + */ + +/** + * @typedef {string} StringAlias + */ + +/** + * @typedef {string} StringType + */ + +/** + * @typedef {ValueTextMarshaler} ValueAlias + */ + +/** + * @typedef {string} ValueTextMarshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js new file mode 100644 index 000000000..810db1875 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>>} + */ +export function Method() { + return $Call.ByID(4021345184); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js new file mode 100644 index 000000000..0f2edd9c7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js @@ -0,0 +1,124 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +import * as $models from "./models.js"; + +/** + * any + * @typedef {$models.AliasJsonMarshaler} AliasJsonMarshaler + */ + +/** + * any + * @typedef {$models.AliasMarshaler} AliasMarshaler + */ + +/** + * struct{} + * @typedef {$models.AliasNonMarshaler} AliasNonMarshaler + */ + +/** + * string + * @typedef {$models.AliasTextMarshaler} AliasTextMarshaler + */ + +/** + * @typedef {$models.Data} Data + */ + +/** + * any + * @typedef {$models.ImplicitJsonButText} ImplicitJsonButText + */ + +/** + * any + * @typedef {$models.ImplicitJsonMarshaler} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitMarshaler} ImplicitMarshaler + */ + +/** + * string + * @typedef {$models.ImplicitNonJson} ImplicitNonJson + */ + +/** + * class{ Marshaler, TextMarshaler } + * @typedef {$models.ImplicitNonMarshaler} ImplicitNonMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitNonText} ImplicitNonText + */ + +/** + * any + * @typedef {$models.ImplicitTextButJson} ImplicitTextButJson + */ + +/** + * string + * @typedef {$models.ImplicitTextMarshaler} ImplicitTextMarshaler + */ + +/** + * class {} + * @typedef {$models.NonMarshaler} NonMarshaler + */ + +/** + * any + * @typedef {$models.PointerJsonMarshaler} PointerJsonMarshaler + */ + +/** + * any + * @typedef {$models.PointerMarshaler} PointerMarshaler + */ + +/** + * string + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingJsonMarshaler} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingMarshaler} UnderlyingMarshaler + */ + +/** + * string + * @typedef {$models.UnderlyingTextMarshaler} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {$models.ValueJsonMarshaler} ValueJsonMarshaler + */ + +/** + * any + * @typedef {$models.ValueMarshaler} ValueMarshaler + */ + +/** + * string + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js new file mode 100644 index 000000000..a956da60f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js @@ -0,0 +1,186 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + * @typedef {any} AliasJsonMarshaler + */ + +/** + * any + * @typedef {any} AliasMarshaler + */ + +/** + * struct{} + * @typedef { { + * } } AliasNonMarshaler + */ + +/** + * string + * @typedef {string} AliasTextMarshaler + */ + +/** + * @typedef {Object} Data + * @property {NonMarshaler} NM + * @property {NonMarshaler | null} NMPtr - NonMarshaler | null + * @property {ValueJsonMarshaler} VJM + * @property {ValueJsonMarshaler | null} VJMPtr - ValueJsonMarshaler | null + * @property {PointerJsonMarshaler} PJM + * @property {PointerJsonMarshaler | null} PJMPtr - PointerJsonMarshaler | null + * @property {ValueTextMarshaler} VTM + * @property {ValueTextMarshaler | null} VTMPtr - ValueTextMarshaler | null + * @property {PointerTextMarshaler} PTM + * @property {PointerTextMarshaler | null} PTMPtr - PointerTextMarshaler | null + * @property {ValueMarshaler} VM + * @property {ValueMarshaler | null} VMPtr - ValueMarshaler | null + * @property {PointerMarshaler} PM + * @property {PointerMarshaler | null} PMPtr - PointerMarshaler | null + * @property {UnderlyingJsonMarshaler} UJM + * @property {UnderlyingJsonMarshaler | null} UJMPtr - UnderlyingJsonMarshaler | null + * @property {UnderlyingTextMarshaler} UTM + * @property {UnderlyingTextMarshaler | null} UTMPtr - UnderlyingTextMarshaler | null + * @property {UnderlyingMarshaler} UM + * @property {UnderlyingMarshaler | null} UMPtr - UnderlyingMarshaler | null + * @property {any} JM - any + * @property {any | null} JMPtr - any | null + * @property {string} TM - string + * @property {string | null} TMPtr - string | null + * @property {any} CJM - any + * @property {any | null} CJMPtr - any | null + * @property {string} CTM - string + * @property {string | null} CTMPtr - string | null + * @property {any} CM - any + * @property {any | null} CMPtr - any | null + * @property {AliasNonMarshaler} ANM + * @property {AliasNonMarshaler | null} ANMPtr - AliasNonMarshaler | null + * @property {AliasJsonMarshaler} AJM + * @property {AliasJsonMarshaler | null} AJMPtr - AliasJsonMarshaler | null + * @property {AliasTextMarshaler} ATM + * @property {AliasTextMarshaler | null} ATMPtr - AliasTextMarshaler | null + * @property {AliasMarshaler} AM + * @property {AliasMarshaler | null} AMPtr - AliasMarshaler | null + * @property {ImplicitJsonMarshaler} ImJM + * @property {ImplicitJsonMarshaler | null} ImJMPtr - ImplicitJsonMarshaler | null + * @property {ImplicitTextMarshaler} ImTM + * @property {ImplicitTextMarshaler | null} ImTMPtr - ImplicitTextMarshaler | null + * @property {ImplicitMarshaler} ImM + * @property {ImplicitMarshaler | null} ImMPtr - ImplicitMarshaler | null + * @property {ImplicitNonJson} ImNJ + * @property {ImplicitNonJson | null} ImNJPtr - ImplicitNonJson | null + * @property {ImplicitNonText} ImNT + * @property {ImplicitNonText | null} ImNTPtr - ImplicitNonText | null + * @property {ImplicitNonMarshaler} ImNM + * @property {ImplicitNonMarshaler | null} ImNMPtr - ImplicitNonMarshaler | null + * @property {ImplicitJsonButText} ImJbT + * @property {ImplicitJsonButText | null} ImJbTPtr - ImplicitJsonButText | null + * @property {ImplicitTextButJson} ImTbJ + * @property {ImplicitTextButJson | null} ImTbJPtr - ImplicitTextButJson | null + */ + +/** + * any + * @typedef {any} ImplicitJsonButText + */ + +/** + * any + * @typedef {any} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {any} ImplicitMarshaler + */ + +/** + * string + * @typedef {string} ImplicitNonJson + */ + +/** + * class{ Marshaler, TextMarshaler } + * @typedef {Object} ImplicitNonMarshaler + * @property {json$0.Marshaler} Marshaler + * @property {encoding$0.TextMarshaler} TextMarshaler + */ + +/** + * any + * @typedef {any} ImplicitNonText + */ + +/** + * any + * @typedef {any} ImplicitTextButJson + */ + +/** + * string + * @typedef {string} ImplicitTextMarshaler + */ + +/** + * class {} + * @typedef { { + * } } NonMarshaler + */ + +/** + * any + * @typedef {any} PointerJsonMarshaler + */ + +/** + * any + * @typedef {any} PointerMarshaler + */ + +/** + * string + * @typedef {string} PointerTextMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingMarshaler + */ + +/** + * string + * @typedef {string} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {any} ValueJsonMarshaler + */ + +/** + * any + * @typedef {any} ValueMarshaler + */ + +/** + * string + * @typedef {string} ValueTextMarshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js new file mode 100644 index 000000000..5ba9fe470 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Data>} + */ +export function Method() { + return $Call.ByID(4021345184); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js new file mode 100644 index 000000000..0b7f42650 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +import * as $models from "./models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + * @template How + * @typedef {$models.HowDifferent} HowDifferent + */ + +/** + * Impersonator gets their fields from other people. + * @typedef {$models.Impersonator} Impersonator + */ + +/** + * Person is not a number. + * @typedef {$models.Person} Person + */ + +/** + * PrivatePerson gets their fields from hidden sources. + * @typedef {$models.PrivatePerson} PrivatePerson + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js new file mode 100644 index 000000000..36f231303 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js @@ -0,0 +1,44 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + * @template How + * @typedef {Object} HowDifferent + * @property {string} Name - They have a name as well. + * @property {({ [_: string]: How } | null)[] | null} Differences - But they may have many differences. + */ + +/** + * Impersonator gets their fields from other people. + * @typedef {other$0.OtherPerson} Impersonator + */ + +/** + * Person is not a number. + * @typedef {Object} Person + * @property {string} Name - They have a name. + * @property {Impersonator[]} Friends - Exactly 4 sketchy friends. + */ + +/** + * PrivatePerson gets their fields from hidden sources. + * @typedef {personImpl} PrivatePerson + */ + +/** + * @typedef {Object} personImpl + * @property {string} Nickname - Nickname conceals a person's identity. + * @property {string} Name - They have a name. + * @property {Impersonator[]} Friends - Exactly 4 sketchy friends. + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.js new file mode 100644 index 000000000..c3eabd022 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * StringPtr is a nullable string. + * @typedef {$models.StringPtr} StringPtr + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.js new file mode 100644 index 000000000..04f4d47dd --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * StringPtr is a nullable string. + * @typedef {string | null} StringPtr + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js new file mode 100644 index 000000000..33246d35e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js @@ -0,0 +1,16 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +import * as $models from "./models.js"; + +/** + * OtherPerson is like a person, but different. + * @template T + * @typedef {$models.OtherPerson} OtherPerson + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js new file mode 100644 index 000000000..63a2ee722 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherPerson is like a person, but different. + * @template T + * @typedef {Object} OtherPerson + * @property {string} Name - They have a name as well. + * @property {T[] | null} Differences - But they may have many differences. + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js new file mode 100644 index 000000000..d647deeb4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(3606939272); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js new file mode 100644 index 000000000..dca6f4e6c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByID(2124352079); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(4281222271); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js new file mode 100644 index 000000000..19bdcac29 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(3566862802); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js new file mode 100644 index 000000000..f50558b8c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByID(2590614085); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(773650321); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js new file mode 100644 index 000000000..3597b6460 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} $0 + * @returns {$CancellablePromise} + */ +export function Greet($0) { + return $Call.ByID(1411160069, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js new file mode 100644 index 000000000..734fb02e7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js new file mode 100644 index 000000000..9343d5531 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js new file mode 100644 index 000000000..b4f3f039c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js new file mode 100644 index 000000000..9343d5531 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js new file mode 100644 index 000000000..b4f3f039c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js new file mode 100644 index 000000000..40d2245b9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js new file mode 100644 index 000000000..41b452f57 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js @@ -0,0 +1,17 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {services$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js new file mode 100644 index 000000000..7a4789f75 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(3568225479); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js new file mode 100644 index 000000000..ddd5e8916 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js @@ -0,0 +1,360 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByID(3862002418, $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByID(2424639793, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByID(3132595881, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByID(3306292566, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByID(1754277916, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByID(1909469092, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByID(4251088558, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByID(1343888303, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByID(2205561041, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByID(572240879, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByID(2189402897, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByID(642881729, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByID(1066151743, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByID(2718999663, $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByID(2386486356, $in); +} + +/** + * @param {{ [_: `${number}`]: number | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByID(2163571325, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByID(2900172572, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] | null } | null>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByID(881980169, $in); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByID(1075577233); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByID(3589606958, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByID(224675106, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByID(2124953624, $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByID(3516977899, $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByID(229603958, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByID(3678582682, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByID(319259595, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByID(383995060, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByID(1091960237, $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByID(3835643147, $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByID(2447692557, $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByID(2943477349, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByID(3401034892, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByID(1236957573, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByID(1160383782, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByID(1739300671, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByID(793803239, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByID(1403757716, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByID(2988345717, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByID(518250834, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByID(2836661285, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByID(1367187362, $in); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js new file mode 100644 index 000000000..ec6136e27 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {Person | null} Parent + * @property {{"Age": number, "Address": {"Street": string}}} Details + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js new file mode 100644 index 000000000..ddd5e8916 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js @@ -0,0 +1,360 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByID(3862002418, $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByID(2424639793, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByID(3132595881, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByID(3306292566, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByID(1754277916, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByID(1909469092, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByID(4251088558, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByID(1343888303, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByID(2205561041, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByID(572240879, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByID(2189402897, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByID(642881729, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByID(1066151743, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByID(2718999663, $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByID(2386486356, $in); +} + +/** + * @param {{ [_: `${number}`]: number | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByID(2163571325, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByID(2900172572, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] | null } | null>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByID(881980169, $in); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByID(1075577233); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByID(3589606958, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByID(224675106, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByID(2124953624, $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByID(3516977899, $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByID(229603958, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByID(3678582682, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByID(319259595, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByID(383995060, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByID(1091960237, $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByID(3835643147, $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByID(2447692557, $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByID(2943477349, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByID(3401034892, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByID(1236957573, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByID(1160383782, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByID(1739300671, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByID(793803239, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByID(1403757716, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByID(2988345717, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByID(518250834, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByID(2836661285, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByID(1367187362, $in); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js new file mode 100644 index 000000000..ec6136e27 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {Person | null} Parent + * @property {{"Age": number, "Address": {"Street": string}}} Details + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js new file mode 100644 index 000000000..6c1448d81 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js new file mode 100644 index 000000000..6c1448d81 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js new file mode 100644 index 000000000..40d2245b9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js new file mode 100644 index 000000000..977600693 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js @@ -0,0 +1,16 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * Person is a person! + * They have a name and an address + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js new file mode 100644 index 000000000..ab5cea255 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js @@ -0,0 +1,19 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + * @typedef {Object} Person + * @property {string} Name + * @property {services$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js new file mode 100644 index 000000000..3c9c56546 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(1491748400); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/warnings.log b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/warnings.log new file mode 100644 index 000000000..ce8369307 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/warnings.log @@ -0,0 +1,83 @@ +[warn] /testcases/complex_json/main.go:127:2: event 'collision' has one of multiple definitions here with data type map[string]int +[warn] /testcases/events_only/events.go:20:2: event 'collision' has one of multiple definitions here with data type int +[warn] /testcases/events_only/events.go:21:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:10:5: `application.RegisterEvent` is instantiated here but not called +[warn] /testcases/events_only/other.go:13:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:17:2: data type []T for event 'parametric' contains unresolved type parameters and will be ignored` +[warn] /testcases/events_only/other.go:22:2: event 'common:ApplicationStarted' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic +[warn] /testcases/marshalers/main.go:212:2: event 'collision' has one of multiple definitions here with data type *struct{Field []bool} +[warn] /testcases/marshalers/main.go:214:2: data type encoding/json.Marshaler for event 'interface' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors +[warn] dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only +[warn] event 'collision' has multiple conflicting definitions and will be ignored +[warn] events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly +[warn] generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/index.js new file mode 100644 index 000000000..cf48d86db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/index.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {$models.TextMarshaler} TextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/json/index.js new file mode 100644 index 000000000..22f1fd904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/json/index.js @@ -0,0 +1,11 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {$models.Marshaler} Marshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/json/models.js new file mode 100644 index 000000000..5dce4eea6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/json/models.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {any} Marshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/models.js new file mode 100644 index 000000000..db89bafbc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/models.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {any} TextMarshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/eventcreate.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/eventcreate.js new file mode 100644 index 000000000..71a208a3d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/eventcreate.js @@ -0,0 +1,9 @@ +//@ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +Object.freeze($Create.Events); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/eventdata.d.ts b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/eventdata.d.ts new file mode 100644 index 000000000..4384cef06 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/eventdata.d.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type { Events } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as json$0 from "../../../../../encoding/json/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as events_only$0 from "./generator/testcases/events_only/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as more$0 from "./generator/testcases/no_bindings_here/more/models.js"; + +declare module "/wails/runtime.js" { + namespace Events { + interface CustomEvents { + "events_only:class": events_only$0.SomeClass; + "events_only:map": { [_: string]: number[] | null } | null; + "events_only:nodata": void; + "events_only:other": more$0.StringPtr[] | null; + "events_only:string": string; + "interface": json$0.Marshaler; + "overlap": {"Field": boolean[] | null} | null; + } + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js new file mode 100644 index 000000000..cc81267aa --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js @@ -0,0 +1,70 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + * @param {$models.Alias} aliasValue + * @returns {$CancellablePromise<$models.Person>} + */ +export function Get(aliasValue) { + return $Call.ByName("main.GreetService.Get", aliasValue); +} + +/** + * Apparently, aliases are all the rage right now. + * @param {$models.AliasedPerson} p + * @returns {$CancellablePromise<$models.StrangelyAliasedPerson>} + */ +export function GetButAliased(p) { + return $Call.ByName("main.GreetService.GetButAliased", p); +} + +/** + * Get someone quite different. + * @returns {$CancellablePromise<$models.GenericPerson>} + */ +export function GetButDifferent() { + return $Call.ByName("main.GreetService.GetButDifferent"); +} + +/** + * @returns {$CancellablePromise} + */ +export function GetButForeignPrivateAlias() { + return $Call.ByName("main.GreetService.GetButForeignPrivateAlias"); +} + +/** + * @returns {$CancellablePromise<$models.AliasGroup>} + */ +export function GetButGenericAliases() { + return $Call.ByName("main.GreetService.GetButGenericAliases"); +} + +/** + * Greet a lot of unusual things. + * @param {$models.EmptyAliasStruct} $0 + * @param {$models.EmptyStruct} $1 + * @returns {$CancellablePromise<$models.AliasStruct>} + */ +export function Greet($0, $1) { + return $Call.ByName("main.GreetService.Greet", $0, $1); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js new file mode 100644 index 000000000..5a5c62644 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js @@ -0,0 +1,96 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * A nice type Alias. + * @typedef {$models.Alias} Alias + */ + +/** + * A class whose fields have various aliased types. + * @typedef {$models.AliasGroup} AliasGroup + */ + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {$models.AliasStruct} AliasStruct + */ + +/** + * A class alias. + * @typedef {$models.AliasedPerson} AliasedPerson + */ + +/** + * An empty struct alias. + * @typedef {$models.EmptyAliasStruct} EmptyAliasStruct + */ + +/** + * An empty struct. + * @typedef {$models.EmptyStruct} EmptyStruct + */ + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {$models.GenericAlias} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {$models.GenericMapAlias} GenericMapAlias + */ + +/** + * A generic struct containing an alias. + * @template T + * @typedef {$models.GenericPerson} GenericPerson + */ + +/** + * A generic alias that wraps a generic struct. + * @template T + * @typedef {$models.GenericPersonAlias} GenericPersonAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {$models.GenericPtrAlias} GenericPtrAlias + */ + +/** + * An alias that wraps a class through a non-typeparam alias. + * @typedef {$models.IndirectPersonAlias} IndirectPersonAlias + */ + +/** + * Another struct alias. + * @typedef {$models.OtherAliasStruct} OtherAliasStruct + */ + +/** + * A non-generic struct containing an alias. + * @typedef {$models.Person} Person + */ + +/** + * Another class alias, but ordered after its aliased class. + * @typedef {$models.StrangelyAliasedPerson} StrangelyAliasedPerson + */ + +/** + * An alias that wraps a class through a typeparam alias. + * @typedef {$models.TPIndirectPersonAlias} TPIndirectPersonAlias + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js new file mode 100644 index 000000000..1ba2af395 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js @@ -0,0 +1,112 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * A nice type Alias. + * @typedef {number} Alias + */ + +/** + * A class whose fields have various aliased types. + * @typedef {Object} AliasGroup + * @property {GenericAlias} GAi + * @property {GenericAlias>} GAP + * @property {GenericPtrAlias} GPAs + * @property {GenericPtrAlias>} GPAP + * @property {GenericMapAlias} GMA + * @property {GenericPersonAlias} GPA + * @property {IndirectPersonAlias} IPA + * @property {TPIndirectPersonAlias} TPIPA + */ + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {Object} AliasStruct + * @property {number[] | null} Foo - A field with a comment. + * @property {string} [Bar] - Definitely not Foo. + * @property {string} [Baz] - Definitely not Foo. + * @property {OtherAliasStruct} Other - A nested alias struct. + */ + +/** + * A class alias. + * @typedef {Person} AliasedPerson + */ + +/** + * An empty struct alias. + * @typedef { { + * } } EmptyAliasStruct + */ + +/** + * An empty struct. + * @typedef { { + * } } EmptyStruct + */ + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {T} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {{ [_: string]: U } | null} GenericMapAlias + */ + +/** + * A generic struct containing an alias. + * @template T + * @typedef {Object} GenericPerson + * @property {T} Name + * @property {Alias} AliasedField + */ + +/** + * A generic alias that wraps a generic struct. + * @template T + * @typedef {GenericPerson[] | null>} GenericPersonAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {GenericAlias | null} GenericPtrAlias + */ + +/** + * An alias that wraps a class through a non-typeparam alias. + * @typedef {GenericPersonAlias} IndirectPersonAlias + */ + +/** + * Another struct alias. + * @typedef {Object} OtherAliasStruct + * @property {number[] | null} NoMoreIdeas + */ + +/** + * A non-generic struct containing an alias. + * @typedef {Object} Person + * @property {string} Name - The Person's name. + * @property {Alias} AliasedField - A random alias field. + */ + +/** + * Another class alias, but ordered after its aliased class. + * @typedef {Person} StrangelyAliasedPerson + */ + +/** + * An alias that wraps a class through a typeparam alias. + * @typedef {GenericAlias>} TPIndirectPersonAlias + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js new file mode 100644 index 000000000..f6c839720 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js new file mode 100644 index 000000000..f62552e8a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service7.TestMethod"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js new file mode 100644 index 000000000..244e74afe --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod2() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service9.TestMethod2"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js new file mode 100644 index 000000000..e146c3669 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {$models.Person} person + * @param {$models.Embedded1} emb + * @returns {$CancellablePromise} + */ +export function Greet(person, emb) { + return $Call.ByName("main.GreetService.Greet", person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js new file mode 100644 index 000000000..f21130b86 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js @@ -0,0 +1,27 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Title +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Embedded1} Embedded1 + */ + +/** + * @typedef {$models.Embedded3} Embedded3 + */ + +/** + * Person represents a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js new file mode 100644 index 000000000..b30630777 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js @@ -0,0 +1,64 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Embedded1 + * @property {number} Friends - Friends should be shadowed in Person by a field of lesser depth + * @property {number} Vanish - Vanish should be omitted from Person because there is another field with same depth and no tag + * @property {string} StillThere - StillThere should be shadowed in Person by other field with same depth and a json tag + * @property {`${boolean}`} NamingThingsIsHard - NamingThingsIsHard is a law of programming + */ + +/** + * @typedef {string} Embedded3 + */ + +/** + * Person represents a person + * @typedef { { + * "Titles"?: Title[] | null, + * "Names": string[] | null, + * "Partner": Person | null, + * "Friends": (Person | null)[] | null, + * "NamingThingsIsHard": `${boolean}`, + * "StillThere": Embedded3 | null, + * "-": number, + * "Embedded3": Embedded3, + * "StrangerNumber": `${number}`, + * "StrangestString"?: `"${string}"`, + * "StringStrangest"?: `"${string}"`, + * "emb4"?: embedded4, + * } } Person + */ + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +/** + * @typedef {Object} embedded4 + * @property {`${boolean}`} NamingThingsIsHard - NamingThingsIsHard is a law of programming + * @property {boolean} Friends - Friends should not be shadowed in Person as embedded4 is not embedded from encoding/json's point of view; however, it should be shadowed in Embedded1 + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js new file mode 100644 index 000000000..03ef1f90f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + * @param {string} str + * @param {$models.Person[] | null} people + * @param {{"AnotherCount": number, "AnotherOne": $models.Person | null}} $2 + * @param {{ [_: `${number}`]: boolean | null } | null} assoc + * @param {(number | null)[] | null} $4 + * @param {string[]} other + * @returns {$CancellablePromise<[$models.Person, any, number[] | null]>} + */ +export function Greet(str, people, $2, assoc, $4, ...other) { + return $Call.ByName("main.GreetService.Greet", str, people, $2, assoc, $4, other); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js new file mode 100644 index 000000000..88af1203b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * Person represents a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js new file mode 100644 index 000000000..035bc0792 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Person represents a person + * @typedef {Object} Person + * @property {string} Name + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js new file mode 100644 index 000000000..8203e4168 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.StructA, $models.StructC]>} + */ +export function MakeCycles() { + return $Call.ByName("main.GreetService.MakeCycles"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js new file mode 100644 index 000000000..9b49a9172 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js @@ -0,0 +1,22 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.StructA} StructA + */ + +/** + * @typedef {$models.StructC} StructC + */ + +/** + * @typedef {$models.StructE} StructE + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js new file mode 100644 index 000000000..50e0d0fc0 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} StructA + * @property {structB | null} B + */ + +/** + * @typedef {Object} StructC + * @property {structD} D + */ + +/** + * @typedef { { + * } } StructE + */ + +/** + * @typedef {Object} structB + * @property {StructA | null} A + */ + +/** + * @typedef {Object} structD + * @property {StructE} E + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js new file mode 100644 index 000000000..623e88035 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]>} + */ +export function MakeCycles() { + return $Call.ByName("main.GreetService.MakeCycles"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js new file mode 100644 index 000000000..9fc31bf7c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Alias} Alias + */ + +/** + * @typedef {$models.Cyclic} Cyclic + */ + +/** + * @template T + * @typedef {$models.GenericCyclic} GenericCyclic + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js new file mode 100644 index 000000000..2413995ac --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Cyclic | null} Alias + */ + +/** + * @typedef {({ [_: string]: Alias } | null)[] | null} Cyclic + */ + +/** + * @template T + * @typedef {{"X": GenericCyclic | null, "Y": T[] | null}[] | null} GenericCyclic + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js new file mode 100644 index 000000000..40d68bf85 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Interfaces!"); +console.log("Hello JS!"); +console.log("Hello JS Interfaces!"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js new file mode 100644 index 000000000..374d13203 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.InternalModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByName("main.InternalService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js new file mode 100644 index 000000000..a4c233c8c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js @@ -0,0 +1,19 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal model. + * @typedef {Object} InternalModel + * @property {string} Field + */ + +/** + * An unexported model. + * @typedef {Object} unexportedModel + * @property {string} Field + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js new file mode 100644 index 000000000..c93da8f05 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js @@ -0,0 +1,9 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Dummy} Dummy + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js new file mode 100644 index 000000000..6b6f5401f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef { { + * } } Dummy + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js new file mode 100644 index 000000000..8332051eb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js @@ -0,0 +1,35 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +/** + * @param {string} $0 + * @returns {$CancellablePromise} + */ +function InternalMethod($0) { + return $Call.ByName("main.Service.InternalMethod", $0); +} + +/** + * @param {otherpackage$0.Dummy} $0 + * @returns {$CancellablePromise} + */ +export function VisibleMethod($0) { + return $Call.ByName("main.Service.VisibleMethod", $0); +} + +/** + * @param {string} arg + * @returns {Promise} + */ +export async function CustomMethod(arg) { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js new file mode 100644 index 000000000..442f20472 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js new file mode 100644 index 000000000..b2f9c5edb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ji.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ji.js new file mode 100644 index 000000000..36e28f09b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ji.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js new file mode 100644 index 000000000..1fe8a0f69 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.unexportedModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByName("main.unexportedService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js new file mode 100644 index 000000000..3d245f10c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js @@ -0,0 +1,53 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Comment 1. + * @returns {$CancellablePromise} + */ +export function Method1() { + return $Call.ByName("main.GreetService.Method1"); +} + +/** + * Comment 2. + * @returns {$CancellablePromise} + */ +export function Method2() { + return $Call.ByName("main.GreetService.Method2"); +} + +/** + * Comment 3a. + * Comment 3b. + * @returns {$CancellablePromise} + */ +export function Method3() { + return $Call.ByName("main.GreetService.Method3"); +} + +/** + * Comment 4. + * @returns {$CancellablePromise} + */ +export function Method4() { + return $Call.ByName("main.GreetService.Method4"); +} + +/** + * Comment 5. + * @returns {$CancellablePromise} + */ +export function Method5() { + return $Call.ByName("main.GreetService.Method5"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js new file mode 100644 index 000000000..fa5b9a262 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js @@ -0,0 +1,35 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {$models.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByName("main.GreetService.Greet", name, title); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js new file mode 100644 index 000000000..649d8d016 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Title +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * Person represents a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js new file mode 100644 index 000000000..e7c70729c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js @@ -0,0 +1,61 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Age is an integer with some predefined values + * @typedef {number} Age + */ + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + * @typedef {Object} Person + * @property {Title} Title + * @property {string} Name + * @property {Age} Age + */ + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js new file mode 100644 index 000000000..2f953de7c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {services$0.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByName("main.GreetService.Greet", name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js new file mode 100644 index 000000000..089a8b685 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js new file mode 100644 index 000000000..e0e2d3014 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js @@ -0,0 +1,27 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.js new file mode 100644 index 000000000..14b4d5da8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * SomeClass renders as a TS class. + * @typedef {$models.SomeClass} SomeClass + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.js new file mode 100644 index 000000000..b99dbf18d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * SomeClass renders as a TS class. + * @typedef {Object} SomeClass + * @property {string} Field + * @property {nobindingshere$0.HowDifferent} Meadow + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js new file mode 100644 index 000000000..8ab5f3462 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js new file mode 100644 index 000000000..26922b7eb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * Person is a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js new file mode 100644 index 000000000..5743a9055 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + * @typedef {Object} Person + * @property {string} Name + * @property {services$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js new file mode 100644 index 000000000..a27b0fea8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js new file mode 100644 index 000000000..8ab5f3462 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js new file mode 100644 index 000000000..8a9890617 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js @@ -0,0 +1,17 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {other$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js new file mode 100644 index 000000000..98097e64f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js new file mode 100644 index 000000000..e2ba84581 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js new file mode 100644 index 000000000..bba7ea7ea --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js new file mode 100644 index 000000000..f89bfc417 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js @@ -0,0 +1,30 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function GreetWithContext(name) { + return $Call.ByName("main.GreetService.GreetWithContext", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js new file mode 100644 index 000000000..bba7ea7ea --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js new file mode 100644 index 000000000..80fdcd24c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js @@ -0,0 +1,98 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +import * as $models from "./models.js"; + +/** + * @template S + * @typedef {$models.BasicCstrAlias} BasicCstrAlias + */ + +/** + * @template R + * @typedef {$models.ComparableCstrAlias} ComparableCstrAlias + */ + +/** + * @typedef {$models.EmbeddedCustomInterface} EmbeddedCustomInterface + */ + +/** + * @typedef {$models.EmbeddedOriginalInterface} EmbeddedOriginalInterface + */ + +/** + * @typedef {$models.EmbeddedPointer} EmbeddedPointer + */ + +/** + * @typedef {$models.EmbeddedPointerPtr} EmbeddedPointerPtr + */ + +/** + * @typedef {$models.EmbeddedValue} EmbeddedValue + */ + +/** + * @typedef {$models.EmbeddedValuePtr} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {$models.GoodTildeCstrAlias} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {$models.InterfaceCstrAlias} InterfaceCstrAlias + */ + +/** + * @template R,S,T,U,V,W,X,Y,Z + * @typedef {$models.Maps} Maps + */ + +/** + * @template X + * @typedef {$models.MixedCstrAlias} MixedCstrAlias + */ + +/** + * @template V + * @typedef {$models.NonBasicCstrAlias} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {$models.PointableCstrAlias} PointableCstrAlias + */ + +/** + * @typedef {$models.PointerAlias} PointerAlias + */ + +/** + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * @typedef {$models.StringAlias} StringAlias + */ + +/** + * @typedef {$models.StringType} StringType + */ + +/** + * @typedef {$models.ValueAlias} ValueAlias + */ + +/** + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js new file mode 100644 index 000000000..cee61a5e4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js @@ -0,0 +1,240 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @template S + * @typedef {S} BasicCstrAlias + */ + +/** + * @template R + * @typedef {R} ComparableCstrAlias + */ + +/** + * @typedef {string} EmbeddedCustomInterface + */ + +/** + * @typedef {string} EmbeddedOriginalInterface + */ + +/** + * @typedef {string} EmbeddedPointer + */ + +/** + * @typedef {string} EmbeddedPointerPtr + */ + +/** + * @typedef {string} EmbeddedValue + */ + +/** + * @typedef {string} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {U} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {Y} InterfaceCstrAlias + */ + +/** + * @template R,S,T,U,V,W,X,Y,Z + * @typedef {Object} Maps + * @property {{ [_: string]: number } | null} Bool - Reject + * @property {{ [_: `${number}`]: number } | null} Int - Accept + * @property {{ [_: `${number}`]: number } | null} Uint - Accept + * @property {{ [_: string]: number } | null} Float - Reject + * @property {{ [_: string]: number } | null} Complex - Reject + * @property {{ [_: `${number}`]: number } | null} Byte - Accept + * @property {{ [_: `${number}`]: number } | null} Rune - Accept + * @property {{ [_: string]: number } | null} String - Accept + * @property {{ [_: string]: number } | null} IntPtr - Reject + * @property {{ [_: string]: number } | null} UintPtr - Reject + * @property {{ [_: string]: number } | null} FloatPtr - Reject + * @property {{ [_: string]: number } | null} ComplexPtr - Reject + * @property {{ [_: string]: number } | null} StringPtr - Reject + * @property {{ [_: string]: number } | null} NTM - Reject + * @property {{ [_: string]: number } | null} NTMPtr - Reject + * @property {{ [_: ValueTextMarshaler]: number } | null} VTM - Accept + * @property {{ [_: ValueTextMarshaler]: number } | null} VTMPtr - Accept + * @property {{ [_: string]: number } | null} PTM - Reject + * @property {{ [_: PointerTextMarshaler]: number } | null} PTMPtr - Accept + * @property {{ [_: string]: number } | null} JTM - Accept, hide + * @property {{ [_: string]: number } | null} JTMPtr - Accept, hide + * @property {{ [_: string]: number } | null} A - Reject + * @property {{ [_: string]: number } | null} APtr - Reject + * @property {{ [_: string]: number } | null} TM - Accept, hide + * @property {{ [_: string]: number } | null} TMPtr - Reject + * @property {{ [_: string]: number } | null} CI - Accept, hide + * @property {{ [_: string]: number } | null} CIPtr - Reject + * @property {{ [_: string]: number } | null} EI - Accept, hide + * @property {{ [_: string]: number } | null} EIPtr - Reject + * @property {{ [_: EmbeddedValue]: number } | null} EV - Accept + * @property {{ [_: EmbeddedValue]: number } | null} EVPtr - Accept + * @property {{ [_: EmbeddedValuePtr]: number } | null} EVP - Accept + * @property {{ [_: EmbeddedValuePtr]: number } | null} EVPPtr - Accept + * @property {{ [_: string]: number } | null} EP - Reject + * @property {{ [_: EmbeddedPointer]: number } | null} EPPtr - Accept + * @property {{ [_: EmbeddedPointerPtr]: number } | null} EPP - Accept + * @property {{ [_: EmbeddedPointerPtr]: number } | null} EPPPtr - Accept + * @property {{ [_: EmbeddedCustomInterface]: number } | null} ECI - Accept + * @property {{ [_: EmbeddedCustomInterface]: number } | null} ECIPtr - Accept + * @property {{ [_: EmbeddedOriginalInterface]: number } | null} EOI - Accept + * @property {{ [_: EmbeddedOriginalInterface]: number } | null} EOIPtr - Accept + * @property {{ [_: string]: number } | null} WT - Reject + * @property {{ [_: string]: number } | null} WA - Reject + * @property {{ [_: StringType]: number } | null} ST - Accept + * @property {{ [_: StringAlias]: number } | null} SA - Accept + * @property {{ [_: `${number}`]: number } | null} IntT - Accept + * @property {{ [_: `${number}`]: number } | null} IntA - Accept + * @property {{ [_: string]: number } | null} VT - Reject + * @property {{ [_: string]: number } | null} VTPtr - Reject + * @property {{ [_: string]: number } | null} VPT - Reject + * @property {{ [_: string]: number } | null} VPTPtr - Reject + * @property {{ [_: ValueAlias]: number } | null} VA - Accept + * @property {{ [_: ValueAlias]: number } | null} VAPtr - Accept + * @property {{ [_: string]: number } | null} VPA - Accept, hide + * @property {{ [_: string]: number } | null} VPAPtr - Reject + * @property {{ [_: string]: number } | null} PT - Reject + * @property {{ [_: string]: number } | null} PTPtr - Reject + * @property {{ [_: string]: number } | null} PPT - Reject + * @property {{ [_: string]: number } | null} PPTPtr - Reject + * @property {{ [_: string]: number } | null} PA - Reject + * @property {{ [_: PointerAlias]: number } | null} PAPtr - Accept + * @property {{ [_: string]: number } | null} PPA - Accept, hide + * @property {{ [_: string]: number } | null} PPAPtr - Reject + * @property {{ [_: string]: number } | null} IT - Accept, hide + * @property {{ [_: string]: number } | null} ITPtr - Reject + * @property {{ [_: string]: number } | null} IPT - Reject + * @property {{ [_: string]: number } | null} IPTPtr - Reject + * @property {{ [_: string]: number } | null} IA - Accept, hide + * @property {{ [_: string]: number } | null} IAPtr - Reject + * @property {{ [_: string]: number } | null} IPA - Reject + * @property {{ [_: string]: number } | null} IPAPtr - Reject + * @property {{ [_: string]: number } | null} TPR - Soft reject + * @property {{ [_: string]: number } | null} TPRPtr - Soft reject + * @property {{ [_: string]: number } | null} TPS - Accept, hide + * @property {{ [_: string]: number } | null} TPSPtr - Soft reject + * @property {{ [_: string]: number } | null} TPT - Soft reject + * @property {{ [_: string]: number } | null} TPTPtr - Soft reject + * @property {{ [_: string]: number } | null} TPU - Accept, hide + * @property {{ [_: string]: number } | null} TPUPtr - Soft reject + * @property {{ [_: string]: number } | null} TPV - Accept, hide + * @property {{ [_: string]: number } | null} TPVPtr - Soft reject + * @property {{ [_: string]: number } | null} TPW - Soft reject + * @property {{ [_: string]: number } | null} TPWPtr - Accept, hide + * @property {{ [_: string]: number } | null} TPX - Accept, hide + * @property {{ [_: string]: number } | null} TPXPtr - Soft reject + * @property {{ [_: string]: number } | null} TPY - Accept, hide + * @property {{ [_: string]: number } | null} TPYPtr - Soft reject + * @property {{ [_: string]: number } | null} TPZ - Accept, hide + * @property {{ [_: string]: number } | null} TPZPtr - Soft reject + * @property {{ [_: string]: number } | null} GAR - Soft reject + * @property {{ [_: string]: number } | null} GARPtr - Soft reject + * @property {{ [_: string]: number } | null} GAS - Accept, hide + * @property {{ [_: string]: number } | null} GASPtr - Soft reject + * @property {{ [_: string]: number } | null} GAT - Soft reject + * @property {{ [_: string]: number } | null} GATPtr - Soft reject + * @property {{ [_: string]: number } | null} GAU - Accept, hide + * @property {{ [_: string]: number } | null} GAUPtr - Soft reject + * @property {{ [_: string]: number } | null} GAV - Accept, hide + * @property {{ [_: string]: number } | null} GAVPtr - Soft reject + * @property {{ [_: string]: number } | null} GAW - Soft reject + * @property {{ [_: string]: number } | null} GAWPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAX - Accept, hide + * @property {{ [_: string]: number } | null} GAXPtr - Soft reject + * @property {{ [_: string]: number } | null} GAY - Accept, hide + * @property {{ [_: string]: number } | null} GAYPtr - Soft reject + * @property {{ [_: string]: number } | null} GAZ - Accept, hide + * @property {{ [_: string]: number } | null} GAZPtr - Soft reject + * @property {{ [_: `${number}`]: number } | null} GACi - Accept, hide + * @property {{ [_: ComparableCstrAlias]: number } | null} GACV - Accept + * @property {{ [_: string]: number } | null} GACP - Reject + * @property {{ [_: string]: number } | null} GACiPtr - Reject + * @property {{ [_: string]: number } | null} GACVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GACPPtr - Accept, hide + * @property {{ [_: `${number}`]: number } | null} GABi - Accept, hide + * @property {{ [_: BasicCstrAlias]: number } | null} GABs - Accept + * @property {{ [_: string]: number } | null} GABiPtr - Reject + * @property {{ [_: string]: number } | null} GABT - Reject + * @property {{ [_: string]: number } | null} GABTPtr - Reject + * @property {{ [_: GoodTildeCstrAlias]: number } | null} GAGT - Accept + * @property {{ [_: string]: number } | null} GAGTPtr - Accept, hide + * @property {{ [_: NonBasicCstrAlias]: number } | null} GANBV - Accept + * @property {{ [_: string]: number } | null} GANBP - Accept, hide + * @property {{ [_: string]: number } | null} GANBVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GANBPPtr - Reject + * @property {{ [_: PointableCstrAlias]: number } | null} GAPlV1 - Accept + * @property {{ [_: PointableCstrAlias]: number } | null} GAPlV2 - Accept + * @property {{ [_: string]: number } | null} GAPlP1 - Reject + * @property {{ [_: PointableCstrAlias]: number } | null} GAPlP2 - Accept + * @property {{ [_: string]: number } | null} GAPlVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAPlPPtr - Accept, hide + * @property {{ [_: `${number}`]: number } | null} GAMi - Accept, hide + * @property {{ [_: MixedCstrAlias]: number } | null} GAMS - Accept + * @property {{ [_: MixedCstrAlias]: number } | null} GAMV - Accept + * @property {{ [_: string]: number } | null} GAMSPtr - Reject + * @property {{ [_: string]: number } | null} GAMVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAII - Accept, hide + * @property {{ [_: InterfaceCstrAlias]: number } | null} GAIV - Accept + * @property {{ [_: string]: number } | null} GAIP - Accept, hide + * @property {{ [_: string]: number } | null} GAIIPtr - Reject + * @property {{ [_: string]: number } | null} GAIVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAIPPtr - Reject + * @property {{ [_: string]: number } | null} GAPrV - Accept, hide + * @property {{ [_: string]: number } | null} GAPrP - Accept, hide + * @property {{ [_: string]: number } | null} GAPrVPtr - Reject + * @property {{ [_: string]: number } | null} GAPrPPtr - Reject + */ + +/** + * @template X + * @typedef {X} MixedCstrAlias + */ + +/** + * @template V + * @typedef {V} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {W} PointableCstrAlias + */ + +/** + * @typedef {PointerTextMarshaler} PointerAlias + */ + +/** + * @typedef {string} PointerTextMarshaler + */ + +/** + * @typedef {string} StringAlias + */ + +/** + * @typedef {string} StringType + */ + +/** + * @typedef {ValueTextMarshaler} ValueAlias + */ + +/** + * @typedef {string} ValueTextMarshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js new file mode 100644 index 000000000..e207d968c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>>} + */ +export function Method() { + return $Call.ByName("main.Service.Method"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js new file mode 100644 index 000000000..0f2edd9c7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js @@ -0,0 +1,124 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +import * as $models from "./models.js"; + +/** + * any + * @typedef {$models.AliasJsonMarshaler} AliasJsonMarshaler + */ + +/** + * any + * @typedef {$models.AliasMarshaler} AliasMarshaler + */ + +/** + * struct{} + * @typedef {$models.AliasNonMarshaler} AliasNonMarshaler + */ + +/** + * string + * @typedef {$models.AliasTextMarshaler} AliasTextMarshaler + */ + +/** + * @typedef {$models.Data} Data + */ + +/** + * any + * @typedef {$models.ImplicitJsonButText} ImplicitJsonButText + */ + +/** + * any + * @typedef {$models.ImplicitJsonMarshaler} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitMarshaler} ImplicitMarshaler + */ + +/** + * string + * @typedef {$models.ImplicitNonJson} ImplicitNonJson + */ + +/** + * class{ Marshaler, TextMarshaler } + * @typedef {$models.ImplicitNonMarshaler} ImplicitNonMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitNonText} ImplicitNonText + */ + +/** + * any + * @typedef {$models.ImplicitTextButJson} ImplicitTextButJson + */ + +/** + * string + * @typedef {$models.ImplicitTextMarshaler} ImplicitTextMarshaler + */ + +/** + * class {} + * @typedef {$models.NonMarshaler} NonMarshaler + */ + +/** + * any + * @typedef {$models.PointerJsonMarshaler} PointerJsonMarshaler + */ + +/** + * any + * @typedef {$models.PointerMarshaler} PointerMarshaler + */ + +/** + * string + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingJsonMarshaler} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingMarshaler} UnderlyingMarshaler + */ + +/** + * string + * @typedef {$models.UnderlyingTextMarshaler} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {$models.ValueJsonMarshaler} ValueJsonMarshaler + */ + +/** + * any + * @typedef {$models.ValueMarshaler} ValueMarshaler + */ + +/** + * string + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js new file mode 100644 index 000000000..a956da60f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js @@ -0,0 +1,186 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + * @typedef {any} AliasJsonMarshaler + */ + +/** + * any + * @typedef {any} AliasMarshaler + */ + +/** + * struct{} + * @typedef { { + * } } AliasNonMarshaler + */ + +/** + * string + * @typedef {string} AliasTextMarshaler + */ + +/** + * @typedef {Object} Data + * @property {NonMarshaler} NM + * @property {NonMarshaler | null} NMPtr - NonMarshaler | null + * @property {ValueJsonMarshaler} VJM + * @property {ValueJsonMarshaler | null} VJMPtr - ValueJsonMarshaler | null + * @property {PointerJsonMarshaler} PJM + * @property {PointerJsonMarshaler | null} PJMPtr - PointerJsonMarshaler | null + * @property {ValueTextMarshaler} VTM + * @property {ValueTextMarshaler | null} VTMPtr - ValueTextMarshaler | null + * @property {PointerTextMarshaler} PTM + * @property {PointerTextMarshaler | null} PTMPtr - PointerTextMarshaler | null + * @property {ValueMarshaler} VM + * @property {ValueMarshaler | null} VMPtr - ValueMarshaler | null + * @property {PointerMarshaler} PM + * @property {PointerMarshaler | null} PMPtr - PointerMarshaler | null + * @property {UnderlyingJsonMarshaler} UJM + * @property {UnderlyingJsonMarshaler | null} UJMPtr - UnderlyingJsonMarshaler | null + * @property {UnderlyingTextMarshaler} UTM + * @property {UnderlyingTextMarshaler | null} UTMPtr - UnderlyingTextMarshaler | null + * @property {UnderlyingMarshaler} UM + * @property {UnderlyingMarshaler | null} UMPtr - UnderlyingMarshaler | null + * @property {any} JM - any + * @property {any | null} JMPtr - any | null + * @property {string} TM - string + * @property {string | null} TMPtr - string | null + * @property {any} CJM - any + * @property {any | null} CJMPtr - any | null + * @property {string} CTM - string + * @property {string | null} CTMPtr - string | null + * @property {any} CM - any + * @property {any | null} CMPtr - any | null + * @property {AliasNonMarshaler} ANM + * @property {AliasNonMarshaler | null} ANMPtr - AliasNonMarshaler | null + * @property {AliasJsonMarshaler} AJM + * @property {AliasJsonMarshaler | null} AJMPtr - AliasJsonMarshaler | null + * @property {AliasTextMarshaler} ATM + * @property {AliasTextMarshaler | null} ATMPtr - AliasTextMarshaler | null + * @property {AliasMarshaler} AM + * @property {AliasMarshaler | null} AMPtr - AliasMarshaler | null + * @property {ImplicitJsonMarshaler} ImJM + * @property {ImplicitJsonMarshaler | null} ImJMPtr - ImplicitJsonMarshaler | null + * @property {ImplicitTextMarshaler} ImTM + * @property {ImplicitTextMarshaler | null} ImTMPtr - ImplicitTextMarshaler | null + * @property {ImplicitMarshaler} ImM + * @property {ImplicitMarshaler | null} ImMPtr - ImplicitMarshaler | null + * @property {ImplicitNonJson} ImNJ + * @property {ImplicitNonJson | null} ImNJPtr - ImplicitNonJson | null + * @property {ImplicitNonText} ImNT + * @property {ImplicitNonText | null} ImNTPtr - ImplicitNonText | null + * @property {ImplicitNonMarshaler} ImNM + * @property {ImplicitNonMarshaler | null} ImNMPtr - ImplicitNonMarshaler | null + * @property {ImplicitJsonButText} ImJbT + * @property {ImplicitJsonButText | null} ImJbTPtr - ImplicitJsonButText | null + * @property {ImplicitTextButJson} ImTbJ + * @property {ImplicitTextButJson | null} ImTbJPtr - ImplicitTextButJson | null + */ + +/** + * any + * @typedef {any} ImplicitJsonButText + */ + +/** + * any + * @typedef {any} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {any} ImplicitMarshaler + */ + +/** + * string + * @typedef {string} ImplicitNonJson + */ + +/** + * class{ Marshaler, TextMarshaler } + * @typedef {Object} ImplicitNonMarshaler + * @property {json$0.Marshaler} Marshaler + * @property {encoding$0.TextMarshaler} TextMarshaler + */ + +/** + * any + * @typedef {any} ImplicitNonText + */ + +/** + * any + * @typedef {any} ImplicitTextButJson + */ + +/** + * string + * @typedef {string} ImplicitTextMarshaler + */ + +/** + * class {} + * @typedef { { + * } } NonMarshaler + */ + +/** + * any + * @typedef {any} PointerJsonMarshaler + */ + +/** + * any + * @typedef {any} PointerMarshaler + */ + +/** + * string + * @typedef {string} PointerTextMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingMarshaler + */ + +/** + * string + * @typedef {string} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {any} ValueJsonMarshaler + */ + +/** + * any + * @typedef {any} ValueMarshaler + */ + +/** + * string + * @typedef {string} ValueTextMarshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js new file mode 100644 index 000000000..9ad0c6d96 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Data>} + */ +export function Method() { + return $Call.ByName("main.Service.Method"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js new file mode 100644 index 000000000..0b7f42650 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +import * as $models from "./models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + * @template How + * @typedef {$models.HowDifferent} HowDifferent + */ + +/** + * Impersonator gets their fields from other people. + * @typedef {$models.Impersonator} Impersonator + */ + +/** + * Person is not a number. + * @typedef {$models.Person} Person + */ + +/** + * PrivatePerson gets their fields from hidden sources. + * @typedef {$models.PrivatePerson} PrivatePerson + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js new file mode 100644 index 000000000..36f231303 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js @@ -0,0 +1,44 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + * @template How + * @typedef {Object} HowDifferent + * @property {string} Name - They have a name as well. + * @property {({ [_: string]: How } | null)[] | null} Differences - But they may have many differences. + */ + +/** + * Impersonator gets their fields from other people. + * @typedef {other$0.OtherPerson} Impersonator + */ + +/** + * Person is not a number. + * @typedef {Object} Person + * @property {string} Name - They have a name. + * @property {Impersonator[]} Friends - Exactly 4 sketchy friends. + */ + +/** + * PrivatePerson gets their fields from hidden sources. + * @typedef {personImpl} PrivatePerson + */ + +/** + * @typedef {Object} personImpl + * @property {string} Nickname - Nickname conceals a person's identity. + * @property {string} Name - They have a name. + * @property {Impersonator[]} Friends - Exactly 4 sketchy friends. + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.js new file mode 100644 index 000000000..c3eabd022 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * StringPtr is a nullable string. + * @typedef {$models.StringPtr} StringPtr + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.js new file mode 100644 index 000000000..04f4d47dd --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * StringPtr is a nullable string. + * @typedef {string | null} StringPtr + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js new file mode 100644 index 000000000..33246d35e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js @@ -0,0 +1,16 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +import * as $models from "./models.js"; + +/** + * OtherPerson is like a person, but different. + * @template T + * @typedef {$models.OtherPerson} OtherPerson + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js new file mode 100644 index 000000000..63a2ee722 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherPerson is like a person, but different. + * @template T + * @typedef {Object} OtherPerson + * @property {string} Name - They have a name as well. + * @property {T[] | null} Differences - But they may have many differences. + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js new file mode 100644 index 000000000..c3f8ff04b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other.OtherMethods.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js new file mode 100644 index 000000000..92435f679 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOne"); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js new file mode 100644 index 000000000..490c12c08 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("main.EmbedOther.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js new file mode 100644 index 000000000..5f940b2c7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByName("main.EmbedService.LikeThisOne"); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("main.EmbedService.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js new file mode 100644 index 000000000..f8c6b19b2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} $0 + * @returns {$CancellablePromise} + */ +export function Greet($0) { + return $Call.ByName("main.GreetService.Greet", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js new file mode 100644 index 000000000..734fb02e7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js new file mode 100644 index 000000000..e2ba84581 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js new file mode 100644 index 000000000..2fdca31cc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js new file mode 100644 index 000000000..e2ba84581 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js new file mode 100644 index 000000000..2fdca31cc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js new file mode 100644 index 000000000..8ab5f3462 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js new file mode 100644 index 000000000..41b452f57 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js @@ -0,0 +1,17 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {services$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js new file mode 100644 index 000000000..911d42560 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js new file mode 100644 index 000000000..eca08a018 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js @@ -0,0 +1,360 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] | null } | null>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js new file mode 100644 index 000000000..ec6136e27 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {Person | null} Parent + * @property {{"Age": number, "Address": {"Street": string}}} Details + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js new file mode 100644 index 000000000..eca08a018 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js @@ -0,0 +1,360 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] | null } | null>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js new file mode 100644 index 000000000..ec6136e27 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {Person | null} Parent + * @property {{"Age": number, "Address": {"Street": string}}} Details + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js new file mode 100644 index 000000000..bba7ea7ea --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js new file mode 100644 index 000000000..bba7ea7ea --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js new file mode 100644 index 000000000..8ab5f3462 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js new file mode 100644 index 000000000..977600693 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js @@ -0,0 +1,16 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * Person is a person! + * They have a name and an address + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js new file mode 100644 index 000000000..ab5cea255 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js @@ -0,0 +1,19 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + * @typedef {Object} Person + * @property {string} Name + * @property {services$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js new file mode 100644 index 000000000..6594edc5b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/warnings.log b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/warnings.log new file mode 100644 index 000000000..ce8369307 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/warnings.log @@ -0,0 +1,83 @@ +[warn] /testcases/complex_json/main.go:127:2: event 'collision' has one of multiple definitions here with data type map[string]int +[warn] /testcases/events_only/events.go:20:2: event 'collision' has one of multiple definitions here with data type int +[warn] /testcases/events_only/events.go:21:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:10:5: `application.RegisterEvent` is instantiated here but not called +[warn] /testcases/events_only/other.go:13:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:17:2: data type []T for event 'parametric' contains unresolved type parameters and will be ignored` +[warn] /testcases/events_only/other.go:22:2: event 'common:ApplicationStarted' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic +[warn] /testcases/marshalers/main.go:212:2: event 'collision' has one of multiple definitions here with data type *struct{Field []bool} +[warn] /testcases/marshalers/main.go:214:2: data type encoding/json.Marshaler for event 'interface' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors +[warn] dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only +[warn] event 'collision' has multiple conflicting definitions and will be ignored +[warn] events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly +[warn] generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/index.ts new file mode 100644 index 000000000..ba2885969 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + TextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/json/index.ts new file mode 100644 index 000000000..00ec01151 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/json/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + Marshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/json/models.ts new file mode 100644 index 000000000..8cd1a164f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/json/models.ts @@ -0,0 +1,12 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + */ +export type Marshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/models.ts new file mode 100644 index 000000000..235dfce3e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/models.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + */ +export type TextMarshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/eventcreate.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/eventcreate.ts new file mode 100644 index 000000000..9cc7781aa --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/eventcreate.ts @@ -0,0 +1,39 @@ +//@ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../encoding/json/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as events_only$0 from "./generator/testcases/events_only/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as more$0 from "./generator/testcases/no_bindings_here/more/models.js"; + +function configure() { + Object.freeze(Object.assign($Create.Events, { + "events_only:class": $$createType0, + "events_only:map": $$createType2, + "events_only:other": $$createType3, + "overlap": $$createType6, + })); +} + +// Private type creation functions +const $$createType0 = events_only$0.SomeClass.createFrom; +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = $Create.Map($Create.Any, $$createType1); +const $$createType3 = $Create.Array($Create.Any); +const $$createType4 = $Create.Array($Create.Any); +const $$createType5 = $Create.Struct({ + "Field": $$createType4, +}); +const $$createType6 = $Create.Nullable($$createType5); + +configure(); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/eventdata.d.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/eventdata.d.ts new file mode 100644 index 000000000..d265e3adc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/eventdata.d.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type { Events } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as json$0 from "../../../../../encoding/json/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as events_only$0 from "./generator/testcases/events_only/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as more$0 from "./generator/testcases/no_bindings_here/more/models.js"; + +declare module "/wails/runtime.js" { + namespace Events { + interface CustomEvents { + "events_only:class": events_only$0.SomeClass; + "events_only:map": { [_: string]: number[] }; + "events_only:nodata": void; + "events_only:other": more$0.StringPtr[]; + "events_only:string": string; + "interface": json$0.Marshaler; + "overlap": {"Field": boolean[]} | null; + } + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts new file mode 100644 index 000000000..3c6865881 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts @@ -0,0 +1,82 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + */ +export function Get(aliasValue: $models.Alias): $CancellablePromise<$models.Person> { + return $Call.ByID(1928502664, aliasValue).then(($result: any) => { + return $$createType0($result); + }); +} + +/** + * Apparently, aliases are all the rage right now. + */ +export function GetButAliased(p: $models.AliasedPerson): $CancellablePromise<$models.StrangelyAliasedPerson> { + return $Call.ByID(1896499664, p).then(($result: any) => { + return $$createType0($result); + }); +} + +/** + * Get someone quite different. + */ +export function GetButDifferent(): $CancellablePromise<$models.GenericPerson> { + return $Call.ByID(2240931744).then(($result: any) => { + return $$createType1($result); + }); +} + +export function GetButForeignPrivateAlias(): $CancellablePromise { + return $Call.ByID(643456960).then(($result: any) => { + return $$createType2($result); + }); +} + +export function GetButGenericAliases(): $CancellablePromise<$models.AliasGroup> { + return $Call.ByID(914093800).then(($result: any) => { + return $$createType3($result); + }); +} + +/** + * Greet a lot of unusual things. + */ +export function Greet($0: $models.EmptyAliasStruct, $1: $models.EmptyStruct): $CancellablePromise<$models.AliasStruct> { + return $Call.ByID(1411160069, $0, $1).then(($result: any) => { + return $$createType7($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.GenericPerson.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; +const $$createType3 = $models.AliasGroup.createFrom; +const $$createType4 = $Create.Array($Create.Any); +const $$createType5 = $Create.Array($Create.Any); +const $$createType6 = $Create.Struct({ + "NoMoreIdeas": $$createType5, +}); +const $$createType7 = $Create.Struct({ + "Foo": $$createType4, + "Other": $$createType6, +}); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts new file mode 100644 index 000000000..13f61da0f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + AliasGroup, + AliasedPerson, + EmptyStruct, + GenericPerson, + GenericPersonAlias, + IndirectPersonAlias, + Person, + StrangelyAliasedPerson, + TPIndirectPersonAlias +} from "./models.js"; + +export type { + Alias, + AliasStruct, + EmptyAliasStruct, + GenericAlias, + GenericMapAlias, + GenericPtrAlias, + OtherAliasStruct +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts new file mode 100644 index 000000000..63ca43914 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts @@ -0,0 +1,290 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * A nice type Alias. + */ +export type Alias = number; + +/** + * A class whose fields have various aliased types. + */ +export class AliasGroup { + "GAi": GenericAlias; + "GAP": GenericAlias>; + "GPAs": GenericPtrAlias; + "GPAP": GenericPtrAlias>; + "GMA": GenericMapAlias; + "GPA": GenericPersonAlias; + "IPA": IndirectPersonAlias; + "TPIPA": TPIndirectPersonAlias; + + /** Creates a new AliasGroup instance. */ + constructor($$source: Partial = {}) { + if (!("GAi" in $$source)) { + this["GAi"] = 0; + } + if (!("GAP" in $$source)) { + this["GAP"] = (new GenericPerson()); + } + if (!("GPAs" in $$source)) { + this["GPAs"] = null; + } + if (!("GPAP" in $$source)) { + this["GPAP"] = null; + } + if (!("GMA" in $$source)) { + this["GMA"] = {}; + } + if (!("GPA" in $$source)) { + this["GPA"] = (new GenericPersonAlias()); + } + if (!("IPA" in $$source)) { + this["IPA"] = (new IndirectPersonAlias()); + } + if (!("TPIPA" in $$source)) { + this["TPIPA"] = (new TPIndirectPersonAlias()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new AliasGroup instance from a string or object. + */ + static createFrom($$source: any = {}): AliasGroup { + const $$createField1_0 = $$createType0; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType5; + const $$createField4_0 = $$createType6; + const $$createField5_0 = $$createType8; + const $$createField6_0 = $$createType8; + const $$createField7_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("GAP" in $$parsedSource) { + $$parsedSource["GAP"] = $$createField1_0($$parsedSource["GAP"]); + } + if ("GPAs" in $$parsedSource) { + $$parsedSource["GPAs"] = $$createField2_0($$parsedSource["GPAs"]); + } + if ("GPAP" in $$parsedSource) { + $$parsedSource["GPAP"] = $$createField3_0($$parsedSource["GPAP"]); + } + if ("GMA" in $$parsedSource) { + $$parsedSource["GMA"] = $$createField4_0($$parsedSource["GMA"]); + } + if ("GPA" in $$parsedSource) { + $$parsedSource["GPA"] = $$createField5_0($$parsedSource["GPA"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField6_0($$parsedSource["IPA"]); + } + if ("TPIPA" in $$parsedSource) { + $$parsedSource["TPIPA"] = $$createField7_0($$parsedSource["TPIPA"]); + } + return new AliasGroup($$parsedSource as Partial); + } +} + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + */ +export interface AliasStruct { + /** + * A field with a comment. + */ + "Foo": number[]; + + /** + * Definitely not Foo. + */ + "Bar"?: string; + "Baz"?: string; + + /** + * A nested alias struct. + */ + "Other": OtherAliasStruct; +} + +/** + * An empty struct alias. + */ +export interface EmptyAliasStruct { +} + +/** + * An empty struct. + */ +export class EmptyStruct { + + /** Creates a new EmptyStruct instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new EmptyStruct instance from a string or object. + */ + static createFrom($$source: any = {}): EmptyStruct { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new EmptyStruct($$parsedSource as Partial); + } +} + +/** + * A generic alias that forwards to a type parameter. + */ +export type GenericAlias = T; + +/** + * A generic alias that wraps a map. + */ +export type GenericMapAlias = { [_: string]: U }; + +/** + * A generic struct containing an alias. + */ +export class GenericPerson { + "Name"?: T; + "AliasedField": Alias; + + /** Creates a new GenericPerson instance. */ + constructor($$source: Partial> = {}) { + if (!("AliasedField" in $$source)) { + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class GenericPerson. + */ + static createFrom($$createParamT: (source: any) => T): ($$source?: any) => GenericPerson { + const $$createField0_0 = $$createParamT; + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Name" in $$parsedSource) { + $$parsedSource["Name"] = $$createField0_0($$parsedSource["Name"]); + } + return new GenericPerson($$parsedSource as Partial>); + }; + } +} + +/** + * A generic alias that wraps a generic struct. + */ +export const GenericPersonAlias = GenericPerson; + +/** + * A generic alias that wraps a generic struct. + */ +export type GenericPersonAlias = GenericPerson[]>; + +/** + * A generic alias that wraps a pointer type. + */ +export type GenericPtrAlias = GenericAlias | null; + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export const IndirectPersonAlias = GenericPersonAlias; + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export type IndirectPersonAlias = GenericPersonAlias; + +/** + * Another struct alias. + */ +export interface OtherAliasStruct { + "NoMoreIdeas": number[]; +} + +/** + * A non-generic struct containing an alias. + */ +export class Person { + /** + * The Person's name. + */ + "Name": string; + + /** + * A random alias field. + */ + "AliasedField": Alias; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("AliasedField" in $$source)) { + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person($$parsedSource as Partial); + } +} + +/** + * A class alias. + */ +export const AliasedPerson = Person; + +/** + * A class alias. + */ +export type AliasedPerson = Person; + +/** + * Another class alias, but ordered after its aliased class. + */ +export const StrangelyAliasedPerson = Person; + +/** + * Another class alias, but ordered after its aliased class. + */ +export type StrangelyAliasedPerson = Person; + +/** + * An alias that wraps a class through a typeparam alias. + */ +export const TPIndirectPersonAlias = GenericPerson; + +/** + * An alias that wraps a class through a typeparam alias. + */ +export type TPIndirectPersonAlias = GenericAlias>; + +// Private type creation functions +const $$createType0 = GenericPerson.createFrom($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = $Create.Nullable($$createType1); +const $$createType3 = $Create.Array($Create.Any); +const $$createType4 = GenericPerson.createFrom($$createType3); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Array($Create.Any); +const $$createType8 = GenericPerson.createFrom($$createType7); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts new file mode 100644 index 000000000..bdcf43c67 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts new file mode 100644 index 000000000..8fe0035ed --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function TestMethod(): $CancellablePromise { + return $Call.ByID(2241101727); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts new file mode 100644 index 000000000..8260ce500 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function TestMethod2(): $CancellablePromise { + return $Call.ByID(1556848345); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts new file mode 100644 index 000000000..69c89433a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(person: $models.Person, emb: $models.Embedded1): $CancellablePromise { + return $Call.ByID(1411160069, person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts new file mode 100644 index 000000000..6cdc52c66 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts @@ -0,0 +1,17 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Embedded1, + Person, + Title +} from "./models.js"; + +export type { + Embedded3 +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts new file mode 100644 index 000000000..50f23b52d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts @@ -0,0 +1,237 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Embedded1 { + /** + * Friends should be shadowed in Person by a field of lesser depth + */ + "Friends": number; + + /** + * Vanish should be omitted from Person because there is another field with same depth and no tag + */ + "Vanish": number; + + /** + * StillThere should be shadowed in Person by other field with same depth and a json tag + */ + "StillThere": string; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** Creates a new Embedded1 instance. */ + constructor($$source: Partial = {}) { + if (!("Friends" in $$source)) { + this["Friends"] = 0; + } + if (!("Vanish" in $$source)) { + this["Vanish"] = 0; + } + if (!("StillThere" in $$source)) { + this["StillThere"] = ""; + } + if (!("NamingThingsIsHard" in $$source)) { + this["NamingThingsIsHard"] = "false"; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Embedded1 instance from a string or object. + */ + static createFrom($$source: any = {}): Embedded1 { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Embedded1($$parsedSource as Partial); + } +} + +export type Embedded3 = string; + +/** + * Person represents a person + */ +export class Person { + /** + * Titles is optional in JSON + */ + "Titles"?: Title[]; + + /** + * Names has a + * multiline comment + */ + "Names": string[]; + + /** + * Partner has a custom and complex JSON key + */ + "Partner": Person | null; + "Friends": (Person | null)[]; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * StillThereButRenamed should shadow in Person the other field with same depth and no json tag + */ + "StillThere": Embedded3 | null; + + /** + * StrangeNumber maps to "-" + */ + "-": number; + + /** + * Embedded3 should appear with key "Embedded3" + */ + "Embedded3": Embedded3; + + /** + * StrangerNumber is serialized as a string + */ + "StrangerNumber": `${number}`; + + /** + * StrangestString is optional and serialized as a JSON string + */ + "StrangestString"?: `"${string}"`; + + /** + * StringStrangest is serialized as a JSON string and optional + */ + "StringStrangest"?: `"${string}"`; + + /** + * embedded4 should be optional and appear with key "emb4" + */ + "emb4"?: embedded4; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Names" in $$source)) { + this["Names"] = []; + } + if (!("Partner" in $$source)) { + this["Partner"] = null; + } + if (!("Friends" in $$source)) { + this["Friends"] = []; + } + if (!("NamingThingsIsHard" in $$source)) { + this["NamingThingsIsHard"] = "false"; + } + if (!("StillThere" in $$source)) { + this["StillThere"] = null; + } + if (!("-" in $$source)) { + this["-"] = 0; + } + if (!("Embedded3" in $$source)) { + this["Embedded3"] = ""; + } + if (!("StrangerNumber" in $$source)) { + this["StrangerNumber"] = "0"; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType3; + const $$createField3_0 = $$createType4; + const $$createField11_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Titles" in $$parsedSource) { + $$parsedSource["Titles"] = $$createField0_0($$parsedSource["Titles"]); + } + if ("Names" in $$parsedSource) { + $$parsedSource["Names"] = $$createField1_0($$parsedSource["Names"]); + } + if ("Partner" in $$parsedSource) { + $$parsedSource["Partner"] = $$createField2_0($$parsedSource["Partner"]); + } + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField3_0($$parsedSource["Friends"]); + } + if ("emb4" in $$parsedSource) { + $$parsedSource["emb4"] = $$createField11_0($$parsedSource["emb4"]); + } + return new Person($$parsedSource as Partial); + } +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; + +export class embedded4 { + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * Friends should not be shadowed in Person as embedded4 is not embedded + * from encoding/json's point of view; + * however, it should be shadowed in Embedded1 + */ + "Friends": boolean; + + /** Creates a new embedded4 instance. */ + constructor($$source: Partial = {}) { + if (!("NamingThingsIsHard" in $$source)) { + this["NamingThingsIsHard"] = "false"; + } + if (!("Friends" in $$source)) { + this["Friends"] = false; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new embedded4 instance from a string or object. + */ + static createFrom($$source: any = {}): embedded4 { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new embedded4($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = Person.createFrom; +const $$createType3 = $Create.Nullable($$createType2); +const $$createType4 = $Create.Array($$createType3); +const $$createType5 = embedded4.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts new file mode 100644 index 000000000..cc1e88ad7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts @@ -0,0 +1,32 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + */ +export function Greet(str: string, people: $models.Person[], $2: {"AnotherCount": number, "AnotherOne": $models.Person | null}, assoc: { [_: `${number}`]: boolean | null }, $4: (number | null)[], ...other: string[]): $CancellablePromise<[$models.Person, any, number[]]> { + return $Call.ByID(1411160069, str, people, $2, assoc, $4, other).then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[2] = $$createType1($result[2]); + return $result; + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Array($Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts new file mode 100644 index 000000000..2417aff4c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Person represents a person + */ +export class Person { + "Name": string; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person($$parsedSource as Partial); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts new file mode 100644 index 000000000..c1c70be69 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.StructA, $models.StructC]> { + return $Call.ByID(440020721).then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + return $result; + }); +} + +// Private type creation functions +const $$createType0 = $models.StructA.createFrom; +const $$createType1 = $models.StructC.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts new file mode 100644 index 000000000..4b190b8da --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + StructA, + StructC, + StructE +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts new file mode 100644 index 000000000..9e86cd674 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts @@ -0,0 +1,131 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class StructA { + "B": structB | null; + + /** Creates a new StructA instance. */ + constructor($$source: Partial = {}) { + if (!("B" in $$source)) { + this["B"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructA instance from a string or object. + */ + static createFrom($$source: any = {}): StructA { + const $$createField0_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("B" in $$parsedSource) { + $$parsedSource["B"] = $$createField0_0($$parsedSource["B"]); + } + return new StructA($$parsedSource as Partial); + } +} + +export class StructC { + "D": structD; + + /** Creates a new StructC instance. */ + constructor($$source: Partial = {}) { + if (!("D" in $$source)) { + this["D"] = (new structD()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructC instance from a string or object. + */ + static createFrom($$source: any = {}): StructC { + const $$createField0_0 = $$createType2; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("D" in $$parsedSource) { + $$parsedSource["D"] = $$createField0_0($$parsedSource["D"]); + } + return new StructC($$parsedSource as Partial); + } +} + +export class StructE { + + /** Creates a new StructE instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new StructE instance from a string or object. + */ + static createFrom($$source: any = {}): StructE { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new StructE($$parsedSource as Partial); + } +} + +export class structB { + "A": StructA | null; + + /** Creates a new structB instance. */ + constructor($$source: Partial = {}) { + if (!("A" in $$source)) { + this["A"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structB instance from a string or object. + */ + static createFrom($$source: any = {}): structB { + const $$createField0_0 = $$createType4; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField0_0($$parsedSource["A"]); + } + return new structB($$parsedSource as Partial); + } +} + +export class structD { + "E": StructE; + + /** Creates a new structD instance. */ + constructor($$source: Partial = {}) { + if (!("E" in $$source)) { + this["E"] = (new StructE()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structD instance from a string or object. + */ + static createFrom($$source: any = {}): structD { + const $$createField0_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("E" in $$parsedSource) { + $$parsedSource["E"] = $$createField0_0($$parsedSource["E"]); + } + return new structD($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = structB.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = structD.createFrom; +const $$createType3 = StructA.createFrom; +const $$createType4 = $Create.Nullable($$createType3); +const $$createType5 = StructE.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts new file mode 100644 index 000000000..cbddfb346 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts @@ -0,0 +1,63 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]> { + return $Call.ByID(440020721).then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType9($result[1]); + return $result; + }); +} + +// Private type creation functions +var $$createType0 = (function $$initCreateType0(...args: any[]): any { + if ($$createType0 === $$initCreateType0) { + $$createType0 = $$createType3; + } + return $$createType0(...args); +}); +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = $Create.Map($Create.Any, $$createType1); +const $$createType3 = $Create.Array($$createType2); +var $$createType4 = (function $$initCreateType4(...args: any[]): any { + if ($$createType4 === $$initCreateType4) { + $$createType4 = $$createType8; + } + return $$createType4(...args); +}); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Array($Create.Any); +const $$createType7 = $Create.Struct({ + "X": $$createType5, + "Y": $$createType6, +}); +const $$createType8 = $Create.Array($$createType7); +var $$createType9 = (function $$initCreateType9(...args: any[]): any { + if ($$createType9 === $$initCreateType9) { + $$createType9 = $$createType13; + } + return $$createType9(...args); +}); +const $$createType10 = $Create.Nullable($$createType9); +const $$createType11 = $Create.Array($$createType4); +const $$createType12 = $Create.Struct({ + "X": $$createType10, + "Y": $$createType11, +}); +const $$createType13 = $Create.Array($$createType12); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts new file mode 100644 index 000000000..16cef660c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Alias, + Cyclic, + GenericCyclic +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts new file mode 100644 index 000000000..93d1ef5c6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts @@ -0,0 +1,12 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export type Alias = Cyclic | null; + +export type Cyclic = { [_: string]: Alias }[]; + +export type GenericCyclic = {"X": GenericCyclic | null, "Y": T[]}[]; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts new file mode 100644 index 000000000..b9fbdba96 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Classes!"); +console.log("Hello TS!"); +console.log("Hello TS Classes!"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts new file mode 100644 index 000000000..b968330db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.InternalModel): $CancellablePromise { + return $Call.ByID(538079117, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts new file mode 100644 index 000000000..4d242fc2c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts @@ -0,0 +1,54 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * An exported but internal model. + */ +export class InternalModel { + "Field": string; + + /** Creates a new InternalModel instance. */ + constructor($$source: Partial = {}) { + if (!("Field" in $$source)) { + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new InternalModel instance from a string or object. + */ + static createFrom($$source: any = {}): InternalModel { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new InternalModel($$parsedSource as Partial); + } +} + +/** + * An unexported model. + */ +export class unexportedModel { + "Field": string; + + /** Creates a new unexportedModel instance. */ + constructor($$source: Partial = {}) { + if (!("Field" in $$source)) { + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new unexportedModel instance from a string or object. + */ + static createFrom($$source: any = {}): unexportedModel { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new unexportedModel($$parsedSource as Partial); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts new file mode 100644 index 000000000..e52a0ccb8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Dummy +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts new file mode 100644 index 000000000..b927155d5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Dummy { + + /** Creates a new Dummy instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new Dummy instance from a string or object. + */ + static createFrom($$source: any = {}): Dummy { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Dummy($$parsedSource as Partial); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_t.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_t.ts new file mode 100644 index 000000000..6703820f1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_tc.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_tc.ts new file mode 100644 index 000000000..15d2994e9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_tc.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("TS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts new file mode 100644 index 000000000..338c7fbd1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +function InternalMethod($0: string): $CancellablePromise { + return $Call.ByID(3518775569, $0); +} + +export function VisibleMethod($0: otherpackage$0.Dummy): $CancellablePromise { + return $Call.ByID(474018228, $0); +} + +export async function CustomMethod(arg: string): Promise { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js new file mode 100644 index 000000000..724e79e12 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Classes"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts new file mode 100644 index 000000000..253d3f2f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_tc.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_tc.ts new file mode 100644 index 000000000..66b739d3a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_tc.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts new file mode 100644 index 000000000..a819daffd --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.unexportedModel): $CancellablePromise { + return $Call.ByID(37626172, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts new file mode 100644 index 000000000..b5d189f76 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts @@ -0,0 +1,47 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Comment 1. + */ +export function Method1(): $CancellablePromise { + return $Call.ByID(841558284); +} + +/** + * Comment 2. + */ +export function Method2(): $CancellablePromise { + return $Call.ByID(891891141); +} + +/** + * Comment 3a. + * Comment 3b. + */ +export function Method3(): $CancellablePromise { + return $Call.ByID(875113522); +} + +/** + * Comment 4. + */ +export function Method4(): $CancellablePromise { + return $Call.ByID(791225427); +} + +/** + * Comment 5. + */ +export function Method5(): $CancellablePromise { + return $Call.ByID(774447808); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts new file mode 100644 index 000000000..10de8838c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: $models.Title): $CancellablePromise { + return $Call.ByID(1411160069, name, title); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts new file mode 100644 index 000000000..3b4d2f5c6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Person, + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts new file mode 100644 index 000000000..a50282a38 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts @@ -0,0 +1,82 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Age is an integer with some predefined values + */ +export type Age = number; + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + */ +export class Person { + "Title": Title; + "Name": string; + "Age": Age; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Title" in $$source)) { + this["Title"] = Title.$zero; + } + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Age" in $$source)) { + this["Age"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person($$parsedSource as Partial); + } +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts new file mode 100644 index 000000000..3bce7b000 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: services$0.Title): $CancellablePromise { + return $Call.ByID(1411160069, name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts new file mode 100644 index 000000000..01c612edc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts new file mode 100644 index 000000000..661222bdf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.ts new file mode 100644 index 000000000..bb6e0a17f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + SomeClass +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.ts new file mode 100644 index 000000000..4719f61cf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.ts @@ -0,0 +1,45 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * SomeClass renders as a TS class. + */ +export class SomeClass { + "Field": string; + "Meadow": nobindingshere$0.HowDifferent; + + /** Creates a new SomeClass instance. */ + constructor($$source: Partial = {}) { + if (!("Field" in $$source)) { + this["Field"] = ""; + } + if (!("Meadow" in $$source)) { + this["Meadow"] = (new nobindingshere$0.HowDifferent()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new SomeClass instance from a string or object. + */ + static createFrom($$source: any = {}): SomeClass { + const $$createField1_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Meadow" in $$parsedSource) { + $$parsedSource["Meadow"] = $$createField1_0($$parsedSource["Meadow"]); + } + return new SomeClass($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = nobindingshere$0.HowDifferent.createFrom($Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts new file mode 100644 index 000000000..88cafa9d1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts new file mode 100644 index 000000000..f1ff262a1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts @@ -0,0 +1,46 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + */ +export class Person { + "Name": string; + "Address": services$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
        = {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
        ); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts new file mode 100644 index 000000000..8dc381545 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(2007737399).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts new file mode 100644 index 000000000..88cafa9d1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts new file mode 100644 index 000000000..88b2c78db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +export class Person { + "Name": string; + "Address": other$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = other$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
        = {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
        ); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts new file mode 100644 index 000000000..fe69a1da0 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(2447353446).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts new file mode 100644 index 000000000..62283209b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts new file mode 100644 index 000000000..6e6ac2007 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts new file mode 100644 index 000000000..386913d65 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts @@ -0,0 +1,25 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * Greet someone + */ +export function GreetWithContext(name: string): $CancellablePromise { + return $Call.ByID(1310150960, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts new file mode 100644 index 000000000..6e6ac2007 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts new file mode 100644 index 000000000..b5890af28 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts @@ -0,0 +1,33 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Maps +} from "./models.js"; + +export type { + BasicCstrAlias, + ComparableCstrAlias, + EmbeddedCustomInterface, + EmbeddedOriginalInterface, + EmbeddedPointer, + EmbeddedPointerPtr, + EmbeddedValue, + EmbeddedValuePtr, + GoodTildeCstrAlias, + InterfaceCstrAlias, + MixedCstrAlias, + NonBasicCstrAlias, + PointableCstrAlias, + PointerAlias, + PointerTextMarshaler, + StringAlias, + StringType, + ValueAlias, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts new file mode 100644 index 000000000..0d26a6b0b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts @@ -0,0 +1,1886 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export type BasicCstrAlias = S; + +export type ComparableCstrAlias = R; + +export type EmbeddedCustomInterface = string; + +export type EmbeddedOriginalInterface = string; + +export type EmbeddedPointer = string; + +export type EmbeddedPointerPtr = string; + +export type EmbeddedValue = string; + +export type EmbeddedValuePtr = string; + +export type GoodTildeCstrAlias = U; + +export type InterfaceCstrAlias = Y; + +export class Maps { + /** + * Reject + */ + "Bool": { [_: string]: number }; + + /** + * Accept + */ + "Int": { [_: `${number}`]: number }; + + /** + * Accept + */ + "Uint": { [_: `${number}`]: number }; + + /** + * Reject + */ + "Float": { [_: string]: number }; + + /** + * Reject + */ + "Complex": { [_: string]: number }; + + /** + * Accept + */ + "Byte": { [_: `${number}`]: number }; + + /** + * Accept + */ + "Rune": { [_: `${number}`]: number }; + + /** + * Accept + */ + "String": { [_: string]: number }; + + /** + * Reject + */ + "IntPtr": { [_: string]: number }; + + /** + * Reject + */ + "UintPtr": { [_: string]: number }; + + /** + * Reject + */ + "FloatPtr": { [_: string]: number }; + + /** + * Reject + */ + "ComplexPtr": { [_: string]: number }; + + /** + * Reject + */ + "StringPtr": { [_: string]: number }; + + /** + * Reject + */ + "NTM": { [_: string]: number }; + + /** + * Reject + */ + "NTMPtr": { [_: string]: number }; + + /** + * Accept + */ + "VTM": { [_: ValueTextMarshaler]: number }; + + /** + * Accept + */ + "VTMPtr": { [_: ValueTextMarshaler]: number }; + + /** + * Reject + */ + "PTM": { [_: string]: number }; + + /** + * Accept + */ + "PTMPtr": { [_: PointerTextMarshaler]: number }; + + /** + * Accept, hide + */ + "JTM": { [_: string]: number }; + + /** + * Accept, hide + */ + "JTMPtr": { [_: string]: number }; + + /** + * Reject + */ + "A": { [_: string]: number }; + + /** + * Reject + */ + "APtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TM": { [_: string]: number }; + + /** + * Reject + */ + "TMPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "CI": { [_: string]: number }; + + /** + * Reject + */ + "CIPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "EI": { [_: string]: number }; + + /** + * Reject + */ + "EIPtr": { [_: string]: number }; + + /** + * Accept + */ + "EV": { [_: EmbeddedValue]: number }; + + /** + * Accept + */ + "EVPtr": { [_: EmbeddedValue]: number }; + + /** + * Accept + */ + "EVP": { [_: EmbeddedValuePtr]: number }; + + /** + * Accept + */ + "EVPPtr": { [_: EmbeddedValuePtr]: number }; + + /** + * Reject + */ + "EP": { [_: string]: number }; + + /** + * Accept + */ + "EPPtr": { [_: EmbeddedPointer]: number }; + + /** + * Accept + */ + "EPP": { [_: EmbeddedPointerPtr]: number }; + + /** + * Accept + */ + "EPPPtr": { [_: EmbeddedPointerPtr]: number }; + + /** + * Accept + */ + "ECI": { [_: EmbeddedCustomInterface]: number }; + + /** + * Accept + */ + "ECIPtr": { [_: EmbeddedCustomInterface]: number }; + + /** + * Accept + */ + "EOI": { [_: EmbeddedOriginalInterface]: number }; + + /** + * Accept + */ + "EOIPtr": { [_: EmbeddedOriginalInterface]: number }; + + /** + * Reject + */ + "WT": { [_: string]: number }; + + /** + * Reject + */ + "WA": { [_: string]: number }; + + /** + * Accept + */ + "ST": { [_: StringType]: number }; + + /** + * Accept + */ + "SA": { [_: StringAlias]: number }; + + /** + * Accept + */ + "IntT": { [_: `${number}`]: number }; + + /** + * Accept + */ + "IntA": { [_: `${number}`]: number }; + + /** + * Reject + */ + "VT": { [_: string]: number }; + + /** + * Reject + */ + "VTPtr": { [_: string]: number }; + + /** + * Reject + */ + "VPT": { [_: string]: number }; + + /** + * Reject + */ + "VPTPtr": { [_: string]: number }; + + /** + * Accept + */ + "VA": { [_: ValueAlias]: number }; + + /** + * Accept + */ + "VAPtr": { [_: ValueAlias]: number }; + + /** + * Accept, hide + */ + "VPA": { [_: string]: number }; + + /** + * Reject + */ + "VPAPtr": { [_: string]: number }; + + /** + * Reject + */ + "PT": { [_: string]: number }; + + /** + * Reject + */ + "PTPtr": { [_: string]: number }; + + /** + * Reject + */ + "PPT": { [_: string]: number }; + + /** + * Reject + */ + "PPTPtr": { [_: string]: number }; + + /** + * Reject + */ + "PA": { [_: string]: number }; + + /** + * Accept + */ + "PAPtr": { [_: PointerAlias]: number }; + + /** + * Accept, hide + */ + "PPA": { [_: string]: number }; + + /** + * Reject + */ + "PPAPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "IT": { [_: string]: number }; + + /** + * Reject + */ + "ITPtr": { [_: string]: number }; + + /** + * Reject + */ + "IPT": { [_: string]: number }; + + /** + * Reject + */ + "IPTPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "IA": { [_: string]: number }; + + /** + * Reject + */ + "IAPtr": { [_: string]: number }; + + /** + * Reject + */ + "IPA": { [_: string]: number }; + + /** + * Reject + */ + "IPAPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "TPR": { [_: string]: number }; + + /** + * Soft reject + */ + "TPRPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPS": { [_: string]: number }; + + /** + * Soft reject + */ + "TPSPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "TPT": { [_: string]: number }; + + /** + * Soft reject + */ + "TPTPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPU": { [_: string]: number }; + + /** + * Soft reject + */ + "TPUPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPV": { [_: string]: number }; + + /** + * Soft reject + */ + "TPVPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "TPW": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPWPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPX": { [_: string]: number }; + + /** + * Soft reject + */ + "TPXPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPY": { [_: string]: number }; + + /** + * Soft reject + */ + "TPYPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPZ": { [_: string]: number }; + + /** + * Soft reject + */ + "TPZPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "GAR": { [_: string]: number }; + + /** + * Soft reject + */ + "GARPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAS": { [_: string]: number }; + + /** + * Soft reject + */ + "GASPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "GAT": { [_: string]: number }; + + /** + * Soft reject + */ + "GATPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAU": { [_: string]: number }; + + /** + * Soft reject + */ + "GAUPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAV": { [_: string]: number }; + + /** + * Soft reject + */ + "GAVPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "GAW": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAWPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAX": { [_: string]: number }; + + /** + * Soft reject + */ + "GAXPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAY": { [_: string]: number }; + + /** + * Soft reject + */ + "GAYPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAZ": { [_: string]: number }; + + /** + * Soft reject + */ + "GAZPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GACi": { [_: `${number}`]: number }; + + /** + * Accept + */ + "GACV": { [_: ComparableCstrAlias]: number }; + + /** + * Reject + */ + "GACP": { [_: string]: number }; + + /** + * Reject + */ + "GACiPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GACVPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GACPPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GABi": { [_: `${number}`]: number }; + + /** + * Accept + */ + "GABs": { [_: BasicCstrAlias]: number }; + + /** + * Reject + */ + "GABiPtr": { [_: string]: number }; + + /** + * Reject + */ + "GABT": { [_: string]: number }; + + /** + * Reject + */ + "GABTPtr": { [_: string]: number }; + + /** + * Accept + */ + "GAGT": { [_: GoodTildeCstrAlias]: number }; + + /** + * Accept, hide + */ + "GAGTPtr": { [_: string]: number }; + + /** + * Accept + */ + "GANBV": { [_: NonBasicCstrAlias]: number }; + + /** + * Accept, hide + */ + "GANBP": { [_: string]: number }; + + /** + * Accept, hide + */ + "GANBVPtr": { [_: string]: number }; + + /** + * Reject + */ + "GANBPPtr": { [_: string]: number }; + + /** + * Accept + */ + "GAPlV1": { [_: PointableCstrAlias]: number }; + + /** + * Accept + */ + "GAPlV2": { [_: PointableCstrAlias]: number }; + + /** + * Reject + */ + "GAPlP1": { [_: string]: number }; + + /** + * Accept + */ + "GAPlP2": { [_: PointableCstrAlias]: number }; + + /** + * Accept, hide + */ + "GAPlVPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAPlPPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAMi": { [_: `${number}`]: number }; + + /** + * Accept + */ + "GAMS": { [_: MixedCstrAlias]: number }; + + /** + * Accept + */ + "GAMV": { [_: MixedCstrAlias]: number }; + + /** + * Reject + */ + "GAMSPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAMVPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAII": { [_: string]: number }; + + /** + * Accept + */ + "GAIV": { [_: InterfaceCstrAlias]: number }; + + /** + * Accept, hide + */ + "GAIP": { [_: string]: number }; + + /** + * Reject + */ + "GAIIPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAIVPtr": { [_: string]: number }; + + /** + * Reject + */ + "GAIPPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAPrV": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAPrP": { [_: string]: number }; + + /** + * Reject + */ + "GAPrVPtr": { [_: string]: number }; + + /** + * Reject + */ + "GAPrPPtr": { [_: string]: number }; + + /** Creates a new Maps instance. */ + constructor($$source: Partial> = {}) { + if (!("Bool" in $$source)) { + this["Bool"] = {}; + } + if (!("Int" in $$source)) { + this["Int"] = {}; + } + if (!("Uint" in $$source)) { + this["Uint"] = {}; + } + if (!("Float" in $$source)) { + this["Float"] = {}; + } + if (!("Complex" in $$source)) { + this["Complex"] = {}; + } + if (!("Byte" in $$source)) { + this["Byte"] = {}; + } + if (!("Rune" in $$source)) { + this["Rune"] = {}; + } + if (!("String" in $$source)) { + this["String"] = {}; + } + if (!("IntPtr" in $$source)) { + this["IntPtr"] = {}; + } + if (!("UintPtr" in $$source)) { + this["UintPtr"] = {}; + } + if (!("FloatPtr" in $$source)) { + this["FloatPtr"] = {}; + } + if (!("ComplexPtr" in $$source)) { + this["ComplexPtr"] = {}; + } + if (!("StringPtr" in $$source)) { + this["StringPtr"] = {}; + } + if (!("NTM" in $$source)) { + this["NTM"] = {}; + } + if (!("NTMPtr" in $$source)) { + this["NTMPtr"] = {}; + } + if (!("VTM" in $$source)) { + this["VTM"] = {}; + } + if (!("VTMPtr" in $$source)) { + this["VTMPtr"] = {}; + } + if (!("PTM" in $$source)) { + this["PTM"] = {}; + } + if (!("PTMPtr" in $$source)) { + this["PTMPtr"] = {}; + } + if (!("JTM" in $$source)) { + this["JTM"] = {}; + } + if (!("JTMPtr" in $$source)) { + this["JTMPtr"] = {}; + } + if (!("A" in $$source)) { + this["A"] = {}; + } + if (!("APtr" in $$source)) { + this["APtr"] = {}; + } + if (!("TM" in $$source)) { + this["TM"] = {}; + } + if (!("TMPtr" in $$source)) { + this["TMPtr"] = {}; + } + if (!("CI" in $$source)) { + this["CI"] = {}; + } + if (!("CIPtr" in $$source)) { + this["CIPtr"] = {}; + } + if (!("EI" in $$source)) { + this["EI"] = {}; + } + if (!("EIPtr" in $$source)) { + this["EIPtr"] = {}; + } + if (!("EV" in $$source)) { + this["EV"] = {}; + } + if (!("EVPtr" in $$source)) { + this["EVPtr"] = {}; + } + if (!("EVP" in $$source)) { + this["EVP"] = {}; + } + if (!("EVPPtr" in $$source)) { + this["EVPPtr"] = {}; + } + if (!("EP" in $$source)) { + this["EP"] = {}; + } + if (!("EPPtr" in $$source)) { + this["EPPtr"] = {}; + } + if (!("EPP" in $$source)) { + this["EPP"] = {}; + } + if (!("EPPPtr" in $$source)) { + this["EPPPtr"] = {}; + } + if (!("ECI" in $$source)) { + this["ECI"] = {}; + } + if (!("ECIPtr" in $$source)) { + this["ECIPtr"] = {}; + } + if (!("EOI" in $$source)) { + this["EOI"] = {}; + } + if (!("EOIPtr" in $$source)) { + this["EOIPtr"] = {}; + } + if (!("WT" in $$source)) { + this["WT"] = {}; + } + if (!("WA" in $$source)) { + this["WA"] = {}; + } + if (!("ST" in $$source)) { + this["ST"] = {}; + } + if (!("SA" in $$source)) { + this["SA"] = {}; + } + if (!("IntT" in $$source)) { + this["IntT"] = {}; + } + if (!("IntA" in $$source)) { + this["IntA"] = {}; + } + if (!("VT" in $$source)) { + this["VT"] = {}; + } + if (!("VTPtr" in $$source)) { + this["VTPtr"] = {}; + } + if (!("VPT" in $$source)) { + this["VPT"] = {}; + } + if (!("VPTPtr" in $$source)) { + this["VPTPtr"] = {}; + } + if (!("VA" in $$source)) { + this["VA"] = {}; + } + if (!("VAPtr" in $$source)) { + this["VAPtr"] = {}; + } + if (!("VPA" in $$source)) { + this["VPA"] = {}; + } + if (!("VPAPtr" in $$source)) { + this["VPAPtr"] = {}; + } + if (!("PT" in $$source)) { + this["PT"] = {}; + } + if (!("PTPtr" in $$source)) { + this["PTPtr"] = {}; + } + if (!("PPT" in $$source)) { + this["PPT"] = {}; + } + if (!("PPTPtr" in $$source)) { + this["PPTPtr"] = {}; + } + if (!("PA" in $$source)) { + this["PA"] = {}; + } + if (!("PAPtr" in $$source)) { + this["PAPtr"] = {}; + } + if (!("PPA" in $$source)) { + this["PPA"] = {}; + } + if (!("PPAPtr" in $$source)) { + this["PPAPtr"] = {}; + } + if (!("IT" in $$source)) { + this["IT"] = {}; + } + if (!("ITPtr" in $$source)) { + this["ITPtr"] = {}; + } + if (!("IPT" in $$source)) { + this["IPT"] = {}; + } + if (!("IPTPtr" in $$source)) { + this["IPTPtr"] = {}; + } + if (!("IA" in $$source)) { + this["IA"] = {}; + } + if (!("IAPtr" in $$source)) { + this["IAPtr"] = {}; + } + if (!("IPA" in $$source)) { + this["IPA"] = {}; + } + if (!("IPAPtr" in $$source)) { + this["IPAPtr"] = {}; + } + if (!("TPR" in $$source)) { + this["TPR"] = {}; + } + if (!("TPRPtr" in $$source)) { + this["TPRPtr"] = {}; + } + if (!("TPS" in $$source)) { + this["TPS"] = {}; + } + if (!("TPSPtr" in $$source)) { + this["TPSPtr"] = {}; + } + if (!("TPT" in $$source)) { + this["TPT"] = {}; + } + if (!("TPTPtr" in $$source)) { + this["TPTPtr"] = {}; + } + if (!("TPU" in $$source)) { + this["TPU"] = {}; + } + if (!("TPUPtr" in $$source)) { + this["TPUPtr"] = {}; + } + if (!("TPV" in $$source)) { + this["TPV"] = {}; + } + if (!("TPVPtr" in $$source)) { + this["TPVPtr"] = {}; + } + if (!("TPW" in $$source)) { + this["TPW"] = {}; + } + if (!("TPWPtr" in $$source)) { + this["TPWPtr"] = {}; + } + if (!("TPX" in $$source)) { + this["TPX"] = {}; + } + if (!("TPXPtr" in $$source)) { + this["TPXPtr"] = {}; + } + if (!("TPY" in $$source)) { + this["TPY"] = {}; + } + if (!("TPYPtr" in $$source)) { + this["TPYPtr"] = {}; + } + if (!("TPZ" in $$source)) { + this["TPZ"] = {}; + } + if (!("TPZPtr" in $$source)) { + this["TPZPtr"] = {}; + } + if (!("GAR" in $$source)) { + this["GAR"] = {}; + } + if (!("GARPtr" in $$source)) { + this["GARPtr"] = {}; + } + if (!("GAS" in $$source)) { + this["GAS"] = {}; + } + if (!("GASPtr" in $$source)) { + this["GASPtr"] = {}; + } + if (!("GAT" in $$source)) { + this["GAT"] = {}; + } + if (!("GATPtr" in $$source)) { + this["GATPtr"] = {}; + } + if (!("GAU" in $$source)) { + this["GAU"] = {}; + } + if (!("GAUPtr" in $$source)) { + this["GAUPtr"] = {}; + } + if (!("GAV" in $$source)) { + this["GAV"] = {}; + } + if (!("GAVPtr" in $$source)) { + this["GAVPtr"] = {}; + } + if (!("GAW" in $$source)) { + this["GAW"] = {}; + } + if (!("GAWPtr" in $$source)) { + this["GAWPtr"] = {}; + } + if (!("GAX" in $$source)) { + this["GAX"] = {}; + } + if (!("GAXPtr" in $$source)) { + this["GAXPtr"] = {}; + } + if (!("GAY" in $$source)) { + this["GAY"] = {}; + } + if (!("GAYPtr" in $$source)) { + this["GAYPtr"] = {}; + } + if (!("GAZ" in $$source)) { + this["GAZ"] = {}; + } + if (!("GAZPtr" in $$source)) { + this["GAZPtr"] = {}; + } + if (!("GACi" in $$source)) { + this["GACi"] = {}; + } + if (!("GACV" in $$source)) { + this["GACV"] = {}; + } + if (!("GACP" in $$source)) { + this["GACP"] = {}; + } + if (!("GACiPtr" in $$source)) { + this["GACiPtr"] = {}; + } + if (!("GACVPtr" in $$source)) { + this["GACVPtr"] = {}; + } + if (!("GACPPtr" in $$source)) { + this["GACPPtr"] = {}; + } + if (!("GABi" in $$source)) { + this["GABi"] = {}; + } + if (!("GABs" in $$source)) { + this["GABs"] = {}; + } + if (!("GABiPtr" in $$source)) { + this["GABiPtr"] = {}; + } + if (!("GABT" in $$source)) { + this["GABT"] = {}; + } + if (!("GABTPtr" in $$source)) { + this["GABTPtr"] = {}; + } + if (!("GAGT" in $$source)) { + this["GAGT"] = {}; + } + if (!("GAGTPtr" in $$source)) { + this["GAGTPtr"] = {}; + } + if (!("GANBV" in $$source)) { + this["GANBV"] = {}; + } + if (!("GANBP" in $$source)) { + this["GANBP"] = {}; + } + if (!("GANBVPtr" in $$source)) { + this["GANBVPtr"] = {}; + } + if (!("GANBPPtr" in $$source)) { + this["GANBPPtr"] = {}; + } + if (!("GAPlV1" in $$source)) { + this["GAPlV1"] = {}; + } + if (!("GAPlV2" in $$source)) { + this["GAPlV2"] = {}; + } + if (!("GAPlP1" in $$source)) { + this["GAPlP1"] = {}; + } + if (!("GAPlP2" in $$source)) { + this["GAPlP2"] = {}; + } + if (!("GAPlVPtr" in $$source)) { + this["GAPlVPtr"] = {}; + } + if (!("GAPlPPtr" in $$source)) { + this["GAPlPPtr"] = {}; + } + if (!("GAMi" in $$source)) { + this["GAMi"] = {}; + } + if (!("GAMS" in $$source)) { + this["GAMS"] = {}; + } + if (!("GAMV" in $$source)) { + this["GAMV"] = {}; + } + if (!("GAMSPtr" in $$source)) { + this["GAMSPtr"] = {}; + } + if (!("GAMVPtr" in $$source)) { + this["GAMVPtr"] = {}; + } + if (!("GAII" in $$source)) { + this["GAII"] = {}; + } + if (!("GAIV" in $$source)) { + this["GAIV"] = {}; + } + if (!("GAIP" in $$source)) { + this["GAIP"] = {}; + } + if (!("GAIIPtr" in $$source)) { + this["GAIIPtr"] = {}; + } + if (!("GAIVPtr" in $$source)) { + this["GAIVPtr"] = {}; + } + if (!("GAIPPtr" in $$source)) { + this["GAIPPtr"] = {}; + } + if (!("GAPrV" in $$source)) { + this["GAPrV"] = {}; + } + if (!("GAPrP" in $$source)) { + this["GAPrP"] = {}; + } + if (!("GAPrVPtr" in $$source)) { + this["GAPrVPtr"] = {}; + } + if (!("GAPrPPtr" in $$source)) { + this["GAPrPPtr"] = {}; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class Maps. + */ + static createFrom($$createParamR: (source: any) => R, $$createParamS: (source: any) => S, $$createParamT: (source: any) => T, $$createParamU: (source: any) => U, $$createParamV: (source: any) => V, $$createParamW: (source: any) => W, $$createParamX: (source: any) => X, $$createParamY: (source: any) => Y, $$createParamZ: (source: any) => Z): ($$source?: any) => Maps { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType3; + const $$createField4_0 = $$createType4; + const $$createField5_0 = $$createType5; + const $$createField6_0 = $$createType6; + const $$createField7_0 = $$createType7; + const $$createField8_0 = $$createType8; + const $$createField9_0 = $$createType9; + const $$createField10_0 = $$createType10; + const $$createField11_0 = $$createType11; + const $$createField12_0 = $$createType12; + const $$createField13_0 = $$createType13; + const $$createField14_0 = $$createType14; + const $$createField15_0 = $$createType15; + const $$createField16_0 = $$createType16; + const $$createField17_0 = $$createType17; + const $$createField18_0 = $$createType18; + const $$createField19_0 = $$createType19; + const $$createField20_0 = $$createType20; + const $$createField21_0 = $$createType21; + const $$createField22_0 = $$createType22; + const $$createField23_0 = $$createType23; + const $$createField24_0 = $$createType24; + const $$createField25_0 = $$createType25; + const $$createField26_0 = $$createType26; + const $$createField27_0 = $$createType27; + const $$createField28_0 = $$createType28; + const $$createField29_0 = $$createType29; + const $$createField30_0 = $$createType30; + const $$createField31_0 = $$createType31; + const $$createField32_0 = $$createType32; + const $$createField33_0 = $$createType33; + const $$createField34_0 = $$createType34; + const $$createField35_0 = $$createType35; + const $$createField36_0 = $$createType36; + const $$createField37_0 = $$createType37; + const $$createField38_0 = $$createType38; + const $$createField39_0 = $$createType39; + const $$createField40_0 = $$createType40; + const $$createField41_0 = $$createType41; + const $$createField42_0 = $$createType0; + const $$createField43_0 = $$createType42; + const $$createField44_0 = $$createType7; + const $$createField45_0 = $$createType43; + const $$createField46_0 = $$createType1; + const $$createField47_0 = $$createType44; + const $$createField48_0 = $$createType45; + const $$createField49_0 = $$createType46; + const $$createField50_0 = $$createType47; + const $$createField51_0 = $$createType15; + const $$createField52_0 = $$createType16; + const $$createField53_0 = $$createType16; + const $$createField54_0 = $$createType48; + const $$createField55_0 = $$createType49; + const $$createField56_0 = $$createType50; + const $$createField57_0 = $$createType51; + const $$createField58_0 = $$createType52; + const $$createField59_0 = $$createType17; + const $$createField60_0 = $$createType18; + const $$createField61_0 = $$createType18; + const $$createField62_0 = $$createType53; + const $$createField63_0 = $$createType54; + const $$createField64_0 = $$createType55; + const $$createField65_0 = $$createType56; + const $$createField66_0 = $$createType57; + const $$createField67_0 = $$createType23; + const $$createField68_0 = $$createType24; + const $$createField69_0 = $$createType24; + const $$createField70_0 = $$createType58; + const $$createField71_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField72_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField73_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField74_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField75_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField76_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField77_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField78_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField79_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField80_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField81_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField82_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField83_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField84_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField85_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField86_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField87_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField88_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField89_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField90_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField91_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField92_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField93_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField94_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField95_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField96_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField97_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField98_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField99_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField100_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField101_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField102_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField103_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField104_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField105_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField106_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField107_0 = $$createType1; + const $$createField108_0 = $$createType15; + const $$createField109_0 = $$createType17; + const $$createField110_0 = $$createType8; + const $$createField111_0 = $$createType16; + const $$createField112_0 = $$createType18; + const $$createField113_0 = $$createType1; + const $$createField114_0 = $$createType7; + const $$createField115_0 = $$createType8; + const $$createField116_0 = $$createType77; + const $$createField117_0 = $$createType78; + const $$createField118_0 = $$createType15; + const $$createField119_0 = $$createType16; + const $$createField120_0 = $$createType15; + const $$createField121_0 = $$createType18; + const $$createField122_0 = $$createType16; + const $$createField123_0 = $$createType53; + const $$createField124_0 = $$createType15; + const $$createField125_0 = $$createType16; + const $$createField126_0 = $$createType17; + const $$createField127_0 = $$createType18; + const $$createField128_0 = $$createType16; + const $$createField129_0 = $$createType18; + const $$createField130_0 = $$createType2; + const $$createField131_0 = $$createType42; + const $$createField132_0 = $$createType15; + const $$createField133_0 = $$createType79; + const $$createField134_0 = $$createType16; + const $$createField135_0 = $$createType23; + const $$createField136_0 = $$createType15; + const $$createField137_0 = $$createType18; + const $$createField138_0 = $$createType24; + const $$createField139_0 = $$createType16; + const $$createField140_0 = $$createType53; + const $$createField141_0 = $$createType16; + const $$createField142_0 = $$createType18; + const $$createField143_0 = $$createType48; + const $$createField144_0 = $$createType53; + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Bool" in $$parsedSource) { + $$parsedSource["Bool"] = $$createField0_0($$parsedSource["Bool"]); + } + if ("Int" in $$parsedSource) { + $$parsedSource["Int"] = $$createField1_0($$parsedSource["Int"]); + } + if ("Uint" in $$parsedSource) { + $$parsedSource["Uint"] = $$createField2_0($$parsedSource["Uint"]); + } + if ("Float" in $$parsedSource) { + $$parsedSource["Float"] = $$createField3_0($$parsedSource["Float"]); + } + if ("Complex" in $$parsedSource) { + $$parsedSource["Complex"] = $$createField4_0($$parsedSource["Complex"]); + } + if ("Byte" in $$parsedSource) { + $$parsedSource["Byte"] = $$createField5_0($$parsedSource["Byte"]); + } + if ("Rune" in $$parsedSource) { + $$parsedSource["Rune"] = $$createField6_0($$parsedSource["Rune"]); + } + if ("String" in $$parsedSource) { + $$parsedSource["String"] = $$createField7_0($$parsedSource["String"]); + } + if ("IntPtr" in $$parsedSource) { + $$parsedSource["IntPtr"] = $$createField8_0($$parsedSource["IntPtr"]); + } + if ("UintPtr" in $$parsedSource) { + $$parsedSource["UintPtr"] = $$createField9_0($$parsedSource["UintPtr"]); + } + if ("FloatPtr" in $$parsedSource) { + $$parsedSource["FloatPtr"] = $$createField10_0($$parsedSource["FloatPtr"]); + } + if ("ComplexPtr" in $$parsedSource) { + $$parsedSource["ComplexPtr"] = $$createField11_0($$parsedSource["ComplexPtr"]); + } + if ("StringPtr" in $$parsedSource) { + $$parsedSource["StringPtr"] = $$createField12_0($$parsedSource["StringPtr"]); + } + if ("NTM" in $$parsedSource) { + $$parsedSource["NTM"] = $$createField13_0($$parsedSource["NTM"]); + } + if ("NTMPtr" in $$parsedSource) { + $$parsedSource["NTMPtr"] = $$createField14_0($$parsedSource["NTMPtr"]); + } + if ("VTM" in $$parsedSource) { + $$parsedSource["VTM"] = $$createField15_0($$parsedSource["VTM"]); + } + if ("VTMPtr" in $$parsedSource) { + $$parsedSource["VTMPtr"] = $$createField16_0($$parsedSource["VTMPtr"]); + } + if ("PTM" in $$parsedSource) { + $$parsedSource["PTM"] = $$createField17_0($$parsedSource["PTM"]); + } + if ("PTMPtr" in $$parsedSource) { + $$parsedSource["PTMPtr"] = $$createField18_0($$parsedSource["PTMPtr"]); + } + if ("JTM" in $$parsedSource) { + $$parsedSource["JTM"] = $$createField19_0($$parsedSource["JTM"]); + } + if ("JTMPtr" in $$parsedSource) { + $$parsedSource["JTMPtr"] = $$createField20_0($$parsedSource["JTMPtr"]); + } + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField21_0($$parsedSource["A"]); + } + if ("APtr" in $$parsedSource) { + $$parsedSource["APtr"] = $$createField22_0($$parsedSource["APtr"]); + } + if ("TM" in $$parsedSource) { + $$parsedSource["TM"] = $$createField23_0($$parsedSource["TM"]); + } + if ("TMPtr" in $$parsedSource) { + $$parsedSource["TMPtr"] = $$createField24_0($$parsedSource["TMPtr"]); + } + if ("CI" in $$parsedSource) { + $$parsedSource["CI"] = $$createField25_0($$parsedSource["CI"]); + } + if ("CIPtr" in $$parsedSource) { + $$parsedSource["CIPtr"] = $$createField26_0($$parsedSource["CIPtr"]); + } + if ("EI" in $$parsedSource) { + $$parsedSource["EI"] = $$createField27_0($$parsedSource["EI"]); + } + if ("EIPtr" in $$parsedSource) { + $$parsedSource["EIPtr"] = $$createField28_0($$parsedSource["EIPtr"]); + } + if ("EV" in $$parsedSource) { + $$parsedSource["EV"] = $$createField29_0($$parsedSource["EV"]); + } + if ("EVPtr" in $$parsedSource) { + $$parsedSource["EVPtr"] = $$createField30_0($$parsedSource["EVPtr"]); + } + if ("EVP" in $$parsedSource) { + $$parsedSource["EVP"] = $$createField31_0($$parsedSource["EVP"]); + } + if ("EVPPtr" in $$parsedSource) { + $$parsedSource["EVPPtr"] = $$createField32_0($$parsedSource["EVPPtr"]); + } + if ("EP" in $$parsedSource) { + $$parsedSource["EP"] = $$createField33_0($$parsedSource["EP"]); + } + if ("EPPtr" in $$parsedSource) { + $$parsedSource["EPPtr"] = $$createField34_0($$parsedSource["EPPtr"]); + } + if ("EPP" in $$parsedSource) { + $$parsedSource["EPP"] = $$createField35_0($$parsedSource["EPP"]); + } + if ("EPPPtr" in $$parsedSource) { + $$parsedSource["EPPPtr"] = $$createField36_0($$parsedSource["EPPPtr"]); + } + if ("ECI" in $$parsedSource) { + $$parsedSource["ECI"] = $$createField37_0($$parsedSource["ECI"]); + } + if ("ECIPtr" in $$parsedSource) { + $$parsedSource["ECIPtr"] = $$createField38_0($$parsedSource["ECIPtr"]); + } + if ("EOI" in $$parsedSource) { + $$parsedSource["EOI"] = $$createField39_0($$parsedSource["EOI"]); + } + if ("EOIPtr" in $$parsedSource) { + $$parsedSource["EOIPtr"] = $$createField40_0($$parsedSource["EOIPtr"]); + } + if ("WT" in $$parsedSource) { + $$parsedSource["WT"] = $$createField41_0($$parsedSource["WT"]); + } + if ("WA" in $$parsedSource) { + $$parsedSource["WA"] = $$createField42_0($$parsedSource["WA"]); + } + if ("ST" in $$parsedSource) { + $$parsedSource["ST"] = $$createField43_0($$parsedSource["ST"]); + } + if ("SA" in $$parsedSource) { + $$parsedSource["SA"] = $$createField44_0($$parsedSource["SA"]); + } + if ("IntT" in $$parsedSource) { + $$parsedSource["IntT"] = $$createField45_0($$parsedSource["IntT"]); + } + if ("IntA" in $$parsedSource) { + $$parsedSource["IntA"] = $$createField46_0($$parsedSource["IntA"]); + } + if ("VT" in $$parsedSource) { + $$parsedSource["VT"] = $$createField47_0($$parsedSource["VT"]); + } + if ("VTPtr" in $$parsedSource) { + $$parsedSource["VTPtr"] = $$createField48_0($$parsedSource["VTPtr"]); + } + if ("VPT" in $$parsedSource) { + $$parsedSource["VPT"] = $$createField49_0($$parsedSource["VPT"]); + } + if ("VPTPtr" in $$parsedSource) { + $$parsedSource["VPTPtr"] = $$createField50_0($$parsedSource["VPTPtr"]); + } + if ("VA" in $$parsedSource) { + $$parsedSource["VA"] = $$createField51_0($$parsedSource["VA"]); + } + if ("VAPtr" in $$parsedSource) { + $$parsedSource["VAPtr"] = $$createField52_0($$parsedSource["VAPtr"]); + } + if ("VPA" in $$parsedSource) { + $$parsedSource["VPA"] = $$createField53_0($$parsedSource["VPA"]); + } + if ("VPAPtr" in $$parsedSource) { + $$parsedSource["VPAPtr"] = $$createField54_0($$parsedSource["VPAPtr"]); + } + if ("PT" in $$parsedSource) { + $$parsedSource["PT"] = $$createField55_0($$parsedSource["PT"]); + } + if ("PTPtr" in $$parsedSource) { + $$parsedSource["PTPtr"] = $$createField56_0($$parsedSource["PTPtr"]); + } + if ("PPT" in $$parsedSource) { + $$parsedSource["PPT"] = $$createField57_0($$parsedSource["PPT"]); + } + if ("PPTPtr" in $$parsedSource) { + $$parsedSource["PPTPtr"] = $$createField58_0($$parsedSource["PPTPtr"]); + } + if ("PA" in $$parsedSource) { + $$parsedSource["PA"] = $$createField59_0($$parsedSource["PA"]); + } + if ("PAPtr" in $$parsedSource) { + $$parsedSource["PAPtr"] = $$createField60_0($$parsedSource["PAPtr"]); + } + if ("PPA" in $$parsedSource) { + $$parsedSource["PPA"] = $$createField61_0($$parsedSource["PPA"]); + } + if ("PPAPtr" in $$parsedSource) { + $$parsedSource["PPAPtr"] = $$createField62_0($$parsedSource["PPAPtr"]); + } + if ("IT" in $$parsedSource) { + $$parsedSource["IT"] = $$createField63_0($$parsedSource["IT"]); + } + if ("ITPtr" in $$parsedSource) { + $$parsedSource["ITPtr"] = $$createField64_0($$parsedSource["ITPtr"]); + } + if ("IPT" in $$parsedSource) { + $$parsedSource["IPT"] = $$createField65_0($$parsedSource["IPT"]); + } + if ("IPTPtr" in $$parsedSource) { + $$parsedSource["IPTPtr"] = $$createField66_0($$parsedSource["IPTPtr"]); + } + if ("IA" in $$parsedSource) { + $$parsedSource["IA"] = $$createField67_0($$parsedSource["IA"]); + } + if ("IAPtr" in $$parsedSource) { + $$parsedSource["IAPtr"] = $$createField68_0($$parsedSource["IAPtr"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField69_0($$parsedSource["IPA"]); + } + if ("IPAPtr" in $$parsedSource) { + $$parsedSource["IPAPtr"] = $$createField70_0($$parsedSource["IPAPtr"]); + } + if ("TPR" in $$parsedSource) { + $$parsedSource["TPR"] = $$createField71_0($$parsedSource["TPR"]); + } + if ("TPRPtr" in $$parsedSource) { + $$parsedSource["TPRPtr"] = $$createField72_0($$parsedSource["TPRPtr"]); + } + if ("TPS" in $$parsedSource) { + $$parsedSource["TPS"] = $$createField73_0($$parsedSource["TPS"]); + } + if ("TPSPtr" in $$parsedSource) { + $$parsedSource["TPSPtr"] = $$createField74_0($$parsedSource["TPSPtr"]); + } + if ("TPT" in $$parsedSource) { + $$parsedSource["TPT"] = $$createField75_0($$parsedSource["TPT"]); + } + if ("TPTPtr" in $$parsedSource) { + $$parsedSource["TPTPtr"] = $$createField76_0($$parsedSource["TPTPtr"]); + } + if ("TPU" in $$parsedSource) { + $$parsedSource["TPU"] = $$createField77_0($$parsedSource["TPU"]); + } + if ("TPUPtr" in $$parsedSource) { + $$parsedSource["TPUPtr"] = $$createField78_0($$parsedSource["TPUPtr"]); + } + if ("TPV" in $$parsedSource) { + $$parsedSource["TPV"] = $$createField79_0($$parsedSource["TPV"]); + } + if ("TPVPtr" in $$parsedSource) { + $$parsedSource["TPVPtr"] = $$createField80_0($$parsedSource["TPVPtr"]); + } + if ("TPW" in $$parsedSource) { + $$parsedSource["TPW"] = $$createField81_0($$parsedSource["TPW"]); + } + if ("TPWPtr" in $$parsedSource) { + $$parsedSource["TPWPtr"] = $$createField82_0($$parsedSource["TPWPtr"]); + } + if ("TPX" in $$parsedSource) { + $$parsedSource["TPX"] = $$createField83_0($$parsedSource["TPX"]); + } + if ("TPXPtr" in $$parsedSource) { + $$parsedSource["TPXPtr"] = $$createField84_0($$parsedSource["TPXPtr"]); + } + if ("TPY" in $$parsedSource) { + $$parsedSource["TPY"] = $$createField85_0($$parsedSource["TPY"]); + } + if ("TPYPtr" in $$parsedSource) { + $$parsedSource["TPYPtr"] = $$createField86_0($$parsedSource["TPYPtr"]); + } + if ("TPZ" in $$parsedSource) { + $$parsedSource["TPZ"] = $$createField87_0($$parsedSource["TPZ"]); + } + if ("TPZPtr" in $$parsedSource) { + $$parsedSource["TPZPtr"] = $$createField88_0($$parsedSource["TPZPtr"]); + } + if ("GAR" in $$parsedSource) { + $$parsedSource["GAR"] = $$createField89_0($$parsedSource["GAR"]); + } + if ("GARPtr" in $$parsedSource) { + $$parsedSource["GARPtr"] = $$createField90_0($$parsedSource["GARPtr"]); + } + if ("GAS" in $$parsedSource) { + $$parsedSource["GAS"] = $$createField91_0($$parsedSource["GAS"]); + } + if ("GASPtr" in $$parsedSource) { + $$parsedSource["GASPtr"] = $$createField92_0($$parsedSource["GASPtr"]); + } + if ("GAT" in $$parsedSource) { + $$parsedSource["GAT"] = $$createField93_0($$parsedSource["GAT"]); + } + if ("GATPtr" in $$parsedSource) { + $$parsedSource["GATPtr"] = $$createField94_0($$parsedSource["GATPtr"]); + } + if ("GAU" in $$parsedSource) { + $$parsedSource["GAU"] = $$createField95_0($$parsedSource["GAU"]); + } + if ("GAUPtr" in $$parsedSource) { + $$parsedSource["GAUPtr"] = $$createField96_0($$parsedSource["GAUPtr"]); + } + if ("GAV" in $$parsedSource) { + $$parsedSource["GAV"] = $$createField97_0($$parsedSource["GAV"]); + } + if ("GAVPtr" in $$parsedSource) { + $$parsedSource["GAVPtr"] = $$createField98_0($$parsedSource["GAVPtr"]); + } + if ("GAW" in $$parsedSource) { + $$parsedSource["GAW"] = $$createField99_0($$parsedSource["GAW"]); + } + if ("GAWPtr" in $$parsedSource) { + $$parsedSource["GAWPtr"] = $$createField100_0($$parsedSource["GAWPtr"]); + } + if ("GAX" in $$parsedSource) { + $$parsedSource["GAX"] = $$createField101_0($$parsedSource["GAX"]); + } + if ("GAXPtr" in $$parsedSource) { + $$parsedSource["GAXPtr"] = $$createField102_0($$parsedSource["GAXPtr"]); + } + if ("GAY" in $$parsedSource) { + $$parsedSource["GAY"] = $$createField103_0($$parsedSource["GAY"]); + } + if ("GAYPtr" in $$parsedSource) { + $$parsedSource["GAYPtr"] = $$createField104_0($$parsedSource["GAYPtr"]); + } + if ("GAZ" in $$parsedSource) { + $$parsedSource["GAZ"] = $$createField105_0($$parsedSource["GAZ"]); + } + if ("GAZPtr" in $$parsedSource) { + $$parsedSource["GAZPtr"] = $$createField106_0($$parsedSource["GAZPtr"]); + } + if ("GACi" in $$parsedSource) { + $$parsedSource["GACi"] = $$createField107_0($$parsedSource["GACi"]); + } + if ("GACV" in $$parsedSource) { + $$parsedSource["GACV"] = $$createField108_0($$parsedSource["GACV"]); + } + if ("GACP" in $$parsedSource) { + $$parsedSource["GACP"] = $$createField109_0($$parsedSource["GACP"]); + } + if ("GACiPtr" in $$parsedSource) { + $$parsedSource["GACiPtr"] = $$createField110_0($$parsedSource["GACiPtr"]); + } + if ("GACVPtr" in $$parsedSource) { + $$parsedSource["GACVPtr"] = $$createField111_0($$parsedSource["GACVPtr"]); + } + if ("GACPPtr" in $$parsedSource) { + $$parsedSource["GACPPtr"] = $$createField112_0($$parsedSource["GACPPtr"]); + } + if ("GABi" in $$parsedSource) { + $$parsedSource["GABi"] = $$createField113_0($$parsedSource["GABi"]); + } + if ("GABs" in $$parsedSource) { + $$parsedSource["GABs"] = $$createField114_0($$parsedSource["GABs"]); + } + if ("GABiPtr" in $$parsedSource) { + $$parsedSource["GABiPtr"] = $$createField115_0($$parsedSource["GABiPtr"]); + } + if ("GABT" in $$parsedSource) { + $$parsedSource["GABT"] = $$createField116_0($$parsedSource["GABT"]); + } + if ("GABTPtr" in $$parsedSource) { + $$parsedSource["GABTPtr"] = $$createField117_0($$parsedSource["GABTPtr"]); + } + if ("GAGT" in $$parsedSource) { + $$parsedSource["GAGT"] = $$createField118_0($$parsedSource["GAGT"]); + } + if ("GAGTPtr" in $$parsedSource) { + $$parsedSource["GAGTPtr"] = $$createField119_0($$parsedSource["GAGTPtr"]); + } + if ("GANBV" in $$parsedSource) { + $$parsedSource["GANBV"] = $$createField120_0($$parsedSource["GANBV"]); + } + if ("GANBP" in $$parsedSource) { + $$parsedSource["GANBP"] = $$createField121_0($$parsedSource["GANBP"]); + } + if ("GANBVPtr" in $$parsedSource) { + $$parsedSource["GANBVPtr"] = $$createField122_0($$parsedSource["GANBVPtr"]); + } + if ("GANBPPtr" in $$parsedSource) { + $$parsedSource["GANBPPtr"] = $$createField123_0($$parsedSource["GANBPPtr"]); + } + if ("GAPlV1" in $$parsedSource) { + $$parsedSource["GAPlV1"] = $$createField124_0($$parsedSource["GAPlV1"]); + } + if ("GAPlV2" in $$parsedSource) { + $$parsedSource["GAPlV2"] = $$createField125_0($$parsedSource["GAPlV2"]); + } + if ("GAPlP1" in $$parsedSource) { + $$parsedSource["GAPlP1"] = $$createField126_0($$parsedSource["GAPlP1"]); + } + if ("GAPlP2" in $$parsedSource) { + $$parsedSource["GAPlP2"] = $$createField127_0($$parsedSource["GAPlP2"]); + } + if ("GAPlVPtr" in $$parsedSource) { + $$parsedSource["GAPlVPtr"] = $$createField128_0($$parsedSource["GAPlVPtr"]); + } + if ("GAPlPPtr" in $$parsedSource) { + $$parsedSource["GAPlPPtr"] = $$createField129_0($$parsedSource["GAPlPPtr"]); + } + if ("GAMi" in $$parsedSource) { + $$parsedSource["GAMi"] = $$createField130_0($$parsedSource["GAMi"]); + } + if ("GAMS" in $$parsedSource) { + $$parsedSource["GAMS"] = $$createField131_0($$parsedSource["GAMS"]); + } + if ("GAMV" in $$parsedSource) { + $$parsedSource["GAMV"] = $$createField132_0($$parsedSource["GAMV"]); + } + if ("GAMSPtr" in $$parsedSource) { + $$parsedSource["GAMSPtr"] = $$createField133_0($$parsedSource["GAMSPtr"]); + } + if ("GAMVPtr" in $$parsedSource) { + $$parsedSource["GAMVPtr"] = $$createField134_0($$parsedSource["GAMVPtr"]); + } + if ("GAII" in $$parsedSource) { + $$parsedSource["GAII"] = $$createField135_0($$parsedSource["GAII"]); + } + if ("GAIV" in $$parsedSource) { + $$parsedSource["GAIV"] = $$createField136_0($$parsedSource["GAIV"]); + } + if ("GAIP" in $$parsedSource) { + $$parsedSource["GAIP"] = $$createField137_0($$parsedSource["GAIP"]); + } + if ("GAIIPtr" in $$parsedSource) { + $$parsedSource["GAIIPtr"] = $$createField138_0($$parsedSource["GAIIPtr"]); + } + if ("GAIVPtr" in $$parsedSource) { + $$parsedSource["GAIVPtr"] = $$createField139_0($$parsedSource["GAIVPtr"]); + } + if ("GAIPPtr" in $$parsedSource) { + $$parsedSource["GAIPPtr"] = $$createField140_0($$parsedSource["GAIPPtr"]); + } + if ("GAPrV" in $$parsedSource) { + $$parsedSource["GAPrV"] = $$createField141_0($$parsedSource["GAPrV"]); + } + if ("GAPrP" in $$parsedSource) { + $$parsedSource["GAPrP"] = $$createField142_0($$parsedSource["GAPrP"]); + } + if ("GAPrVPtr" in $$parsedSource) { + $$parsedSource["GAPrVPtr"] = $$createField143_0($$parsedSource["GAPrVPtr"]); + } + if ("GAPrPPtr" in $$parsedSource) { + $$parsedSource["GAPrPPtr"] = $$createField144_0($$parsedSource["GAPrPPtr"]); + } + return new Maps($$parsedSource as Partial>); + }; + } +} + +export type MixedCstrAlias = X; + +export type NonBasicCstrAlias = V; + +export type PointableCstrAlias = W; + +export type PointerAlias = PointerTextMarshaler; + +export type PointerTextMarshaler = string; + +export type StringAlias = string; + +export type StringType = string; + +export type ValueAlias = ValueTextMarshaler; + +export type ValueTextMarshaler = string; + +// Private type creation functions +const $$createType0 = $Create.Map($Create.Any, $Create.Any); +const $$createType1 = $Create.Map($Create.Any, $Create.Any); +const $$createType2 = $Create.Map($Create.Any, $Create.Any); +const $$createType3 = $Create.Map($Create.Any, $Create.Any); +const $$createType4 = $Create.Map($Create.Any, $Create.Any); +const $$createType5 = $Create.Map($Create.Any, $Create.Any); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Map($Create.Any, $Create.Any); +const $$createType8 = $Create.Map($Create.Any, $Create.Any); +const $$createType9 = $Create.Map($Create.Any, $Create.Any); +const $$createType10 = $Create.Map($Create.Any, $Create.Any); +const $$createType11 = $Create.Map($Create.Any, $Create.Any); +const $$createType12 = $Create.Map($Create.Any, $Create.Any); +const $$createType13 = $Create.Map($Create.Any, $Create.Any); +const $$createType14 = $Create.Map($Create.Any, $Create.Any); +const $$createType15 = $Create.Map($Create.Any, $Create.Any); +const $$createType16 = $Create.Map($Create.Any, $Create.Any); +const $$createType17 = $Create.Map($Create.Any, $Create.Any); +const $$createType18 = $Create.Map($Create.Any, $Create.Any); +const $$createType19 = $Create.Map($Create.Any, $Create.Any); +const $$createType20 = $Create.Map($Create.Any, $Create.Any); +const $$createType21 = $Create.Map($Create.Any, $Create.Any); +const $$createType22 = $Create.Map($Create.Any, $Create.Any); +const $$createType23 = $Create.Map($Create.Any, $Create.Any); +const $$createType24 = $Create.Map($Create.Any, $Create.Any); +const $$createType25 = $Create.Map($Create.Any, $Create.Any); +const $$createType26 = $Create.Map($Create.Any, $Create.Any); +const $$createType27 = $Create.Map($Create.Any, $Create.Any); +const $$createType28 = $Create.Map($Create.Any, $Create.Any); +const $$createType29 = $Create.Map($Create.Any, $Create.Any); +const $$createType30 = $Create.Map($Create.Any, $Create.Any); +const $$createType31 = $Create.Map($Create.Any, $Create.Any); +const $$createType32 = $Create.Map($Create.Any, $Create.Any); +const $$createType33 = $Create.Map($Create.Any, $Create.Any); +const $$createType34 = $Create.Map($Create.Any, $Create.Any); +const $$createType35 = $Create.Map($Create.Any, $Create.Any); +const $$createType36 = $Create.Map($Create.Any, $Create.Any); +const $$createType37 = $Create.Map($Create.Any, $Create.Any); +const $$createType38 = $Create.Map($Create.Any, $Create.Any); +const $$createType39 = $Create.Map($Create.Any, $Create.Any); +const $$createType40 = $Create.Map($Create.Any, $Create.Any); +const $$createType41 = $Create.Map($Create.Any, $Create.Any); +const $$createType42 = $Create.Map($Create.Any, $Create.Any); +const $$createType43 = $Create.Map($Create.Any, $Create.Any); +const $$createType44 = $Create.Map($Create.Any, $Create.Any); +const $$createType45 = $Create.Map($Create.Any, $Create.Any); +const $$createType46 = $Create.Map($Create.Any, $Create.Any); +const $$createType47 = $Create.Map($Create.Any, $Create.Any); +const $$createType48 = $Create.Map($Create.Any, $Create.Any); +const $$createType49 = $Create.Map($Create.Any, $Create.Any); +const $$createType50 = $Create.Map($Create.Any, $Create.Any); +const $$createType51 = $Create.Map($Create.Any, $Create.Any); +const $$createType52 = $Create.Map($Create.Any, $Create.Any); +const $$createType53 = $Create.Map($Create.Any, $Create.Any); +const $$createType54 = $Create.Map($Create.Any, $Create.Any); +const $$createType55 = $Create.Map($Create.Any, $Create.Any); +const $$createType56 = $Create.Map($Create.Any, $Create.Any); +const $$createType57 = $Create.Map($Create.Any, $Create.Any); +const $$createType58 = $Create.Map($Create.Any, $Create.Any); +const $$createType59 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType60 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType61 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType62 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType63 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType64 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType65 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType66 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType67 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType68 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType69 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType70 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType71 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType72 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType73 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType74 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType75 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType76 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType77 = $Create.Map($Create.Any, $Create.Any); +const $$createType78 = $Create.Map($Create.Any, $Create.Any); +const $$createType79 = $Create.Map($Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts new file mode 100644 index 000000000..bd5e88c7b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>> { + return $Call.ByID(4021345184).then(($result: any) => { + return $$createType0($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Maps.createFrom($Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts new file mode 100644 index 000000000..bbe49e32f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts @@ -0,0 +1,36 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Data, + ImplicitNonMarshaler, + NonMarshaler +} from "./models.js"; + +export type { + AliasJsonMarshaler, + AliasMarshaler, + AliasNonMarshaler, + AliasTextMarshaler, + ImplicitJsonButText, + ImplicitJsonMarshaler, + ImplicitMarshaler, + ImplicitNonJson, + ImplicitNonText, + ImplicitTextButJson, + ImplicitTextMarshaler, + PointerJsonMarshaler, + PointerMarshaler, + PointerTextMarshaler, + UnderlyingJsonMarshaler, + UnderlyingMarshaler, + UnderlyingTextMarshaler, + ValueJsonMarshaler, + ValueMarshaler, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts new file mode 100644 index 000000000..d8db23862 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts @@ -0,0 +1,545 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + */ +export type AliasJsonMarshaler = any; + +/** + * any + */ +export type AliasMarshaler = any; + +/** + * struct{} + */ +export interface AliasNonMarshaler { +} + +/** + * string + */ +export type AliasTextMarshaler = string; + +export class Data { + "NM": NonMarshaler; + + /** + * NonMarshaler | null + */ + "NMPtr": NonMarshaler | null; + "VJM": ValueJsonMarshaler; + + /** + * ValueJsonMarshaler | null + */ + "VJMPtr": ValueJsonMarshaler | null; + "PJM": PointerJsonMarshaler; + + /** + * PointerJsonMarshaler | null + */ + "PJMPtr": PointerJsonMarshaler | null; + "VTM": ValueTextMarshaler; + + /** + * ValueTextMarshaler | null + */ + "VTMPtr": ValueTextMarshaler | null; + "PTM": PointerTextMarshaler; + + /** + * PointerTextMarshaler | null + */ + "PTMPtr": PointerTextMarshaler | null; + "VM": ValueMarshaler; + + /** + * ValueMarshaler | null + */ + "VMPtr": ValueMarshaler | null; + "PM": PointerMarshaler; + + /** + * PointerMarshaler | null + */ + "PMPtr": PointerMarshaler | null; + "UJM": UnderlyingJsonMarshaler; + + /** + * UnderlyingJsonMarshaler | null + */ + "UJMPtr": UnderlyingJsonMarshaler | null; + "UTM": UnderlyingTextMarshaler; + + /** + * UnderlyingTextMarshaler | null + */ + "UTMPtr": UnderlyingTextMarshaler | null; + "UM": UnderlyingMarshaler; + + /** + * UnderlyingMarshaler | null + */ + "UMPtr": UnderlyingMarshaler | null; + + /** + * any + */ + "JM": any; + + /** + * any | null + */ + "JMPtr": any | null; + + /** + * string + */ + "TM": string; + + /** + * string | null + */ + "TMPtr": string | null; + + /** + * any + */ + "CJM": any; + + /** + * any | null + */ + "CJMPtr": any | null; + + /** + * string + */ + "CTM": string; + + /** + * string | null + */ + "CTMPtr": string | null; + + /** + * any + */ + "CM": any; + + /** + * any | null + */ + "CMPtr": any | null; + "ANM": AliasNonMarshaler; + + /** + * AliasNonMarshaler | null + */ + "ANMPtr": AliasNonMarshaler | null; + "AJM": AliasJsonMarshaler; + + /** + * AliasJsonMarshaler | null + */ + "AJMPtr": AliasJsonMarshaler | null; + "ATM": AliasTextMarshaler; + + /** + * AliasTextMarshaler | null + */ + "ATMPtr": AliasTextMarshaler | null; + "AM": AliasMarshaler; + + /** + * AliasMarshaler | null + */ + "AMPtr": AliasMarshaler | null; + "ImJM": ImplicitJsonMarshaler; + + /** + * ImplicitJsonMarshaler | null + */ + "ImJMPtr": ImplicitJsonMarshaler | null; + "ImTM": ImplicitTextMarshaler; + + /** + * ImplicitTextMarshaler | null + */ + "ImTMPtr": ImplicitTextMarshaler | null; + "ImM": ImplicitMarshaler; + + /** + * ImplicitMarshaler | null + */ + "ImMPtr": ImplicitMarshaler | null; + "ImNJ": ImplicitNonJson; + + /** + * ImplicitNonJson | null + */ + "ImNJPtr": ImplicitNonJson | null; + "ImNT": ImplicitNonText; + + /** + * ImplicitNonText | null + */ + "ImNTPtr": ImplicitNonText | null; + "ImNM": ImplicitNonMarshaler; + + /** + * ImplicitNonMarshaler | null + */ + "ImNMPtr": ImplicitNonMarshaler | null; + "ImJbT": ImplicitJsonButText; + + /** + * ImplicitJsonButText | null + */ + "ImJbTPtr": ImplicitJsonButText | null; + "ImTbJ": ImplicitTextButJson; + + /** + * ImplicitTextButJson | null + */ + "ImTbJPtr": ImplicitTextButJson | null; + + /** Creates a new Data instance. */ + constructor($$source: Partial = {}) { + if (!("NM" in $$source)) { + this["NM"] = (new NonMarshaler()); + } + if (!("NMPtr" in $$source)) { + this["NMPtr"] = null; + } + if (!("VJM" in $$source)) { + this["VJM"] = null; + } + if (!("VJMPtr" in $$source)) { + this["VJMPtr"] = null; + } + if (!("PJM" in $$source)) { + this["PJM"] = null; + } + if (!("PJMPtr" in $$source)) { + this["PJMPtr"] = null; + } + if (!("VTM" in $$source)) { + this["VTM"] = ""; + } + if (!("VTMPtr" in $$source)) { + this["VTMPtr"] = null; + } + if (!("PTM" in $$source)) { + this["PTM"] = ""; + } + if (!("PTMPtr" in $$source)) { + this["PTMPtr"] = null; + } + if (!("VM" in $$source)) { + this["VM"] = null; + } + if (!("VMPtr" in $$source)) { + this["VMPtr"] = null; + } + if (!("PM" in $$source)) { + this["PM"] = null; + } + if (!("PMPtr" in $$source)) { + this["PMPtr"] = null; + } + if (!("UJM" in $$source)) { + this["UJM"] = null; + } + if (!("UJMPtr" in $$source)) { + this["UJMPtr"] = null; + } + if (!("UTM" in $$source)) { + this["UTM"] = ""; + } + if (!("UTMPtr" in $$source)) { + this["UTMPtr"] = null; + } + if (!("UM" in $$source)) { + this["UM"] = null; + } + if (!("UMPtr" in $$source)) { + this["UMPtr"] = null; + } + if (!("JM" in $$source)) { + this["JM"] = null; + } + if (!("JMPtr" in $$source)) { + this["JMPtr"] = null; + } + if (!("TM" in $$source)) { + this["TM"] = ""; + } + if (!("TMPtr" in $$source)) { + this["TMPtr"] = null; + } + if (!("CJM" in $$source)) { + this["CJM"] = null; + } + if (!("CJMPtr" in $$source)) { + this["CJMPtr"] = null; + } + if (!("CTM" in $$source)) { + this["CTM"] = ""; + } + if (!("CTMPtr" in $$source)) { + this["CTMPtr"] = null; + } + if (!("CM" in $$source)) { + this["CM"] = null; + } + if (!("CMPtr" in $$source)) { + this["CMPtr"] = null; + } + if (!("ANM" in $$source)) { + this["ANM"] = {}; + } + if (!("ANMPtr" in $$source)) { + this["ANMPtr"] = null; + } + if (!("AJM" in $$source)) { + this["AJM"] = null; + } + if (!("AJMPtr" in $$source)) { + this["AJMPtr"] = null; + } + if (!("ATM" in $$source)) { + this["ATM"] = ""; + } + if (!("ATMPtr" in $$source)) { + this["ATMPtr"] = null; + } + if (!("AM" in $$source)) { + this["AM"] = null; + } + if (!("AMPtr" in $$source)) { + this["AMPtr"] = null; + } + if (!("ImJM" in $$source)) { + this["ImJM"] = null; + } + if (!("ImJMPtr" in $$source)) { + this["ImJMPtr"] = null; + } + if (!("ImTM" in $$source)) { + this["ImTM"] = ""; + } + if (!("ImTMPtr" in $$source)) { + this["ImTMPtr"] = null; + } + if (!("ImM" in $$source)) { + this["ImM"] = null; + } + if (!("ImMPtr" in $$source)) { + this["ImMPtr"] = null; + } + if (!("ImNJ" in $$source)) { + this["ImNJ"] = ""; + } + if (!("ImNJPtr" in $$source)) { + this["ImNJPtr"] = null; + } + if (!("ImNT" in $$source)) { + this["ImNT"] = null; + } + if (!("ImNTPtr" in $$source)) { + this["ImNTPtr"] = null; + } + if (!("ImNM" in $$source)) { + this["ImNM"] = (new ImplicitNonMarshaler()); + } + if (!("ImNMPtr" in $$source)) { + this["ImNMPtr"] = null; + } + if (!("ImJbT" in $$source)) { + this["ImJbT"] = null; + } + if (!("ImJbTPtr" in $$source)) { + this["ImJbTPtr"] = null; + } + if (!("ImTbJ" in $$source)) { + this["ImTbJ"] = null; + } + if (!("ImTbJPtr" in $$source)) { + this["ImTbJPtr"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Data instance from a string or object. + */ + static createFrom($$source: any = {}): Data { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField48_0 = $$createType2; + const $$createField49_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("NM" in $$parsedSource) { + $$parsedSource["NM"] = $$createField0_0($$parsedSource["NM"]); + } + if ("NMPtr" in $$parsedSource) { + $$parsedSource["NMPtr"] = $$createField1_0($$parsedSource["NMPtr"]); + } + if ("ImNM" in $$parsedSource) { + $$parsedSource["ImNM"] = $$createField48_0($$parsedSource["ImNM"]); + } + if ("ImNMPtr" in $$parsedSource) { + $$parsedSource["ImNMPtr"] = $$createField49_0($$parsedSource["ImNMPtr"]); + } + return new Data($$parsedSource as Partial); + } +} + +/** + * any + */ +export type ImplicitJsonButText = any; + +/** + * any + */ +export type ImplicitJsonMarshaler = any; + +/** + * any + */ +export type ImplicitMarshaler = any; + +/** + * string + */ +export type ImplicitNonJson = string; + +/** + * class{ Marshaler, TextMarshaler } + */ +export class ImplicitNonMarshaler { + "Marshaler": json$0.Marshaler; + "TextMarshaler": encoding$0.TextMarshaler; + + /** Creates a new ImplicitNonMarshaler instance. */ + constructor($$source: Partial = {}) { + if (!("Marshaler" in $$source)) { + this["Marshaler"] = null; + } + if (!("TextMarshaler" in $$source)) { + this["TextMarshaler"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new ImplicitNonMarshaler instance from a string or object. + */ + static createFrom($$source: any = {}): ImplicitNonMarshaler { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new ImplicitNonMarshaler($$parsedSource as Partial); + } +} + +/** + * any + */ +export type ImplicitNonText = any; + +/** + * any + */ +export type ImplicitTextButJson = any; + +/** + * string + */ +export type ImplicitTextMarshaler = string; + +/** + * class {} + */ +export class NonMarshaler { + + /** Creates a new NonMarshaler instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new NonMarshaler instance from a string or object. + */ + static createFrom($$source: any = {}): NonMarshaler { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new NonMarshaler($$parsedSource as Partial); + } +} + +/** + * any + */ +export type PointerJsonMarshaler = any; + +/** + * any + */ +export type PointerMarshaler = any; + +/** + * string + */ +export type PointerTextMarshaler = string; + +/** + * any + */ +export type UnderlyingJsonMarshaler = any; + +/** + * any + */ +export type UnderlyingMarshaler = any; + +/** + * string + */ +export type UnderlyingTextMarshaler = string; + +/** + * any + */ +export type ValueJsonMarshaler = any; + +/** + * any + */ +export type ValueMarshaler = any; + +/** + * string + */ +export type ValueTextMarshaler = string; + +// Private type creation functions +const $$createType0 = NonMarshaler.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = ImplicitNonMarshaler.createFrom; +const $$createType3 = $Create.Nullable($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts new file mode 100644 index 000000000..342fcf3c4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Data> { + return $Call.ByID(4021345184).then(($result: any) => { + return $$createType0($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Data.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts new file mode 100644 index 000000000..19d990dbf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +export { + HowDifferent, + Impersonator, + Person, + PrivatePerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts new file mode 100644 index 000000000..4cb328e1e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts @@ -0,0 +1,163 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + */ +export class HowDifferent { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": { [_: string]: How }[]; + + /** Creates a new HowDifferent instance. */ + constructor($$source: Partial> = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class HowDifferent. + */ + static createFrom($$createParamHow: (source: any) => How): ($$source?: any) => HowDifferent { + const $$createField1_0 = $$createType1($$createParamHow); + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new HowDifferent($$parsedSource as Partial>); + }; + } +} + +/** + * Impersonator gets their fields from other people. + */ +export const Impersonator = other$0.OtherPerson; + +/** + * Impersonator gets their fields from other people. + */ +export type Impersonator = other$0.OtherPerson; + +/** + * Person is not a number. + */ +export class Person { + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField1_0($$parsedSource["Friends"]); + } + return new Person($$parsedSource as Partial); + } +} + +export class personImpl { + /** + * Nickname conceals a person's identity. + */ + "Nickname": string; + + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; + + /** Creates a new personImpl instance. */ + constructor($$source: Partial = {}) { + if (!("Nickname" in $$source)) { + this["Nickname"] = ""; + } + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new personImpl instance from a string or object. + */ + static createFrom($$source: any = {}): personImpl { + const $$createField2_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField2_0($$parsedSource["Friends"]); + } + return new personImpl($$parsedSource as Partial); + } +} + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export const PrivatePerson = personImpl; + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export type PrivatePerson = personImpl; + +// Private type creation functions +const $$createType0 = ($$createParamHow: any) => $Create.Map($Create.Any, $$createParamHow); +const $$createType1 = ($$createParamHow: any) => $Create.Array($$createType0($$createParamHow)); +const $$createType2 = other$0.OtherPerson.createFrom($Create.Any); +const $$createType3 = $Create.Array($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.ts new file mode 100644 index 000000000..ac743f356 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + StringPtr +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.ts new file mode 100644 index 000000000..168c7fdba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * StringPtr is a nullable string. + */ +export type StringPtr = string | null; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts new file mode 100644 index 000000000..d2d973b28 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +export { + OtherPerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts new file mode 100644 index 000000000..711735a2b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts @@ -0,0 +1,52 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * OtherPerson is like a person, but different. + */ +export class OtherPerson { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": T[]; + + /** Creates a new OtherPerson instance. */ + constructor($$source: Partial> = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class OtherPerson. + */ + static createFrom($$createParamT: (source: any) => T): ($$source?: any) => OtherPerson { + const $$createField1_0 = $$createType0($$createParamT); + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new OtherPerson($$parsedSource as Partial>); + }; + } +} + +// Private type creation functions +const $$createType0 = ($$createParamT: any) => $Create.Array($$createParamT); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts new file mode 100644 index 000000000..da11f7f2f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(3606939272); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts new file mode 100644 index 000000000..9ef257343 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts @@ -0,0 +1,39 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]> { + return $Call.ByID(2124352079).then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + }); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(4281222271); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.HowDifferent.createFrom($Create.Any); +const $$createType2 = $models.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts new file mode 100644 index 000000000..a5fe5368f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(3566862802); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts new file mode 100644 index 000000000..615eae691 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts @@ -0,0 +1,39 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]> { + return $Call.ByID(2590614085).then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + }); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(773650321); +} + +// Private type creation functions +const $$createType0 = nobindingshere$0.Person.createFrom; +const $$createType1 = nobindingshere$0.HowDifferent.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts new file mode 100644 index 000000000..83ca81acc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet($0: string): $CancellablePromise { + return $Call.ByID(1411160069, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts new file mode 100644 index 000000000..e69bebf3b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts new file mode 100644 index 000000000..62283209b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts new file mode 100644 index 000000000..6c902629c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts new file mode 100644 index 000000000..62283209b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts new file mode 100644 index 000000000..6c902629c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts new file mode 100644 index 000000000..88cafa9d1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts new file mode 100644 index 000000000..d76f68d9d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +export class Person { + "Name": string; + "Address": services$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
        = {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
        ); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts new file mode 100644 index 000000000..b8c293ad7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(3568225479).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts new file mode 100644 index 000000000..c8ff6e4db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts @@ -0,0 +1,209 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByID(3862002418, $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByID(2424639793, $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByID(3132595881, $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByID(3306292566, $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1754277916, $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1909469092, $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(4251088558, $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1343888303, $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2205561041, $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByID(572240879, $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2189402897, $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByID(642881729, $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1066151743, $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByID(2718999663, $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number }): $CancellablePromise { + return $Call.ByID(2386486356, $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null }): $CancellablePromise { + return $Call.ByID(2163571325, $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] }): $CancellablePromise { + return $Call.ByID(2900172572, $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] }): $CancellablePromise<{ [_: `${number}`]: number[] }> { + return $Call.ByID(881980169, $in).then(($result: any) => { + return $$createType1($result); + }); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByID(1075577233); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByID(3589606958, $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByID(224675106, $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByID(2124953624, $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByID(3516977899, $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByID(229603958, $in); +} + +export function StringArrayInputNamedOutput($in: string[]): $CancellablePromise { + return $Call.ByID(3678582682, $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputNamedOutputs($in: string[]): $CancellablePromise { + return $Call.ByID(319259595, $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringArrayOut($in: string[]): $CancellablePromise { + return $Call.ByID(383995060, $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringOut($in: string[]): $CancellablePromise { + return $Call.ByID(1091960237, $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByID(3835643147, $in).then(($result: any) => { + return $$createType3($result); + }); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByID(2447692557, $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByID(2943477349, $in).then(($result: any) => { + return $$createType4($result); + }); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(3401034892, $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1236957573, $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(1160383782, $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1739300671, $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(793803239, $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1403757716, $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2988345717, $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(518250834, $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2836661285, $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1367187362, $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts new file mode 100644 index 000000000..cd282c90c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + this["Parent"] = null; + } + if (!("Details" in $$source)) { + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts new file mode 100644 index 000000000..c8ff6e4db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts @@ -0,0 +1,209 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByID(3862002418, $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByID(2424639793, $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByID(3132595881, $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByID(3306292566, $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1754277916, $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1909469092, $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(4251088558, $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1343888303, $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2205561041, $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByID(572240879, $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2189402897, $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByID(642881729, $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1066151743, $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByID(2718999663, $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number }): $CancellablePromise { + return $Call.ByID(2386486356, $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null }): $CancellablePromise { + return $Call.ByID(2163571325, $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] }): $CancellablePromise { + return $Call.ByID(2900172572, $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] }): $CancellablePromise<{ [_: `${number}`]: number[] }> { + return $Call.ByID(881980169, $in).then(($result: any) => { + return $$createType1($result); + }); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByID(1075577233); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByID(3589606958, $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByID(224675106, $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByID(2124953624, $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByID(3516977899, $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByID(229603958, $in); +} + +export function StringArrayInputNamedOutput($in: string[]): $CancellablePromise { + return $Call.ByID(3678582682, $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputNamedOutputs($in: string[]): $CancellablePromise { + return $Call.ByID(319259595, $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringArrayOut($in: string[]): $CancellablePromise { + return $Call.ByID(383995060, $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringOut($in: string[]): $CancellablePromise { + return $Call.ByID(1091960237, $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByID(3835643147, $in).then(($result: any) => { + return $$createType3($result); + }); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByID(2447692557, $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByID(2943477349, $in).then(($result: any) => { + return $$createType4($result); + }); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(3401034892, $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1236957573, $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(1160383782, $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1739300671, $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(793803239, $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1403757716, $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2988345717, $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(518250834, $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2836661285, $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1367187362, $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts new file mode 100644 index 000000000..cd282c90c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + this["Parent"] = null; + } + if (!("Details" in $$source)) { + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts new file mode 100644 index 000000000..6e6ac2007 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts new file mode 100644 index 000000000..6e6ac2007 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts new file mode 100644 index 000000000..88cafa9d1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts new file mode 100644 index 000000000..f6eee9de8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts @@ -0,0 +1,47 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + */ +export class Person { + "Name": string; + "Address": services$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
        = {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
        ); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts new file mode 100644 index 000000000..ec098d45a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(1491748400).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/warnings.log b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/warnings.log new file mode 100644 index 000000000..ce8369307 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/warnings.log @@ -0,0 +1,83 @@ +[warn] /testcases/complex_json/main.go:127:2: event 'collision' has one of multiple definitions here with data type map[string]int +[warn] /testcases/events_only/events.go:20:2: event 'collision' has one of multiple definitions here with data type int +[warn] /testcases/events_only/events.go:21:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:10:5: `application.RegisterEvent` is instantiated here but not called +[warn] /testcases/events_only/other.go:13:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:17:2: data type []T for event 'parametric' contains unresolved type parameters and will be ignored` +[warn] /testcases/events_only/other.go:22:2: event 'common:ApplicationStarted' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic +[warn] /testcases/marshalers/main.go:212:2: event 'collision' has one of multiple definitions here with data type *struct{Field []bool} +[warn] /testcases/marshalers/main.go:214:2: data type encoding/json.Marshaler for event 'interface' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors +[warn] dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only +[warn] event 'collision' has multiple conflicting definitions and will be ignored +[warn] events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly +[warn] generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/index.ts new file mode 100644 index 000000000..ba2885969 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + TextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/json/index.ts new file mode 100644 index 000000000..00ec01151 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/json/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + Marshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/json/models.ts new file mode 100644 index 000000000..8cd1a164f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/json/models.ts @@ -0,0 +1,12 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + */ +export type Marshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/models.ts new file mode 100644 index 000000000..235dfce3e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/models.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + */ +export type TextMarshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/eventcreate.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/eventcreate.ts new file mode 100644 index 000000000..9cc7781aa --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/eventcreate.ts @@ -0,0 +1,39 @@ +//@ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../encoding/json/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as events_only$0 from "./generator/testcases/events_only/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as more$0 from "./generator/testcases/no_bindings_here/more/models.js"; + +function configure() { + Object.freeze(Object.assign($Create.Events, { + "events_only:class": $$createType0, + "events_only:map": $$createType2, + "events_only:other": $$createType3, + "overlap": $$createType6, + })); +} + +// Private type creation functions +const $$createType0 = events_only$0.SomeClass.createFrom; +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = $Create.Map($Create.Any, $$createType1); +const $$createType3 = $Create.Array($Create.Any); +const $$createType4 = $Create.Array($Create.Any); +const $$createType5 = $Create.Struct({ + "Field": $$createType4, +}); +const $$createType6 = $Create.Nullable($$createType5); + +configure(); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/eventdata.d.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/eventdata.d.ts new file mode 100644 index 000000000..d265e3adc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/eventdata.d.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type { Events } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as json$0 from "../../../../../encoding/json/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as events_only$0 from "./generator/testcases/events_only/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as more$0 from "./generator/testcases/no_bindings_here/more/models.js"; + +declare module "/wails/runtime.js" { + namespace Events { + interface CustomEvents { + "events_only:class": events_only$0.SomeClass; + "events_only:map": { [_: string]: number[] }; + "events_only:nodata": void; + "events_only:other": more$0.StringPtr[]; + "events_only:string": string; + "interface": json$0.Marshaler; + "overlap": {"Field": boolean[]} | null; + } + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts new file mode 100644 index 000000000..b33d68383 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts @@ -0,0 +1,82 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + */ +export function Get(aliasValue: $models.Alias): $CancellablePromise<$models.Person> { + return $Call.ByName("main.GreetService.Get", aliasValue).then(($result: any) => { + return $$createType0($result); + }); +} + +/** + * Apparently, aliases are all the rage right now. + */ +export function GetButAliased(p: $models.AliasedPerson): $CancellablePromise<$models.StrangelyAliasedPerson> { + return $Call.ByName("main.GreetService.GetButAliased", p).then(($result: any) => { + return $$createType0($result); + }); +} + +/** + * Get someone quite different. + */ +export function GetButDifferent(): $CancellablePromise<$models.GenericPerson> { + return $Call.ByName("main.GreetService.GetButDifferent").then(($result: any) => { + return $$createType1($result); + }); +} + +export function GetButForeignPrivateAlias(): $CancellablePromise { + return $Call.ByName("main.GreetService.GetButForeignPrivateAlias").then(($result: any) => { + return $$createType2($result); + }); +} + +export function GetButGenericAliases(): $CancellablePromise<$models.AliasGroup> { + return $Call.ByName("main.GreetService.GetButGenericAliases").then(($result: any) => { + return $$createType3($result); + }); +} + +/** + * Greet a lot of unusual things. + */ +export function Greet($0: $models.EmptyAliasStruct, $1: $models.EmptyStruct): $CancellablePromise<$models.AliasStruct> { + return $Call.ByName("main.GreetService.Greet", $0, $1).then(($result: any) => { + return $$createType7($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.GenericPerson.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; +const $$createType3 = $models.AliasGroup.createFrom; +const $$createType4 = $Create.Array($Create.Any); +const $$createType5 = $Create.Array($Create.Any); +const $$createType6 = $Create.Struct({ + "NoMoreIdeas": $$createType5, +}); +const $$createType7 = $Create.Struct({ + "Foo": $$createType4, + "Other": $$createType6, +}); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts new file mode 100644 index 000000000..13f61da0f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + AliasGroup, + AliasedPerson, + EmptyStruct, + GenericPerson, + GenericPersonAlias, + IndirectPersonAlias, + Person, + StrangelyAliasedPerson, + TPIndirectPersonAlias +} from "./models.js"; + +export type { + Alias, + AliasStruct, + EmptyAliasStruct, + GenericAlias, + GenericMapAlias, + GenericPtrAlias, + OtherAliasStruct +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts new file mode 100644 index 000000000..63ca43914 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts @@ -0,0 +1,290 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * A nice type Alias. + */ +export type Alias = number; + +/** + * A class whose fields have various aliased types. + */ +export class AliasGroup { + "GAi": GenericAlias; + "GAP": GenericAlias>; + "GPAs": GenericPtrAlias; + "GPAP": GenericPtrAlias>; + "GMA": GenericMapAlias; + "GPA": GenericPersonAlias; + "IPA": IndirectPersonAlias; + "TPIPA": TPIndirectPersonAlias; + + /** Creates a new AliasGroup instance. */ + constructor($$source: Partial = {}) { + if (!("GAi" in $$source)) { + this["GAi"] = 0; + } + if (!("GAP" in $$source)) { + this["GAP"] = (new GenericPerson()); + } + if (!("GPAs" in $$source)) { + this["GPAs"] = null; + } + if (!("GPAP" in $$source)) { + this["GPAP"] = null; + } + if (!("GMA" in $$source)) { + this["GMA"] = {}; + } + if (!("GPA" in $$source)) { + this["GPA"] = (new GenericPersonAlias()); + } + if (!("IPA" in $$source)) { + this["IPA"] = (new IndirectPersonAlias()); + } + if (!("TPIPA" in $$source)) { + this["TPIPA"] = (new TPIndirectPersonAlias()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new AliasGroup instance from a string or object. + */ + static createFrom($$source: any = {}): AliasGroup { + const $$createField1_0 = $$createType0; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType5; + const $$createField4_0 = $$createType6; + const $$createField5_0 = $$createType8; + const $$createField6_0 = $$createType8; + const $$createField7_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("GAP" in $$parsedSource) { + $$parsedSource["GAP"] = $$createField1_0($$parsedSource["GAP"]); + } + if ("GPAs" in $$parsedSource) { + $$parsedSource["GPAs"] = $$createField2_0($$parsedSource["GPAs"]); + } + if ("GPAP" in $$parsedSource) { + $$parsedSource["GPAP"] = $$createField3_0($$parsedSource["GPAP"]); + } + if ("GMA" in $$parsedSource) { + $$parsedSource["GMA"] = $$createField4_0($$parsedSource["GMA"]); + } + if ("GPA" in $$parsedSource) { + $$parsedSource["GPA"] = $$createField5_0($$parsedSource["GPA"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField6_0($$parsedSource["IPA"]); + } + if ("TPIPA" in $$parsedSource) { + $$parsedSource["TPIPA"] = $$createField7_0($$parsedSource["TPIPA"]); + } + return new AliasGroup($$parsedSource as Partial); + } +} + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + */ +export interface AliasStruct { + /** + * A field with a comment. + */ + "Foo": number[]; + + /** + * Definitely not Foo. + */ + "Bar"?: string; + "Baz"?: string; + + /** + * A nested alias struct. + */ + "Other": OtherAliasStruct; +} + +/** + * An empty struct alias. + */ +export interface EmptyAliasStruct { +} + +/** + * An empty struct. + */ +export class EmptyStruct { + + /** Creates a new EmptyStruct instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new EmptyStruct instance from a string or object. + */ + static createFrom($$source: any = {}): EmptyStruct { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new EmptyStruct($$parsedSource as Partial); + } +} + +/** + * A generic alias that forwards to a type parameter. + */ +export type GenericAlias = T; + +/** + * A generic alias that wraps a map. + */ +export type GenericMapAlias = { [_: string]: U }; + +/** + * A generic struct containing an alias. + */ +export class GenericPerson { + "Name"?: T; + "AliasedField": Alias; + + /** Creates a new GenericPerson instance. */ + constructor($$source: Partial> = {}) { + if (!("AliasedField" in $$source)) { + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class GenericPerson. + */ + static createFrom($$createParamT: (source: any) => T): ($$source?: any) => GenericPerson { + const $$createField0_0 = $$createParamT; + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Name" in $$parsedSource) { + $$parsedSource["Name"] = $$createField0_0($$parsedSource["Name"]); + } + return new GenericPerson($$parsedSource as Partial>); + }; + } +} + +/** + * A generic alias that wraps a generic struct. + */ +export const GenericPersonAlias = GenericPerson; + +/** + * A generic alias that wraps a generic struct. + */ +export type GenericPersonAlias = GenericPerson[]>; + +/** + * A generic alias that wraps a pointer type. + */ +export type GenericPtrAlias = GenericAlias | null; + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export const IndirectPersonAlias = GenericPersonAlias; + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export type IndirectPersonAlias = GenericPersonAlias; + +/** + * Another struct alias. + */ +export interface OtherAliasStruct { + "NoMoreIdeas": number[]; +} + +/** + * A non-generic struct containing an alias. + */ +export class Person { + /** + * The Person's name. + */ + "Name": string; + + /** + * A random alias field. + */ + "AliasedField": Alias; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("AliasedField" in $$source)) { + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person($$parsedSource as Partial); + } +} + +/** + * A class alias. + */ +export const AliasedPerson = Person; + +/** + * A class alias. + */ +export type AliasedPerson = Person; + +/** + * Another class alias, but ordered after its aliased class. + */ +export const StrangelyAliasedPerson = Person; + +/** + * Another class alias, but ordered after its aliased class. + */ +export type StrangelyAliasedPerson = Person; + +/** + * An alias that wraps a class through a typeparam alias. + */ +export const TPIndirectPersonAlias = GenericPerson; + +/** + * An alias that wraps a class through a typeparam alias. + */ +export type TPIndirectPersonAlias = GenericAlias>; + +// Private type creation functions +const $$createType0 = GenericPerson.createFrom($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = $Create.Nullable($$createType1); +const $$createType3 = $Create.Array($Create.Any); +const $$createType4 = GenericPerson.createFrom($$createType3); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Array($Create.Any); +const $$createType8 = GenericPerson.createFrom($$createType7); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts new file mode 100644 index 000000000..bdcf43c67 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts new file mode 100644 index 000000000..0780a0de3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function TestMethod(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service7.TestMethod"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts new file mode 100644 index 000000000..a2222265f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function TestMethod2(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service9.TestMethod2"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts new file mode 100644 index 000000000..f2106e7f4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(person: $models.Person, emb: $models.Embedded1): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts new file mode 100644 index 000000000..6cdc52c66 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts @@ -0,0 +1,17 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Embedded1, + Person, + Title +} from "./models.js"; + +export type { + Embedded3 +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts new file mode 100644 index 000000000..50f23b52d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts @@ -0,0 +1,237 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Embedded1 { + /** + * Friends should be shadowed in Person by a field of lesser depth + */ + "Friends": number; + + /** + * Vanish should be omitted from Person because there is another field with same depth and no tag + */ + "Vanish": number; + + /** + * StillThere should be shadowed in Person by other field with same depth and a json tag + */ + "StillThere": string; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** Creates a new Embedded1 instance. */ + constructor($$source: Partial = {}) { + if (!("Friends" in $$source)) { + this["Friends"] = 0; + } + if (!("Vanish" in $$source)) { + this["Vanish"] = 0; + } + if (!("StillThere" in $$source)) { + this["StillThere"] = ""; + } + if (!("NamingThingsIsHard" in $$source)) { + this["NamingThingsIsHard"] = "false"; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Embedded1 instance from a string or object. + */ + static createFrom($$source: any = {}): Embedded1 { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Embedded1($$parsedSource as Partial); + } +} + +export type Embedded3 = string; + +/** + * Person represents a person + */ +export class Person { + /** + * Titles is optional in JSON + */ + "Titles"?: Title[]; + + /** + * Names has a + * multiline comment + */ + "Names": string[]; + + /** + * Partner has a custom and complex JSON key + */ + "Partner": Person | null; + "Friends": (Person | null)[]; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * StillThereButRenamed should shadow in Person the other field with same depth and no json tag + */ + "StillThere": Embedded3 | null; + + /** + * StrangeNumber maps to "-" + */ + "-": number; + + /** + * Embedded3 should appear with key "Embedded3" + */ + "Embedded3": Embedded3; + + /** + * StrangerNumber is serialized as a string + */ + "StrangerNumber": `${number}`; + + /** + * StrangestString is optional and serialized as a JSON string + */ + "StrangestString"?: `"${string}"`; + + /** + * StringStrangest is serialized as a JSON string and optional + */ + "StringStrangest"?: `"${string}"`; + + /** + * embedded4 should be optional and appear with key "emb4" + */ + "emb4"?: embedded4; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Names" in $$source)) { + this["Names"] = []; + } + if (!("Partner" in $$source)) { + this["Partner"] = null; + } + if (!("Friends" in $$source)) { + this["Friends"] = []; + } + if (!("NamingThingsIsHard" in $$source)) { + this["NamingThingsIsHard"] = "false"; + } + if (!("StillThere" in $$source)) { + this["StillThere"] = null; + } + if (!("-" in $$source)) { + this["-"] = 0; + } + if (!("Embedded3" in $$source)) { + this["Embedded3"] = ""; + } + if (!("StrangerNumber" in $$source)) { + this["StrangerNumber"] = "0"; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType3; + const $$createField3_0 = $$createType4; + const $$createField11_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Titles" in $$parsedSource) { + $$parsedSource["Titles"] = $$createField0_0($$parsedSource["Titles"]); + } + if ("Names" in $$parsedSource) { + $$parsedSource["Names"] = $$createField1_0($$parsedSource["Names"]); + } + if ("Partner" in $$parsedSource) { + $$parsedSource["Partner"] = $$createField2_0($$parsedSource["Partner"]); + } + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField3_0($$parsedSource["Friends"]); + } + if ("emb4" in $$parsedSource) { + $$parsedSource["emb4"] = $$createField11_0($$parsedSource["emb4"]); + } + return new Person($$parsedSource as Partial); + } +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; + +export class embedded4 { + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * Friends should not be shadowed in Person as embedded4 is not embedded + * from encoding/json's point of view; + * however, it should be shadowed in Embedded1 + */ + "Friends": boolean; + + /** Creates a new embedded4 instance. */ + constructor($$source: Partial = {}) { + if (!("NamingThingsIsHard" in $$source)) { + this["NamingThingsIsHard"] = "false"; + } + if (!("Friends" in $$source)) { + this["Friends"] = false; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new embedded4 instance from a string or object. + */ + static createFrom($$source: any = {}): embedded4 { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new embedded4($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = Person.createFrom; +const $$createType3 = $Create.Nullable($$createType2); +const $$createType4 = $Create.Array($$createType3); +const $$createType5 = embedded4.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts new file mode 100644 index 000000000..4471270ed --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts @@ -0,0 +1,32 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + */ +export function Greet(str: string, people: $models.Person[], $2: {"AnotherCount": number, "AnotherOne": $models.Person | null}, assoc: { [_: `${number}`]: boolean | null }, $4: (number | null)[], ...other: string[]): $CancellablePromise<[$models.Person, any, number[]]> { + return $Call.ByName("main.GreetService.Greet", str, people, $2, assoc, $4, other).then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[2] = $$createType1($result[2]); + return $result; + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Array($Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts new file mode 100644 index 000000000..2417aff4c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Person represents a person + */ +export class Person { + "Name": string; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person($$parsedSource as Partial); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts new file mode 100644 index 000000000..2f0e45aff --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.StructA, $models.StructC]> { + return $Call.ByName("main.GreetService.MakeCycles").then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + return $result; + }); +} + +// Private type creation functions +const $$createType0 = $models.StructA.createFrom; +const $$createType1 = $models.StructC.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts new file mode 100644 index 000000000..4b190b8da --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + StructA, + StructC, + StructE +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts new file mode 100644 index 000000000..9e86cd674 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts @@ -0,0 +1,131 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class StructA { + "B": structB | null; + + /** Creates a new StructA instance. */ + constructor($$source: Partial = {}) { + if (!("B" in $$source)) { + this["B"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructA instance from a string or object. + */ + static createFrom($$source: any = {}): StructA { + const $$createField0_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("B" in $$parsedSource) { + $$parsedSource["B"] = $$createField0_0($$parsedSource["B"]); + } + return new StructA($$parsedSource as Partial); + } +} + +export class StructC { + "D": structD; + + /** Creates a new StructC instance. */ + constructor($$source: Partial = {}) { + if (!("D" in $$source)) { + this["D"] = (new structD()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructC instance from a string or object. + */ + static createFrom($$source: any = {}): StructC { + const $$createField0_0 = $$createType2; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("D" in $$parsedSource) { + $$parsedSource["D"] = $$createField0_0($$parsedSource["D"]); + } + return new StructC($$parsedSource as Partial); + } +} + +export class StructE { + + /** Creates a new StructE instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new StructE instance from a string or object. + */ + static createFrom($$source: any = {}): StructE { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new StructE($$parsedSource as Partial); + } +} + +export class structB { + "A": StructA | null; + + /** Creates a new structB instance. */ + constructor($$source: Partial = {}) { + if (!("A" in $$source)) { + this["A"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structB instance from a string or object. + */ + static createFrom($$source: any = {}): structB { + const $$createField0_0 = $$createType4; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField0_0($$parsedSource["A"]); + } + return new structB($$parsedSource as Partial); + } +} + +export class structD { + "E": StructE; + + /** Creates a new structD instance. */ + constructor($$source: Partial = {}) { + if (!("E" in $$source)) { + this["E"] = (new StructE()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structD instance from a string or object. + */ + static createFrom($$source: any = {}): structD { + const $$createField0_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("E" in $$parsedSource) { + $$parsedSource["E"] = $$createField0_0($$parsedSource["E"]); + } + return new structD($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = structB.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = structD.createFrom; +const $$createType3 = StructA.createFrom; +const $$createType4 = $Create.Nullable($$createType3); +const $$createType5 = StructE.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts new file mode 100644 index 000000000..dba844168 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts @@ -0,0 +1,63 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]> { + return $Call.ByName("main.GreetService.MakeCycles").then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType9($result[1]); + return $result; + }); +} + +// Private type creation functions +var $$createType0 = (function $$initCreateType0(...args: any[]): any { + if ($$createType0 === $$initCreateType0) { + $$createType0 = $$createType3; + } + return $$createType0(...args); +}); +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = $Create.Map($Create.Any, $$createType1); +const $$createType3 = $Create.Array($$createType2); +var $$createType4 = (function $$initCreateType4(...args: any[]): any { + if ($$createType4 === $$initCreateType4) { + $$createType4 = $$createType8; + } + return $$createType4(...args); +}); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Array($Create.Any); +const $$createType7 = $Create.Struct({ + "X": $$createType5, + "Y": $$createType6, +}); +const $$createType8 = $Create.Array($$createType7); +var $$createType9 = (function $$initCreateType9(...args: any[]): any { + if ($$createType9 === $$initCreateType9) { + $$createType9 = $$createType13; + } + return $$createType9(...args); +}); +const $$createType10 = $Create.Nullable($$createType9); +const $$createType11 = $Create.Array($$createType4); +const $$createType12 = $Create.Struct({ + "X": $$createType10, + "Y": $$createType11, +}); +const $$createType13 = $Create.Array($$createType12); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts new file mode 100644 index 000000000..16cef660c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Alias, + Cyclic, + GenericCyclic +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts new file mode 100644 index 000000000..93d1ef5c6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts @@ -0,0 +1,12 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export type Alias = Cyclic | null; + +export type Cyclic = { [_: string]: Alias }[]; + +export type GenericCyclic = {"X": GenericCyclic | null, "Y": T[]}[]; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts new file mode 100644 index 000000000..b9fbdba96 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Classes!"); +console.log("Hello TS!"); +console.log("Hello TS Classes!"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts new file mode 100644 index 000000000..9271273bc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.InternalModel): $CancellablePromise { + return $Call.ByName("main.InternalService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts new file mode 100644 index 000000000..4d242fc2c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts @@ -0,0 +1,54 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * An exported but internal model. + */ +export class InternalModel { + "Field": string; + + /** Creates a new InternalModel instance. */ + constructor($$source: Partial = {}) { + if (!("Field" in $$source)) { + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new InternalModel instance from a string or object. + */ + static createFrom($$source: any = {}): InternalModel { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new InternalModel($$parsedSource as Partial); + } +} + +/** + * An unexported model. + */ +export class unexportedModel { + "Field": string; + + /** Creates a new unexportedModel instance. */ + constructor($$source: Partial = {}) { + if (!("Field" in $$source)) { + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new unexportedModel instance from a string or object. + */ + static createFrom($$source: any = {}): unexportedModel { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new unexportedModel($$parsedSource as Partial); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts new file mode 100644 index 000000000..e52a0ccb8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Dummy +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts new file mode 100644 index 000000000..b927155d5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Dummy { + + /** Creates a new Dummy instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new Dummy instance from a string or object. + */ + static createFrom($$source: any = {}): Dummy { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Dummy($$parsedSource as Partial); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_t.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_t.ts new file mode 100644 index 000000000..6703820f1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_tc.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_tc.ts new file mode 100644 index 000000000..15d2994e9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_tc.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("TS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts new file mode 100644 index 000000000..0687bd8ac --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +function InternalMethod($0: string): $CancellablePromise { + return $Call.ByName("main.Service.InternalMethod", $0); +} + +export function VisibleMethod($0: otherpackage$0.Dummy): $CancellablePromise { + return $Call.ByName("main.Service.VisibleMethod", $0); +} + +export async function CustomMethod(arg: string): Promise { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js new file mode 100644 index 000000000..724e79e12 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Classes"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts new file mode 100644 index 000000000..253d3f2f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_tc.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_tc.ts new file mode 100644 index 000000000..66b739d3a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_tc.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts new file mode 100644 index 000000000..34b23e24d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.unexportedModel): $CancellablePromise { + return $Call.ByName("main.unexportedService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts new file mode 100644 index 000000000..c5b06be75 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts @@ -0,0 +1,47 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Comment 1. + */ +export function Method1(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method1"); +} + +/** + * Comment 2. + */ +export function Method2(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method2"); +} + +/** + * Comment 3a. + * Comment 3b. + */ +export function Method3(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method3"); +} + +/** + * Comment 4. + */ +export function Method4(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method4"); +} + +/** + * Comment 5. + */ +export function Method5(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method5"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts new file mode 100644 index 000000000..45abc153e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: $models.Title): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name, title); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts new file mode 100644 index 000000000..3b4d2f5c6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Person, + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts new file mode 100644 index 000000000..a50282a38 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts @@ -0,0 +1,82 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Age is an integer with some predefined values + */ +export type Age = number; + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + */ +export class Person { + "Title": Title; + "Name": string; + "Age": Age; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Title" in $$source)) { + this["Title"] = Title.$zero; + } + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Age" in $$source)) { + this["Age"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person($$parsedSource as Partial); + } +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts new file mode 100644 index 000000000..c4afd85da --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: services$0.Title): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts new file mode 100644 index 000000000..01c612edc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts new file mode 100644 index 000000000..661222bdf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.ts new file mode 100644 index 000000000..bb6e0a17f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + SomeClass +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.ts new file mode 100644 index 000000000..4719f61cf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.ts @@ -0,0 +1,45 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * SomeClass renders as a TS class. + */ +export class SomeClass { + "Field": string; + "Meadow": nobindingshere$0.HowDifferent; + + /** Creates a new SomeClass instance. */ + constructor($$source: Partial = {}) { + if (!("Field" in $$source)) { + this["Field"] = ""; + } + if (!("Meadow" in $$source)) { + this["Meadow"] = (new nobindingshere$0.HowDifferent()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new SomeClass instance from a string or object. + */ + static createFrom($$source: any = {}): SomeClass { + const $$createField1_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Meadow" in $$parsedSource) { + $$parsedSource["Meadow"] = $$createField1_0($$parsedSource["Meadow"]); + } + return new SomeClass($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = nobindingshere$0.HowDifferent.createFrom($Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts new file mode 100644 index 000000000..1a2ffb266 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts new file mode 100644 index 000000000..f1ff262a1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts @@ -0,0 +1,46 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + */ +export class Person { + "Name": string; + "Address": services$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
        = {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
        ); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts new file mode 100644 index 000000000..f44f172e7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services.OtherService.Yay").then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts new file mode 100644 index 000000000..1a2ffb266 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts new file mode 100644 index 000000000..88b2c78db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +export class Person { + "Name": string; + "Address": other$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = other$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
        = {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
        ); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts new file mode 100644 index 000000000..fa9a93fc8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other.OtherService.Yay").then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts new file mode 100644 index 000000000..460b2b374 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts new file mode 100644 index 000000000..6a6a881dc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts new file mode 100644 index 000000000..aec011527 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts @@ -0,0 +1,25 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * Greet someone + */ +export function GreetWithContext(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.GreetWithContext", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts new file mode 100644 index 000000000..6a6a881dc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts new file mode 100644 index 000000000..b5890af28 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts @@ -0,0 +1,33 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Maps +} from "./models.js"; + +export type { + BasicCstrAlias, + ComparableCstrAlias, + EmbeddedCustomInterface, + EmbeddedOriginalInterface, + EmbeddedPointer, + EmbeddedPointerPtr, + EmbeddedValue, + EmbeddedValuePtr, + GoodTildeCstrAlias, + InterfaceCstrAlias, + MixedCstrAlias, + NonBasicCstrAlias, + PointableCstrAlias, + PointerAlias, + PointerTextMarshaler, + StringAlias, + StringType, + ValueAlias, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts new file mode 100644 index 000000000..0d26a6b0b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts @@ -0,0 +1,1886 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export type BasicCstrAlias = S; + +export type ComparableCstrAlias = R; + +export type EmbeddedCustomInterface = string; + +export type EmbeddedOriginalInterface = string; + +export type EmbeddedPointer = string; + +export type EmbeddedPointerPtr = string; + +export type EmbeddedValue = string; + +export type EmbeddedValuePtr = string; + +export type GoodTildeCstrAlias = U; + +export type InterfaceCstrAlias = Y; + +export class Maps { + /** + * Reject + */ + "Bool": { [_: string]: number }; + + /** + * Accept + */ + "Int": { [_: `${number}`]: number }; + + /** + * Accept + */ + "Uint": { [_: `${number}`]: number }; + + /** + * Reject + */ + "Float": { [_: string]: number }; + + /** + * Reject + */ + "Complex": { [_: string]: number }; + + /** + * Accept + */ + "Byte": { [_: `${number}`]: number }; + + /** + * Accept + */ + "Rune": { [_: `${number}`]: number }; + + /** + * Accept + */ + "String": { [_: string]: number }; + + /** + * Reject + */ + "IntPtr": { [_: string]: number }; + + /** + * Reject + */ + "UintPtr": { [_: string]: number }; + + /** + * Reject + */ + "FloatPtr": { [_: string]: number }; + + /** + * Reject + */ + "ComplexPtr": { [_: string]: number }; + + /** + * Reject + */ + "StringPtr": { [_: string]: number }; + + /** + * Reject + */ + "NTM": { [_: string]: number }; + + /** + * Reject + */ + "NTMPtr": { [_: string]: number }; + + /** + * Accept + */ + "VTM": { [_: ValueTextMarshaler]: number }; + + /** + * Accept + */ + "VTMPtr": { [_: ValueTextMarshaler]: number }; + + /** + * Reject + */ + "PTM": { [_: string]: number }; + + /** + * Accept + */ + "PTMPtr": { [_: PointerTextMarshaler]: number }; + + /** + * Accept, hide + */ + "JTM": { [_: string]: number }; + + /** + * Accept, hide + */ + "JTMPtr": { [_: string]: number }; + + /** + * Reject + */ + "A": { [_: string]: number }; + + /** + * Reject + */ + "APtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TM": { [_: string]: number }; + + /** + * Reject + */ + "TMPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "CI": { [_: string]: number }; + + /** + * Reject + */ + "CIPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "EI": { [_: string]: number }; + + /** + * Reject + */ + "EIPtr": { [_: string]: number }; + + /** + * Accept + */ + "EV": { [_: EmbeddedValue]: number }; + + /** + * Accept + */ + "EVPtr": { [_: EmbeddedValue]: number }; + + /** + * Accept + */ + "EVP": { [_: EmbeddedValuePtr]: number }; + + /** + * Accept + */ + "EVPPtr": { [_: EmbeddedValuePtr]: number }; + + /** + * Reject + */ + "EP": { [_: string]: number }; + + /** + * Accept + */ + "EPPtr": { [_: EmbeddedPointer]: number }; + + /** + * Accept + */ + "EPP": { [_: EmbeddedPointerPtr]: number }; + + /** + * Accept + */ + "EPPPtr": { [_: EmbeddedPointerPtr]: number }; + + /** + * Accept + */ + "ECI": { [_: EmbeddedCustomInterface]: number }; + + /** + * Accept + */ + "ECIPtr": { [_: EmbeddedCustomInterface]: number }; + + /** + * Accept + */ + "EOI": { [_: EmbeddedOriginalInterface]: number }; + + /** + * Accept + */ + "EOIPtr": { [_: EmbeddedOriginalInterface]: number }; + + /** + * Reject + */ + "WT": { [_: string]: number }; + + /** + * Reject + */ + "WA": { [_: string]: number }; + + /** + * Accept + */ + "ST": { [_: StringType]: number }; + + /** + * Accept + */ + "SA": { [_: StringAlias]: number }; + + /** + * Accept + */ + "IntT": { [_: `${number}`]: number }; + + /** + * Accept + */ + "IntA": { [_: `${number}`]: number }; + + /** + * Reject + */ + "VT": { [_: string]: number }; + + /** + * Reject + */ + "VTPtr": { [_: string]: number }; + + /** + * Reject + */ + "VPT": { [_: string]: number }; + + /** + * Reject + */ + "VPTPtr": { [_: string]: number }; + + /** + * Accept + */ + "VA": { [_: ValueAlias]: number }; + + /** + * Accept + */ + "VAPtr": { [_: ValueAlias]: number }; + + /** + * Accept, hide + */ + "VPA": { [_: string]: number }; + + /** + * Reject + */ + "VPAPtr": { [_: string]: number }; + + /** + * Reject + */ + "PT": { [_: string]: number }; + + /** + * Reject + */ + "PTPtr": { [_: string]: number }; + + /** + * Reject + */ + "PPT": { [_: string]: number }; + + /** + * Reject + */ + "PPTPtr": { [_: string]: number }; + + /** + * Reject + */ + "PA": { [_: string]: number }; + + /** + * Accept + */ + "PAPtr": { [_: PointerAlias]: number }; + + /** + * Accept, hide + */ + "PPA": { [_: string]: number }; + + /** + * Reject + */ + "PPAPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "IT": { [_: string]: number }; + + /** + * Reject + */ + "ITPtr": { [_: string]: number }; + + /** + * Reject + */ + "IPT": { [_: string]: number }; + + /** + * Reject + */ + "IPTPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "IA": { [_: string]: number }; + + /** + * Reject + */ + "IAPtr": { [_: string]: number }; + + /** + * Reject + */ + "IPA": { [_: string]: number }; + + /** + * Reject + */ + "IPAPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "TPR": { [_: string]: number }; + + /** + * Soft reject + */ + "TPRPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPS": { [_: string]: number }; + + /** + * Soft reject + */ + "TPSPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "TPT": { [_: string]: number }; + + /** + * Soft reject + */ + "TPTPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPU": { [_: string]: number }; + + /** + * Soft reject + */ + "TPUPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPV": { [_: string]: number }; + + /** + * Soft reject + */ + "TPVPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "TPW": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPWPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPX": { [_: string]: number }; + + /** + * Soft reject + */ + "TPXPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPY": { [_: string]: number }; + + /** + * Soft reject + */ + "TPYPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPZ": { [_: string]: number }; + + /** + * Soft reject + */ + "TPZPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "GAR": { [_: string]: number }; + + /** + * Soft reject + */ + "GARPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAS": { [_: string]: number }; + + /** + * Soft reject + */ + "GASPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "GAT": { [_: string]: number }; + + /** + * Soft reject + */ + "GATPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAU": { [_: string]: number }; + + /** + * Soft reject + */ + "GAUPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAV": { [_: string]: number }; + + /** + * Soft reject + */ + "GAVPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "GAW": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAWPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAX": { [_: string]: number }; + + /** + * Soft reject + */ + "GAXPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAY": { [_: string]: number }; + + /** + * Soft reject + */ + "GAYPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAZ": { [_: string]: number }; + + /** + * Soft reject + */ + "GAZPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GACi": { [_: `${number}`]: number }; + + /** + * Accept + */ + "GACV": { [_: ComparableCstrAlias]: number }; + + /** + * Reject + */ + "GACP": { [_: string]: number }; + + /** + * Reject + */ + "GACiPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GACVPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GACPPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GABi": { [_: `${number}`]: number }; + + /** + * Accept + */ + "GABs": { [_: BasicCstrAlias]: number }; + + /** + * Reject + */ + "GABiPtr": { [_: string]: number }; + + /** + * Reject + */ + "GABT": { [_: string]: number }; + + /** + * Reject + */ + "GABTPtr": { [_: string]: number }; + + /** + * Accept + */ + "GAGT": { [_: GoodTildeCstrAlias]: number }; + + /** + * Accept, hide + */ + "GAGTPtr": { [_: string]: number }; + + /** + * Accept + */ + "GANBV": { [_: NonBasicCstrAlias]: number }; + + /** + * Accept, hide + */ + "GANBP": { [_: string]: number }; + + /** + * Accept, hide + */ + "GANBVPtr": { [_: string]: number }; + + /** + * Reject + */ + "GANBPPtr": { [_: string]: number }; + + /** + * Accept + */ + "GAPlV1": { [_: PointableCstrAlias]: number }; + + /** + * Accept + */ + "GAPlV2": { [_: PointableCstrAlias]: number }; + + /** + * Reject + */ + "GAPlP1": { [_: string]: number }; + + /** + * Accept + */ + "GAPlP2": { [_: PointableCstrAlias]: number }; + + /** + * Accept, hide + */ + "GAPlVPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAPlPPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAMi": { [_: `${number}`]: number }; + + /** + * Accept + */ + "GAMS": { [_: MixedCstrAlias]: number }; + + /** + * Accept + */ + "GAMV": { [_: MixedCstrAlias]: number }; + + /** + * Reject + */ + "GAMSPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAMVPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAII": { [_: string]: number }; + + /** + * Accept + */ + "GAIV": { [_: InterfaceCstrAlias]: number }; + + /** + * Accept, hide + */ + "GAIP": { [_: string]: number }; + + /** + * Reject + */ + "GAIIPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAIVPtr": { [_: string]: number }; + + /** + * Reject + */ + "GAIPPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAPrV": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAPrP": { [_: string]: number }; + + /** + * Reject + */ + "GAPrVPtr": { [_: string]: number }; + + /** + * Reject + */ + "GAPrPPtr": { [_: string]: number }; + + /** Creates a new Maps instance. */ + constructor($$source: Partial> = {}) { + if (!("Bool" in $$source)) { + this["Bool"] = {}; + } + if (!("Int" in $$source)) { + this["Int"] = {}; + } + if (!("Uint" in $$source)) { + this["Uint"] = {}; + } + if (!("Float" in $$source)) { + this["Float"] = {}; + } + if (!("Complex" in $$source)) { + this["Complex"] = {}; + } + if (!("Byte" in $$source)) { + this["Byte"] = {}; + } + if (!("Rune" in $$source)) { + this["Rune"] = {}; + } + if (!("String" in $$source)) { + this["String"] = {}; + } + if (!("IntPtr" in $$source)) { + this["IntPtr"] = {}; + } + if (!("UintPtr" in $$source)) { + this["UintPtr"] = {}; + } + if (!("FloatPtr" in $$source)) { + this["FloatPtr"] = {}; + } + if (!("ComplexPtr" in $$source)) { + this["ComplexPtr"] = {}; + } + if (!("StringPtr" in $$source)) { + this["StringPtr"] = {}; + } + if (!("NTM" in $$source)) { + this["NTM"] = {}; + } + if (!("NTMPtr" in $$source)) { + this["NTMPtr"] = {}; + } + if (!("VTM" in $$source)) { + this["VTM"] = {}; + } + if (!("VTMPtr" in $$source)) { + this["VTMPtr"] = {}; + } + if (!("PTM" in $$source)) { + this["PTM"] = {}; + } + if (!("PTMPtr" in $$source)) { + this["PTMPtr"] = {}; + } + if (!("JTM" in $$source)) { + this["JTM"] = {}; + } + if (!("JTMPtr" in $$source)) { + this["JTMPtr"] = {}; + } + if (!("A" in $$source)) { + this["A"] = {}; + } + if (!("APtr" in $$source)) { + this["APtr"] = {}; + } + if (!("TM" in $$source)) { + this["TM"] = {}; + } + if (!("TMPtr" in $$source)) { + this["TMPtr"] = {}; + } + if (!("CI" in $$source)) { + this["CI"] = {}; + } + if (!("CIPtr" in $$source)) { + this["CIPtr"] = {}; + } + if (!("EI" in $$source)) { + this["EI"] = {}; + } + if (!("EIPtr" in $$source)) { + this["EIPtr"] = {}; + } + if (!("EV" in $$source)) { + this["EV"] = {}; + } + if (!("EVPtr" in $$source)) { + this["EVPtr"] = {}; + } + if (!("EVP" in $$source)) { + this["EVP"] = {}; + } + if (!("EVPPtr" in $$source)) { + this["EVPPtr"] = {}; + } + if (!("EP" in $$source)) { + this["EP"] = {}; + } + if (!("EPPtr" in $$source)) { + this["EPPtr"] = {}; + } + if (!("EPP" in $$source)) { + this["EPP"] = {}; + } + if (!("EPPPtr" in $$source)) { + this["EPPPtr"] = {}; + } + if (!("ECI" in $$source)) { + this["ECI"] = {}; + } + if (!("ECIPtr" in $$source)) { + this["ECIPtr"] = {}; + } + if (!("EOI" in $$source)) { + this["EOI"] = {}; + } + if (!("EOIPtr" in $$source)) { + this["EOIPtr"] = {}; + } + if (!("WT" in $$source)) { + this["WT"] = {}; + } + if (!("WA" in $$source)) { + this["WA"] = {}; + } + if (!("ST" in $$source)) { + this["ST"] = {}; + } + if (!("SA" in $$source)) { + this["SA"] = {}; + } + if (!("IntT" in $$source)) { + this["IntT"] = {}; + } + if (!("IntA" in $$source)) { + this["IntA"] = {}; + } + if (!("VT" in $$source)) { + this["VT"] = {}; + } + if (!("VTPtr" in $$source)) { + this["VTPtr"] = {}; + } + if (!("VPT" in $$source)) { + this["VPT"] = {}; + } + if (!("VPTPtr" in $$source)) { + this["VPTPtr"] = {}; + } + if (!("VA" in $$source)) { + this["VA"] = {}; + } + if (!("VAPtr" in $$source)) { + this["VAPtr"] = {}; + } + if (!("VPA" in $$source)) { + this["VPA"] = {}; + } + if (!("VPAPtr" in $$source)) { + this["VPAPtr"] = {}; + } + if (!("PT" in $$source)) { + this["PT"] = {}; + } + if (!("PTPtr" in $$source)) { + this["PTPtr"] = {}; + } + if (!("PPT" in $$source)) { + this["PPT"] = {}; + } + if (!("PPTPtr" in $$source)) { + this["PPTPtr"] = {}; + } + if (!("PA" in $$source)) { + this["PA"] = {}; + } + if (!("PAPtr" in $$source)) { + this["PAPtr"] = {}; + } + if (!("PPA" in $$source)) { + this["PPA"] = {}; + } + if (!("PPAPtr" in $$source)) { + this["PPAPtr"] = {}; + } + if (!("IT" in $$source)) { + this["IT"] = {}; + } + if (!("ITPtr" in $$source)) { + this["ITPtr"] = {}; + } + if (!("IPT" in $$source)) { + this["IPT"] = {}; + } + if (!("IPTPtr" in $$source)) { + this["IPTPtr"] = {}; + } + if (!("IA" in $$source)) { + this["IA"] = {}; + } + if (!("IAPtr" in $$source)) { + this["IAPtr"] = {}; + } + if (!("IPA" in $$source)) { + this["IPA"] = {}; + } + if (!("IPAPtr" in $$source)) { + this["IPAPtr"] = {}; + } + if (!("TPR" in $$source)) { + this["TPR"] = {}; + } + if (!("TPRPtr" in $$source)) { + this["TPRPtr"] = {}; + } + if (!("TPS" in $$source)) { + this["TPS"] = {}; + } + if (!("TPSPtr" in $$source)) { + this["TPSPtr"] = {}; + } + if (!("TPT" in $$source)) { + this["TPT"] = {}; + } + if (!("TPTPtr" in $$source)) { + this["TPTPtr"] = {}; + } + if (!("TPU" in $$source)) { + this["TPU"] = {}; + } + if (!("TPUPtr" in $$source)) { + this["TPUPtr"] = {}; + } + if (!("TPV" in $$source)) { + this["TPV"] = {}; + } + if (!("TPVPtr" in $$source)) { + this["TPVPtr"] = {}; + } + if (!("TPW" in $$source)) { + this["TPW"] = {}; + } + if (!("TPWPtr" in $$source)) { + this["TPWPtr"] = {}; + } + if (!("TPX" in $$source)) { + this["TPX"] = {}; + } + if (!("TPXPtr" in $$source)) { + this["TPXPtr"] = {}; + } + if (!("TPY" in $$source)) { + this["TPY"] = {}; + } + if (!("TPYPtr" in $$source)) { + this["TPYPtr"] = {}; + } + if (!("TPZ" in $$source)) { + this["TPZ"] = {}; + } + if (!("TPZPtr" in $$source)) { + this["TPZPtr"] = {}; + } + if (!("GAR" in $$source)) { + this["GAR"] = {}; + } + if (!("GARPtr" in $$source)) { + this["GARPtr"] = {}; + } + if (!("GAS" in $$source)) { + this["GAS"] = {}; + } + if (!("GASPtr" in $$source)) { + this["GASPtr"] = {}; + } + if (!("GAT" in $$source)) { + this["GAT"] = {}; + } + if (!("GATPtr" in $$source)) { + this["GATPtr"] = {}; + } + if (!("GAU" in $$source)) { + this["GAU"] = {}; + } + if (!("GAUPtr" in $$source)) { + this["GAUPtr"] = {}; + } + if (!("GAV" in $$source)) { + this["GAV"] = {}; + } + if (!("GAVPtr" in $$source)) { + this["GAVPtr"] = {}; + } + if (!("GAW" in $$source)) { + this["GAW"] = {}; + } + if (!("GAWPtr" in $$source)) { + this["GAWPtr"] = {}; + } + if (!("GAX" in $$source)) { + this["GAX"] = {}; + } + if (!("GAXPtr" in $$source)) { + this["GAXPtr"] = {}; + } + if (!("GAY" in $$source)) { + this["GAY"] = {}; + } + if (!("GAYPtr" in $$source)) { + this["GAYPtr"] = {}; + } + if (!("GAZ" in $$source)) { + this["GAZ"] = {}; + } + if (!("GAZPtr" in $$source)) { + this["GAZPtr"] = {}; + } + if (!("GACi" in $$source)) { + this["GACi"] = {}; + } + if (!("GACV" in $$source)) { + this["GACV"] = {}; + } + if (!("GACP" in $$source)) { + this["GACP"] = {}; + } + if (!("GACiPtr" in $$source)) { + this["GACiPtr"] = {}; + } + if (!("GACVPtr" in $$source)) { + this["GACVPtr"] = {}; + } + if (!("GACPPtr" in $$source)) { + this["GACPPtr"] = {}; + } + if (!("GABi" in $$source)) { + this["GABi"] = {}; + } + if (!("GABs" in $$source)) { + this["GABs"] = {}; + } + if (!("GABiPtr" in $$source)) { + this["GABiPtr"] = {}; + } + if (!("GABT" in $$source)) { + this["GABT"] = {}; + } + if (!("GABTPtr" in $$source)) { + this["GABTPtr"] = {}; + } + if (!("GAGT" in $$source)) { + this["GAGT"] = {}; + } + if (!("GAGTPtr" in $$source)) { + this["GAGTPtr"] = {}; + } + if (!("GANBV" in $$source)) { + this["GANBV"] = {}; + } + if (!("GANBP" in $$source)) { + this["GANBP"] = {}; + } + if (!("GANBVPtr" in $$source)) { + this["GANBVPtr"] = {}; + } + if (!("GANBPPtr" in $$source)) { + this["GANBPPtr"] = {}; + } + if (!("GAPlV1" in $$source)) { + this["GAPlV1"] = {}; + } + if (!("GAPlV2" in $$source)) { + this["GAPlV2"] = {}; + } + if (!("GAPlP1" in $$source)) { + this["GAPlP1"] = {}; + } + if (!("GAPlP2" in $$source)) { + this["GAPlP2"] = {}; + } + if (!("GAPlVPtr" in $$source)) { + this["GAPlVPtr"] = {}; + } + if (!("GAPlPPtr" in $$source)) { + this["GAPlPPtr"] = {}; + } + if (!("GAMi" in $$source)) { + this["GAMi"] = {}; + } + if (!("GAMS" in $$source)) { + this["GAMS"] = {}; + } + if (!("GAMV" in $$source)) { + this["GAMV"] = {}; + } + if (!("GAMSPtr" in $$source)) { + this["GAMSPtr"] = {}; + } + if (!("GAMVPtr" in $$source)) { + this["GAMVPtr"] = {}; + } + if (!("GAII" in $$source)) { + this["GAII"] = {}; + } + if (!("GAIV" in $$source)) { + this["GAIV"] = {}; + } + if (!("GAIP" in $$source)) { + this["GAIP"] = {}; + } + if (!("GAIIPtr" in $$source)) { + this["GAIIPtr"] = {}; + } + if (!("GAIVPtr" in $$source)) { + this["GAIVPtr"] = {}; + } + if (!("GAIPPtr" in $$source)) { + this["GAIPPtr"] = {}; + } + if (!("GAPrV" in $$source)) { + this["GAPrV"] = {}; + } + if (!("GAPrP" in $$source)) { + this["GAPrP"] = {}; + } + if (!("GAPrVPtr" in $$source)) { + this["GAPrVPtr"] = {}; + } + if (!("GAPrPPtr" in $$source)) { + this["GAPrPPtr"] = {}; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class Maps. + */ + static createFrom($$createParamR: (source: any) => R, $$createParamS: (source: any) => S, $$createParamT: (source: any) => T, $$createParamU: (source: any) => U, $$createParamV: (source: any) => V, $$createParamW: (source: any) => W, $$createParamX: (source: any) => X, $$createParamY: (source: any) => Y, $$createParamZ: (source: any) => Z): ($$source?: any) => Maps { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType3; + const $$createField4_0 = $$createType4; + const $$createField5_0 = $$createType5; + const $$createField6_0 = $$createType6; + const $$createField7_0 = $$createType7; + const $$createField8_0 = $$createType8; + const $$createField9_0 = $$createType9; + const $$createField10_0 = $$createType10; + const $$createField11_0 = $$createType11; + const $$createField12_0 = $$createType12; + const $$createField13_0 = $$createType13; + const $$createField14_0 = $$createType14; + const $$createField15_0 = $$createType15; + const $$createField16_0 = $$createType16; + const $$createField17_0 = $$createType17; + const $$createField18_0 = $$createType18; + const $$createField19_0 = $$createType19; + const $$createField20_0 = $$createType20; + const $$createField21_0 = $$createType21; + const $$createField22_0 = $$createType22; + const $$createField23_0 = $$createType23; + const $$createField24_0 = $$createType24; + const $$createField25_0 = $$createType25; + const $$createField26_0 = $$createType26; + const $$createField27_0 = $$createType27; + const $$createField28_0 = $$createType28; + const $$createField29_0 = $$createType29; + const $$createField30_0 = $$createType30; + const $$createField31_0 = $$createType31; + const $$createField32_0 = $$createType32; + const $$createField33_0 = $$createType33; + const $$createField34_0 = $$createType34; + const $$createField35_0 = $$createType35; + const $$createField36_0 = $$createType36; + const $$createField37_0 = $$createType37; + const $$createField38_0 = $$createType38; + const $$createField39_0 = $$createType39; + const $$createField40_0 = $$createType40; + const $$createField41_0 = $$createType41; + const $$createField42_0 = $$createType0; + const $$createField43_0 = $$createType42; + const $$createField44_0 = $$createType7; + const $$createField45_0 = $$createType43; + const $$createField46_0 = $$createType1; + const $$createField47_0 = $$createType44; + const $$createField48_0 = $$createType45; + const $$createField49_0 = $$createType46; + const $$createField50_0 = $$createType47; + const $$createField51_0 = $$createType15; + const $$createField52_0 = $$createType16; + const $$createField53_0 = $$createType16; + const $$createField54_0 = $$createType48; + const $$createField55_0 = $$createType49; + const $$createField56_0 = $$createType50; + const $$createField57_0 = $$createType51; + const $$createField58_0 = $$createType52; + const $$createField59_0 = $$createType17; + const $$createField60_0 = $$createType18; + const $$createField61_0 = $$createType18; + const $$createField62_0 = $$createType53; + const $$createField63_0 = $$createType54; + const $$createField64_0 = $$createType55; + const $$createField65_0 = $$createType56; + const $$createField66_0 = $$createType57; + const $$createField67_0 = $$createType23; + const $$createField68_0 = $$createType24; + const $$createField69_0 = $$createType24; + const $$createField70_0 = $$createType58; + const $$createField71_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField72_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField73_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField74_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField75_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField76_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField77_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField78_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField79_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField80_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField81_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField82_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField83_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField84_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField85_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField86_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField87_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField88_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField89_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField90_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField91_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField92_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField93_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField94_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField95_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField96_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField97_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField98_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField99_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField100_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField101_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField102_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField103_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField104_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField105_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField106_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField107_0 = $$createType1; + const $$createField108_0 = $$createType15; + const $$createField109_0 = $$createType17; + const $$createField110_0 = $$createType8; + const $$createField111_0 = $$createType16; + const $$createField112_0 = $$createType18; + const $$createField113_0 = $$createType1; + const $$createField114_0 = $$createType7; + const $$createField115_0 = $$createType8; + const $$createField116_0 = $$createType77; + const $$createField117_0 = $$createType78; + const $$createField118_0 = $$createType15; + const $$createField119_0 = $$createType16; + const $$createField120_0 = $$createType15; + const $$createField121_0 = $$createType18; + const $$createField122_0 = $$createType16; + const $$createField123_0 = $$createType53; + const $$createField124_0 = $$createType15; + const $$createField125_0 = $$createType16; + const $$createField126_0 = $$createType17; + const $$createField127_0 = $$createType18; + const $$createField128_0 = $$createType16; + const $$createField129_0 = $$createType18; + const $$createField130_0 = $$createType2; + const $$createField131_0 = $$createType42; + const $$createField132_0 = $$createType15; + const $$createField133_0 = $$createType79; + const $$createField134_0 = $$createType16; + const $$createField135_0 = $$createType23; + const $$createField136_0 = $$createType15; + const $$createField137_0 = $$createType18; + const $$createField138_0 = $$createType24; + const $$createField139_0 = $$createType16; + const $$createField140_0 = $$createType53; + const $$createField141_0 = $$createType16; + const $$createField142_0 = $$createType18; + const $$createField143_0 = $$createType48; + const $$createField144_0 = $$createType53; + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Bool" in $$parsedSource) { + $$parsedSource["Bool"] = $$createField0_0($$parsedSource["Bool"]); + } + if ("Int" in $$parsedSource) { + $$parsedSource["Int"] = $$createField1_0($$parsedSource["Int"]); + } + if ("Uint" in $$parsedSource) { + $$parsedSource["Uint"] = $$createField2_0($$parsedSource["Uint"]); + } + if ("Float" in $$parsedSource) { + $$parsedSource["Float"] = $$createField3_0($$parsedSource["Float"]); + } + if ("Complex" in $$parsedSource) { + $$parsedSource["Complex"] = $$createField4_0($$parsedSource["Complex"]); + } + if ("Byte" in $$parsedSource) { + $$parsedSource["Byte"] = $$createField5_0($$parsedSource["Byte"]); + } + if ("Rune" in $$parsedSource) { + $$parsedSource["Rune"] = $$createField6_0($$parsedSource["Rune"]); + } + if ("String" in $$parsedSource) { + $$parsedSource["String"] = $$createField7_0($$parsedSource["String"]); + } + if ("IntPtr" in $$parsedSource) { + $$parsedSource["IntPtr"] = $$createField8_0($$parsedSource["IntPtr"]); + } + if ("UintPtr" in $$parsedSource) { + $$parsedSource["UintPtr"] = $$createField9_0($$parsedSource["UintPtr"]); + } + if ("FloatPtr" in $$parsedSource) { + $$parsedSource["FloatPtr"] = $$createField10_0($$parsedSource["FloatPtr"]); + } + if ("ComplexPtr" in $$parsedSource) { + $$parsedSource["ComplexPtr"] = $$createField11_0($$parsedSource["ComplexPtr"]); + } + if ("StringPtr" in $$parsedSource) { + $$parsedSource["StringPtr"] = $$createField12_0($$parsedSource["StringPtr"]); + } + if ("NTM" in $$parsedSource) { + $$parsedSource["NTM"] = $$createField13_0($$parsedSource["NTM"]); + } + if ("NTMPtr" in $$parsedSource) { + $$parsedSource["NTMPtr"] = $$createField14_0($$parsedSource["NTMPtr"]); + } + if ("VTM" in $$parsedSource) { + $$parsedSource["VTM"] = $$createField15_0($$parsedSource["VTM"]); + } + if ("VTMPtr" in $$parsedSource) { + $$parsedSource["VTMPtr"] = $$createField16_0($$parsedSource["VTMPtr"]); + } + if ("PTM" in $$parsedSource) { + $$parsedSource["PTM"] = $$createField17_0($$parsedSource["PTM"]); + } + if ("PTMPtr" in $$parsedSource) { + $$parsedSource["PTMPtr"] = $$createField18_0($$parsedSource["PTMPtr"]); + } + if ("JTM" in $$parsedSource) { + $$parsedSource["JTM"] = $$createField19_0($$parsedSource["JTM"]); + } + if ("JTMPtr" in $$parsedSource) { + $$parsedSource["JTMPtr"] = $$createField20_0($$parsedSource["JTMPtr"]); + } + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField21_0($$parsedSource["A"]); + } + if ("APtr" in $$parsedSource) { + $$parsedSource["APtr"] = $$createField22_0($$parsedSource["APtr"]); + } + if ("TM" in $$parsedSource) { + $$parsedSource["TM"] = $$createField23_0($$parsedSource["TM"]); + } + if ("TMPtr" in $$parsedSource) { + $$parsedSource["TMPtr"] = $$createField24_0($$parsedSource["TMPtr"]); + } + if ("CI" in $$parsedSource) { + $$parsedSource["CI"] = $$createField25_0($$parsedSource["CI"]); + } + if ("CIPtr" in $$parsedSource) { + $$parsedSource["CIPtr"] = $$createField26_0($$parsedSource["CIPtr"]); + } + if ("EI" in $$parsedSource) { + $$parsedSource["EI"] = $$createField27_0($$parsedSource["EI"]); + } + if ("EIPtr" in $$parsedSource) { + $$parsedSource["EIPtr"] = $$createField28_0($$parsedSource["EIPtr"]); + } + if ("EV" in $$parsedSource) { + $$parsedSource["EV"] = $$createField29_0($$parsedSource["EV"]); + } + if ("EVPtr" in $$parsedSource) { + $$parsedSource["EVPtr"] = $$createField30_0($$parsedSource["EVPtr"]); + } + if ("EVP" in $$parsedSource) { + $$parsedSource["EVP"] = $$createField31_0($$parsedSource["EVP"]); + } + if ("EVPPtr" in $$parsedSource) { + $$parsedSource["EVPPtr"] = $$createField32_0($$parsedSource["EVPPtr"]); + } + if ("EP" in $$parsedSource) { + $$parsedSource["EP"] = $$createField33_0($$parsedSource["EP"]); + } + if ("EPPtr" in $$parsedSource) { + $$parsedSource["EPPtr"] = $$createField34_0($$parsedSource["EPPtr"]); + } + if ("EPP" in $$parsedSource) { + $$parsedSource["EPP"] = $$createField35_0($$parsedSource["EPP"]); + } + if ("EPPPtr" in $$parsedSource) { + $$parsedSource["EPPPtr"] = $$createField36_0($$parsedSource["EPPPtr"]); + } + if ("ECI" in $$parsedSource) { + $$parsedSource["ECI"] = $$createField37_0($$parsedSource["ECI"]); + } + if ("ECIPtr" in $$parsedSource) { + $$parsedSource["ECIPtr"] = $$createField38_0($$parsedSource["ECIPtr"]); + } + if ("EOI" in $$parsedSource) { + $$parsedSource["EOI"] = $$createField39_0($$parsedSource["EOI"]); + } + if ("EOIPtr" in $$parsedSource) { + $$parsedSource["EOIPtr"] = $$createField40_0($$parsedSource["EOIPtr"]); + } + if ("WT" in $$parsedSource) { + $$parsedSource["WT"] = $$createField41_0($$parsedSource["WT"]); + } + if ("WA" in $$parsedSource) { + $$parsedSource["WA"] = $$createField42_0($$parsedSource["WA"]); + } + if ("ST" in $$parsedSource) { + $$parsedSource["ST"] = $$createField43_0($$parsedSource["ST"]); + } + if ("SA" in $$parsedSource) { + $$parsedSource["SA"] = $$createField44_0($$parsedSource["SA"]); + } + if ("IntT" in $$parsedSource) { + $$parsedSource["IntT"] = $$createField45_0($$parsedSource["IntT"]); + } + if ("IntA" in $$parsedSource) { + $$parsedSource["IntA"] = $$createField46_0($$parsedSource["IntA"]); + } + if ("VT" in $$parsedSource) { + $$parsedSource["VT"] = $$createField47_0($$parsedSource["VT"]); + } + if ("VTPtr" in $$parsedSource) { + $$parsedSource["VTPtr"] = $$createField48_0($$parsedSource["VTPtr"]); + } + if ("VPT" in $$parsedSource) { + $$parsedSource["VPT"] = $$createField49_0($$parsedSource["VPT"]); + } + if ("VPTPtr" in $$parsedSource) { + $$parsedSource["VPTPtr"] = $$createField50_0($$parsedSource["VPTPtr"]); + } + if ("VA" in $$parsedSource) { + $$parsedSource["VA"] = $$createField51_0($$parsedSource["VA"]); + } + if ("VAPtr" in $$parsedSource) { + $$parsedSource["VAPtr"] = $$createField52_0($$parsedSource["VAPtr"]); + } + if ("VPA" in $$parsedSource) { + $$parsedSource["VPA"] = $$createField53_0($$parsedSource["VPA"]); + } + if ("VPAPtr" in $$parsedSource) { + $$parsedSource["VPAPtr"] = $$createField54_0($$parsedSource["VPAPtr"]); + } + if ("PT" in $$parsedSource) { + $$parsedSource["PT"] = $$createField55_0($$parsedSource["PT"]); + } + if ("PTPtr" in $$parsedSource) { + $$parsedSource["PTPtr"] = $$createField56_0($$parsedSource["PTPtr"]); + } + if ("PPT" in $$parsedSource) { + $$parsedSource["PPT"] = $$createField57_0($$parsedSource["PPT"]); + } + if ("PPTPtr" in $$parsedSource) { + $$parsedSource["PPTPtr"] = $$createField58_0($$parsedSource["PPTPtr"]); + } + if ("PA" in $$parsedSource) { + $$parsedSource["PA"] = $$createField59_0($$parsedSource["PA"]); + } + if ("PAPtr" in $$parsedSource) { + $$parsedSource["PAPtr"] = $$createField60_0($$parsedSource["PAPtr"]); + } + if ("PPA" in $$parsedSource) { + $$parsedSource["PPA"] = $$createField61_0($$parsedSource["PPA"]); + } + if ("PPAPtr" in $$parsedSource) { + $$parsedSource["PPAPtr"] = $$createField62_0($$parsedSource["PPAPtr"]); + } + if ("IT" in $$parsedSource) { + $$parsedSource["IT"] = $$createField63_0($$parsedSource["IT"]); + } + if ("ITPtr" in $$parsedSource) { + $$parsedSource["ITPtr"] = $$createField64_0($$parsedSource["ITPtr"]); + } + if ("IPT" in $$parsedSource) { + $$parsedSource["IPT"] = $$createField65_0($$parsedSource["IPT"]); + } + if ("IPTPtr" in $$parsedSource) { + $$parsedSource["IPTPtr"] = $$createField66_0($$parsedSource["IPTPtr"]); + } + if ("IA" in $$parsedSource) { + $$parsedSource["IA"] = $$createField67_0($$parsedSource["IA"]); + } + if ("IAPtr" in $$parsedSource) { + $$parsedSource["IAPtr"] = $$createField68_0($$parsedSource["IAPtr"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField69_0($$parsedSource["IPA"]); + } + if ("IPAPtr" in $$parsedSource) { + $$parsedSource["IPAPtr"] = $$createField70_0($$parsedSource["IPAPtr"]); + } + if ("TPR" in $$parsedSource) { + $$parsedSource["TPR"] = $$createField71_0($$parsedSource["TPR"]); + } + if ("TPRPtr" in $$parsedSource) { + $$parsedSource["TPRPtr"] = $$createField72_0($$parsedSource["TPRPtr"]); + } + if ("TPS" in $$parsedSource) { + $$parsedSource["TPS"] = $$createField73_0($$parsedSource["TPS"]); + } + if ("TPSPtr" in $$parsedSource) { + $$parsedSource["TPSPtr"] = $$createField74_0($$parsedSource["TPSPtr"]); + } + if ("TPT" in $$parsedSource) { + $$parsedSource["TPT"] = $$createField75_0($$parsedSource["TPT"]); + } + if ("TPTPtr" in $$parsedSource) { + $$parsedSource["TPTPtr"] = $$createField76_0($$parsedSource["TPTPtr"]); + } + if ("TPU" in $$parsedSource) { + $$parsedSource["TPU"] = $$createField77_0($$parsedSource["TPU"]); + } + if ("TPUPtr" in $$parsedSource) { + $$parsedSource["TPUPtr"] = $$createField78_0($$parsedSource["TPUPtr"]); + } + if ("TPV" in $$parsedSource) { + $$parsedSource["TPV"] = $$createField79_0($$parsedSource["TPV"]); + } + if ("TPVPtr" in $$parsedSource) { + $$parsedSource["TPVPtr"] = $$createField80_0($$parsedSource["TPVPtr"]); + } + if ("TPW" in $$parsedSource) { + $$parsedSource["TPW"] = $$createField81_0($$parsedSource["TPW"]); + } + if ("TPWPtr" in $$parsedSource) { + $$parsedSource["TPWPtr"] = $$createField82_0($$parsedSource["TPWPtr"]); + } + if ("TPX" in $$parsedSource) { + $$parsedSource["TPX"] = $$createField83_0($$parsedSource["TPX"]); + } + if ("TPXPtr" in $$parsedSource) { + $$parsedSource["TPXPtr"] = $$createField84_0($$parsedSource["TPXPtr"]); + } + if ("TPY" in $$parsedSource) { + $$parsedSource["TPY"] = $$createField85_0($$parsedSource["TPY"]); + } + if ("TPYPtr" in $$parsedSource) { + $$parsedSource["TPYPtr"] = $$createField86_0($$parsedSource["TPYPtr"]); + } + if ("TPZ" in $$parsedSource) { + $$parsedSource["TPZ"] = $$createField87_0($$parsedSource["TPZ"]); + } + if ("TPZPtr" in $$parsedSource) { + $$parsedSource["TPZPtr"] = $$createField88_0($$parsedSource["TPZPtr"]); + } + if ("GAR" in $$parsedSource) { + $$parsedSource["GAR"] = $$createField89_0($$parsedSource["GAR"]); + } + if ("GARPtr" in $$parsedSource) { + $$parsedSource["GARPtr"] = $$createField90_0($$parsedSource["GARPtr"]); + } + if ("GAS" in $$parsedSource) { + $$parsedSource["GAS"] = $$createField91_0($$parsedSource["GAS"]); + } + if ("GASPtr" in $$parsedSource) { + $$parsedSource["GASPtr"] = $$createField92_0($$parsedSource["GASPtr"]); + } + if ("GAT" in $$parsedSource) { + $$parsedSource["GAT"] = $$createField93_0($$parsedSource["GAT"]); + } + if ("GATPtr" in $$parsedSource) { + $$parsedSource["GATPtr"] = $$createField94_0($$parsedSource["GATPtr"]); + } + if ("GAU" in $$parsedSource) { + $$parsedSource["GAU"] = $$createField95_0($$parsedSource["GAU"]); + } + if ("GAUPtr" in $$parsedSource) { + $$parsedSource["GAUPtr"] = $$createField96_0($$parsedSource["GAUPtr"]); + } + if ("GAV" in $$parsedSource) { + $$parsedSource["GAV"] = $$createField97_0($$parsedSource["GAV"]); + } + if ("GAVPtr" in $$parsedSource) { + $$parsedSource["GAVPtr"] = $$createField98_0($$parsedSource["GAVPtr"]); + } + if ("GAW" in $$parsedSource) { + $$parsedSource["GAW"] = $$createField99_0($$parsedSource["GAW"]); + } + if ("GAWPtr" in $$parsedSource) { + $$parsedSource["GAWPtr"] = $$createField100_0($$parsedSource["GAWPtr"]); + } + if ("GAX" in $$parsedSource) { + $$parsedSource["GAX"] = $$createField101_0($$parsedSource["GAX"]); + } + if ("GAXPtr" in $$parsedSource) { + $$parsedSource["GAXPtr"] = $$createField102_0($$parsedSource["GAXPtr"]); + } + if ("GAY" in $$parsedSource) { + $$parsedSource["GAY"] = $$createField103_0($$parsedSource["GAY"]); + } + if ("GAYPtr" in $$parsedSource) { + $$parsedSource["GAYPtr"] = $$createField104_0($$parsedSource["GAYPtr"]); + } + if ("GAZ" in $$parsedSource) { + $$parsedSource["GAZ"] = $$createField105_0($$parsedSource["GAZ"]); + } + if ("GAZPtr" in $$parsedSource) { + $$parsedSource["GAZPtr"] = $$createField106_0($$parsedSource["GAZPtr"]); + } + if ("GACi" in $$parsedSource) { + $$parsedSource["GACi"] = $$createField107_0($$parsedSource["GACi"]); + } + if ("GACV" in $$parsedSource) { + $$parsedSource["GACV"] = $$createField108_0($$parsedSource["GACV"]); + } + if ("GACP" in $$parsedSource) { + $$parsedSource["GACP"] = $$createField109_0($$parsedSource["GACP"]); + } + if ("GACiPtr" in $$parsedSource) { + $$parsedSource["GACiPtr"] = $$createField110_0($$parsedSource["GACiPtr"]); + } + if ("GACVPtr" in $$parsedSource) { + $$parsedSource["GACVPtr"] = $$createField111_0($$parsedSource["GACVPtr"]); + } + if ("GACPPtr" in $$parsedSource) { + $$parsedSource["GACPPtr"] = $$createField112_0($$parsedSource["GACPPtr"]); + } + if ("GABi" in $$parsedSource) { + $$parsedSource["GABi"] = $$createField113_0($$parsedSource["GABi"]); + } + if ("GABs" in $$parsedSource) { + $$parsedSource["GABs"] = $$createField114_0($$parsedSource["GABs"]); + } + if ("GABiPtr" in $$parsedSource) { + $$parsedSource["GABiPtr"] = $$createField115_0($$parsedSource["GABiPtr"]); + } + if ("GABT" in $$parsedSource) { + $$parsedSource["GABT"] = $$createField116_0($$parsedSource["GABT"]); + } + if ("GABTPtr" in $$parsedSource) { + $$parsedSource["GABTPtr"] = $$createField117_0($$parsedSource["GABTPtr"]); + } + if ("GAGT" in $$parsedSource) { + $$parsedSource["GAGT"] = $$createField118_0($$parsedSource["GAGT"]); + } + if ("GAGTPtr" in $$parsedSource) { + $$parsedSource["GAGTPtr"] = $$createField119_0($$parsedSource["GAGTPtr"]); + } + if ("GANBV" in $$parsedSource) { + $$parsedSource["GANBV"] = $$createField120_0($$parsedSource["GANBV"]); + } + if ("GANBP" in $$parsedSource) { + $$parsedSource["GANBP"] = $$createField121_0($$parsedSource["GANBP"]); + } + if ("GANBVPtr" in $$parsedSource) { + $$parsedSource["GANBVPtr"] = $$createField122_0($$parsedSource["GANBVPtr"]); + } + if ("GANBPPtr" in $$parsedSource) { + $$parsedSource["GANBPPtr"] = $$createField123_0($$parsedSource["GANBPPtr"]); + } + if ("GAPlV1" in $$parsedSource) { + $$parsedSource["GAPlV1"] = $$createField124_0($$parsedSource["GAPlV1"]); + } + if ("GAPlV2" in $$parsedSource) { + $$parsedSource["GAPlV2"] = $$createField125_0($$parsedSource["GAPlV2"]); + } + if ("GAPlP1" in $$parsedSource) { + $$parsedSource["GAPlP1"] = $$createField126_0($$parsedSource["GAPlP1"]); + } + if ("GAPlP2" in $$parsedSource) { + $$parsedSource["GAPlP2"] = $$createField127_0($$parsedSource["GAPlP2"]); + } + if ("GAPlVPtr" in $$parsedSource) { + $$parsedSource["GAPlVPtr"] = $$createField128_0($$parsedSource["GAPlVPtr"]); + } + if ("GAPlPPtr" in $$parsedSource) { + $$parsedSource["GAPlPPtr"] = $$createField129_0($$parsedSource["GAPlPPtr"]); + } + if ("GAMi" in $$parsedSource) { + $$parsedSource["GAMi"] = $$createField130_0($$parsedSource["GAMi"]); + } + if ("GAMS" in $$parsedSource) { + $$parsedSource["GAMS"] = $$createField131_0($$parsedSource["GAMS"]); + } + if ("GAMV" in $$parsedSource) { + $$parsedSource["GAMV"] = $$createField132_0($$parsedSource["GAMV"]); + } + if ("GAMSPtr" in $$parsedSource) { + $$parsedSource["GAMSPtr"] = $$createField133_0($$parsedSource["GAMSPtr"]); + } + if ("GAMVPtr" in $$parsedSource) { + $$parsedSource["GAMVPtr"] = $$createField134_0($$parsedSource["GAMVPtr"]); + } + if ("GAII" in $$parsedSource) { + $$parsedSource["GAII"] = $$createField135_0($$parsedSource["GAII"]); + } + if ("GAIV" in $$parsedSource) { + $$parsedSource["GAIV"] = $$createField136_0($$parsedSource["GAIV"]); + } + if ("GAIP" in $$parsedSource) { + $$parsedSource["GAIP"] = $$createField137_0($$parsedSource["GAIP"]); + } + if ("GAIIPtr" in $$parsedSource) { + $$parsedSource["GAIIPtr"] = $$createField138_0($$parsedSource["GAIIPtr"]); + } + if ("GAIVPtr" in $$parsedSource) { + $$parsedSource["GAIVPtr"] = $$createField139_0($$parsedSource["GAIVPtr"]); + } + if ("GAIPPtr" in $$parsedSource) { + $$parsedSource["GAIPPtr"] = $$createField140_0($$parsedSource["GAIPPtr"]); + } + if ("GAPrV" in $$parsedSource) { + $$parsedSource["GAPrV"] = $$createField141_0($$parsedSource["GAPrV"]); + } + if ("GAPrP" in $$parsedSource) { + $$parsedSource["GAPrP"] = $$createField142_0($$parsedSource["GAPrP"]); + } + if ("GAPrVPtr" in $$parsedSource) { + $$parsedSource["GAPrVPtr"] = $$createField143_0($$parsedSource["GAPrVPtr"]); + } + if ("GAPrPPtr" in $$parsedSource) { + $$parsedSource["GAPrPPtr"] = $$createField144_0($$parsedSource["GAPrPPtr"]); + } + return new Maps($$parsedSource as Partial>); + }; + } +} + +export type MixedCstrAlias = X; + +export type NonBasicCstrAlias = V; + +export type PointableCstrAlias = W; + +export type PointerAlias = PointerTextMarshaler; + +export type PointerTextMarshaler = string; + +export type StringAlias = string; + +export type StringType = string; + +export type ValueAlias = ValueTextMarshaler; + +export type ValueTextMarshaler = string; + +// Private type creation functions +const $$createType0 = $Create.Map($Create.Any, $Create.Any); +const $$createType1 = $Create.Map($Create.Any, $Create.Any); +const $$createType2 = $Create.Map($Create.Any, $Create.Any); +const $$createType3 = $Create.Map($Create.Any, $Create.Any); +const $$createType4 = $Create.Map($Create.Any, $Create.Any); +const $$createType5 = $Create.Map($Create.Any, $Create.Any); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Map($Create.Any, $Create.Any); +const $$createType8 = $Create.Map($Create.Any, $Create.Any); +const $$createType9 = $Create.Map($Create.Any, $Create.Any); +const $$createType10 = $Create.Map($Create.Any, $Create.Any); +const $$createType11 = $Create.Map($Create.Any, $Create.Any); +const $$createType12 = $Create.Map($Create.Any, $Create.Any); +const $$createType13 = $Create.Map($Create.Any, $Create.Any); +const $$createType14 = $Create.Map($Create.Any, $Create.Any); +const $$createType15 = $Create.Map($Create.Any, $Create.Any); +const $$createType16 = $Create.Map($Create.Any, $Create.Any); +const $$createType17 = $Create.Map($Create.Any, $Create.Any); +const $$createType18 = $Create.Map($Create.Any, $Create.Any); +const $$createType19 = $Create.Map($Create.Any, $Create.Any); +const $$createType20 = $Create.Map($Create.Any, $Create.Any); +const $$createType21 = $Create.Map($Create.Any, $Create.Any); +const $$createType22 = $Create.Map($Create.Any, $Create.Any); +const $$createType23 = $Create.Map($Create.Any, $Create.Any); +const $$createType24 = $Create.Map($Create.Any, $Create.Any); +const $$createType25 = $Create.Map($Create.Any, $Create.Any); +const $$createType26 = $Create.Map($Create.Any, $Create.Any); +const $$createType27 = $Create.Map($Create.Any, $Create.Any); +const $$createType28 = $Create.Map($Create.Any, $Create.Any); +const $$createType29 = $Create.Map($Create.Any, $Create.Any); +const $$createType30 = $Create.Map($Create.Any, $Create.Any); +const $$createType31 = $Create.Map($Create.Any, $Create.Any); +const $$createType32 = $Create.Map($Create.Any, $Create.Any); +const $$createType33 = $Create.Map($Create.Any, $Create.Any); +const $$createType34 = $Create.Map($Create.Any, $Create.Any); +const $$createType35 = $Create.Map($Create.Any, $Create.Any); +const $$createType36 = $Create.Map($Create.Any, $Create.Any); +const $$createType37 = $Create.Map($Create.Any, $Create.Any); +const $$createType38 = $Create.Map($Create.Any, $Create.Any); +const $$createType39 = $Create.Map($Create.Any, $Create.Any); +const $$createType40 = $Create.Map($Create.Any, $Create.Any); +const $$createType41 = $Create.Map($Create.Any, $Create.Any); +const $$createType42 = $Create.Map($Create.Any, $Create.Any); +const $$createType43 = $Create.Map($Create.Any, $Create.Any); +const $$createType44 = $Create.Map($Create.Any, $Create.Any); +const $$createType45 = $Create.Map($Create.Any, $Create.Any); +const $$createType46 = $Create.Map($Create.Any, $Create.Any); +const $$createType47 = $Create.Map($Create.Any, $Create.Any); +const $$createType48 = $Create.Map($Create.Any, $Create.Any); +const $$createType49 = $Create.Map($Create.Any, $Create.Any); +const $$createType50 = $Create.Map($Create.Any, $Create.Any); +const $$createType51 = $Create.Map($Create.Any, $Create.Any); +const $$createType52 = $Create.Map($Create.Any, $Create.Any); +const $$createType53 = $Create.Map($Create.Any, $Create.Any); +const $$createType54 = $Create.Map($Create.Any, $Create.Any); +const $$createType55 = $Create.Map($Create.Any, $Create.Any); +const $$createType56 = $Create.Map($Create.Any, $Create.Any); +const $$createType57 = $Create.Map($Create.Any, $Create.Any); +const $$createType58 = $Create.Map($Create.Any, $Create.Any); +const $$createType59 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType60 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType61 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType62 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType63 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType64 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType65 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType66 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType67 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType68 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType69 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType70 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType71 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType72 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType73 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType74 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType75 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType76 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType77 = $Create.Map($Create.Any, $Create.Any); +const $$createType78 = $Create.Map($Create.Any, $Create.Any); +const $$createType79 = $Create.Map($Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts new file mode 100644 index 000000000..d62acda96 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>> { + return $Call.ByName("main.Service.Method").then(($result: any) => { + return $$createType0($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Maps.createFrom($Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts new file mode 100644 index 000000000..bbe49e32f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts @@ -0,0 +1,36 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Data, + ImplicitNonMarshaler, + NonMarshaler +} from "./models.js"; + +export type { + AliasJsonMarshaler, + AliasMarshaler, + AliasNonMarshaler, + AliasTextMarshaler, + ImplicitJsonButText, + ImplicitJsonMarshaler, + ImplicitMarshaler, + ImplicitNonJson, + ImplicitNonText, + ImplicitTextButJson, + ImplicitTextMarshaler, + PointerJsonMarshaler, + PointerMarshaler, + PointerTextMarshaler, + UnderlyingJsonMarshaler, + UnderlyingMarshaler, + UnderlyingTextMarshaler, + ValueJsonMarshaler, + ValueMarshaler, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts new file mode 100644 index 000000000..d8db23862 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts @@ -0,0 +1,545 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + */ +export type AliasJsonMarshaler = any; + +/** + * any + */ +export type AliasMarshaler = any; + +/** + * struct{} + */ +export interface AliasNonMarshaler { +} + +/** + * string + */ +export type AliasTextMarshaler = string; + +export class Data { + "NM": NonMarshaler; + + /** + * NonMarshaler | null + */ + "NMPtr": NonMarshaler | null; + "VJM": ValueJsonMarshaler; + + /** + * ValueJsonMarshaler | null + */ + "VJMPtr": ValueJsonMarshaler | null; + "PJM": PointerJsonMarshaler; + + /** + * PointerJsonMarshaler | null + */ + "PJMPtr": PointerJsonMarshaler | null; + "VTM": ValueTextMarshaler; + + /** + * ValueTextMarshaler | null + */ + "VTMPtr": ValueTextMarshaler | null; + "PTM": PointerTextMarshaler; + + /** + * PointerTextMarshaler | null + */ + "PTMPtr": PointerTextMarshaler | null; + "VM": ValueMarshaler; + + /** + * ValueMarshaler | null + */ + "VMPtr": ValueMarshaler | null; + "PM": PointerMarshaler; + + /** + * PointerMarshaler | null + */ + "PMPtr": PointerMarshaler | null; + "UJM": UnderlyingJsonMarshaler; + + /** + * UnderlyingJsonMarshaler | null + */ + "UJMPtr": UnderlyingJsonMarshaler | null; + "UTM": UnderlyingTextMarshaler; + + /** + * UnderlyingTextMarshaler | null + */ + "UTMPtr": UnderlyingTextMarshaler | null; + "UM": UnderlyingMarshaler; + + /** + * UnderlyingMarshaler | null + */ + "UMPtr": UnderlyingMarshaler | null; + + /** + * any + */ + "JM": any; + + /** + * any | null + */ + "JMPtr": any | null; + + /** + * string + */ + "TM": string; + + /** + * string | null + */ + "TMPtr": string | null; + + /** + * any + */ + "CJM": any; + + /** + * any | null + */ + "CJMPtr": any | null; + + /** + * string + */ + "CTM": string; + + /** + * string | null + */ + "CTMPtr": string | null; + + /** + * any + */ + "CM": any; + + /** + * any | null + */ + "CMPtr": any | null; + "ANM": AliasNonMarshaler; + + /** + * AliasNonMarshaler | null + */ + "ANMPtr": AliasNonMarshaler | null; + "AJM": AliasJsonMarshaler; + + /** + * AliasJsonMarshaler | null + */ + "AJMPtr": AliasJsonMarshaler | null; + "ATM": AliasTextMarshaler; + + /** + * AliasTextMarshaler | null + */ + "ATMPtr": AliasTextMarshaler | null; + "AM": AliasMarshaler; + + /** + * AliasMarshaler | null + */ + "AMPtr": AliasMarshaler | null; + "ImJM": ImplicitJsonMarshaler; + + /** + * ImplicitJsonMarshaler | null + */ + "ImJMPtr": ImplicitJsonMarshaler | null; + "ImTM": ImplicitTextMarshaler; + + /** + * ImplicitTextMarshaler | null + */ + "ImTMPtr": ImplicitTextMarshaler | null; + "ImM": ImplicitMarshaler; + + /** + * ImplicitMarshaler | null + */ + "ImMPtr": ImplicitMarshaler | null; + "ImNJ": ImplicitNonJson; + + /** + * ImplicitNonJson | null + */ + "ImNJPtr": ImplicitNonJson | null; + "ImNT": ImplicitNonText; + + /** + * ImplicitNonText | null + */ + "ImNTPtr": ImplicitNonText | null; + "ImNM": ImplicitNonMarshaler; + + /** + * ImplicitNonMarshaler | null + */ + "ImNMPtr": ImplicitNonMarshaler | null; + "ImJbT": ImplicitJsonButText; + + /** + * ImplicitJsonButText | null + */ + "ImJbTPtr": ImplicitJsonButText | null; + "ImTbJ": ImplicitTextButJson; + + /** + * ImplicitTextButJson | null + */ + "ImTbJPtr": ImplicitTextButJson | null; + + /** Creates a new Data instance. */ + constructor($$source: Partial = {}) { + if (!("NM" in $$source)) { + this["NM"] = (new NonMarshaler()); + } + if (!("NMPtr" in $$source)) { + this["NMPtr"] = null; + } + if (!("VJM" in $$source)) { + this["VJM"] = null; + } + if (!("VJMPtr" in $$source)) { + this["VJMPtr"] = null; + } + if (!("PJM" in $$source)) { + this["PJM"] = null; + } + if (!("PJMPtr" in $$source)) { + this["PJMPtr"] = null; + } + if (!("VTM" in $$source)) { + this["VTM"] = ""; + } + if (!("VTMPtr" in $$source)) { + this["VTMPtr"] = null; + } + if (!("PTM" in $$source)) { + this["PTM"] = ""; + } + if (!("PTMPtr" in $$source)) { + this["PTMPtr"] = null; + } + if (!("VM" in $$source)) { + this["VM"] = null; + } + if (!("VMPtr" in $$source)) { + this["VMPtr"] = null; + } + if (!("PM" in $$source)) { + this["PM"] = null; + } + if (!("PMPtr" in $$source)) { + this["PMPtr"] = null; + } + if (!("UJM" in $$source)) { + this["UJM"] = null; + } + if (!("UJMPtr" in $$source)) { + this["UJMPtr"] = null; + } + if (!("UTM" in $$source)) { + this["UTM"] = ""; + } + if (!("UTMPtr" in $$source)) { + this["UTMPtr"] = null; + } + if (!("UM" in $$source)) { + this["UM"] = null; + } + if (!("UMPtr" in $$source)) { + this["UMPtr"] = null; + } + if (!("JM" in $$source)) { + this["JM"] = null; + } + if (!("JMPtr" in $$source)) { + this["JMPtr"] = null; + } + if (!("TM" in $$source)) { + this["TM"] = ""; + } + if (!("TMPtr" in $$source)) { + this["TMPtr"] = null; + } + if (!("CJM" in $$source)) { + this["CJM"] = null; + } + if (!("CJMPtr" in $$source)) { + this["CJMPtr"] = null; + } + if (!("CTM" in $$source)) { + this["CTM"] = ""; + } + if (!("CTMPtr" in $$source)) { + this["CTMPtr"] = null; + } + if (!("CM" in $$source)) { + this["CM"] = null; + } + if (!("CMPtr" in $$source)) { + this["CMPtr"] = null; + } + if (!("ANM" in $$source)) { + this["ANM"] = {}; + } + if (!("ANMPtr" in $$source)) { + this["ANMPtr"] = null; + } + if (!("AJM" in $$source)) { + this["AJM"] = null; + } + if (!("AJMPtr" in $$source)) { + this["AJMPtr"] = null; + } + if (!("ATM" in $$source)) { + this["ATM"] = ""; + } + if (!("ATMPtr" in $$source)) { + this["ATMPtr"] = null; + } + if (!("AM" in $$source)) { + this["AM"] = null; + } + if (!("AMPtr" in $$source)) { + this["AMPtr"] = null; + } + if (!("ImJM" in $$source)) { + this["ImJM"] = null; + } + if (!("ImJMPtr" in $$source)) { + this["ImJMPtr"] = null; + } + if (!("ImTM" in $$source)) { + this["ImTM"] = ""; + } + if (!("ImTMPtr" in $$source)) { + this["ImTMPtr"] = null; + } + if (!("ImM" in $$source)) { + this["ImM"] = null; + } + if (!("ImMPtr" in $$source)) { + this["ImMPtr"] = null; + } + if (!("ImNJ" in $$source)) { + this["ImNJ"] = ""; + } + if (!("ImNJPtr" in $$source)) { + this["ImNJPtr"] = null; + } + if (!("ImNT" in $$source)) { + this["ImNT"] = null; + } + if (!("ImNTPtr" in $$source)) { + this["ImNTPtr"] = null; + } + if (!("ImNM" in $$source)) { + this["ImNM"] = (new ImplicitNonMarshaler()); + } + if (!("ImNMPtr" in $$source)) { + this["ImNMPtr"] = null; + } + if (!("ImJbT" in $$source)) { + this["ImJbT"] = null; + } + if (!("ImJbTPtr" in $$source)) { + this["ImJbTPtr"] = null; + } + if (!("ImTbJ" in $$source)) { + this["ImTbJ"] = null; + } + if (!("ImTbJPtr" in $$source)) { + this["ImTbJPtr"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Data instance from a string or object. + */ + static createFrom($$source: any = {}): Data { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField48_0 = $$createType2; + const $$createField49_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("NM" in $$parsedSource) { + $$parsedSource["NM"] = $$createField0_0($$parsedSource["NM"]); + } + if ("NMPtr" in $$parsedSource) { + $$parsedSource["NMPtr"] = $$createField1_0($$parsedSource["NMPtr"]); + } + if ("ImNM" in $$parsedSource) { + $$parsedSource["ImNM"] = $$createField48_0($$parsedSource["ImNM"]); + } + if ("ImNMPtr" in $$parsedSource) { + $$parsedSource["ImNMPtr"] = $$createField49_0($$parsedSource["ImNMPtr"]); + } + return new Data($$parsedSource as Partial); + } +} + +/** + * any + */ +export type ImplicitJsonButText = any; + +/** + * any + */ +export type ImplicitJsonMarshaler = any; + +/** + * any + */ +export type ImplicitMarshaler = any; + +/** + * string + */ +export type ImplicitNonJson = string; + +/** + * class{ Marshaler, TextMarshaler } + */ +export class ImplicitNonMarshaler { + "Marshaler": json$0.Marshaler; + "TextMarshaler": encoding$0.TextMarshaler; + + /** Creates a new ImplicitNonMarshaler instance. */ + constructor($$source: Partial = {}) { + if (!("Marshaler" in $$source)) { + this["Marshaler"] = null; + } + if (!("TextMarshaler" in $$source)) { + this["TextMarshaler"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new ImplicitNonMarshaler instance from a string or object. + */ + static createFrom($$source: any = {}): ImplicitNonMarshaler { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new ImplicitNonMarshaler($$parsedSource as Partial); + } +} + +/** + * any + */ +export type ImplicitNonText = any; + +/** + * any + */ +export type ImplicitTextButJson = any; + +/** + * string + */ +export type ImplicitTextMarshaler = string; + +/** + * class {} + */ +export class NonMarshaler { + + /** Creates a new NonMarshaler instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new NonMarshaler instance from a string or object. + */ + static createFrom($$source: any = {}): NonMarshaler { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new NonMarshaler($$parsedSource as Partial); + } +} + +/** + * any + */ +export type PointerJsonMarshaler = any; + +/** + * any + */ +export type PointerMarshaler = any; + +/** + * string + */ +export type PointerTextMarshaler = string; + +/** + * any + */ +export type UnderlyingJsonMarshaler = any; + +/** + * any + */ +export type UnderlyingMarshaler = any; + +/** + * string + */ +export type UnderlyingTextMarshaler = string; + +/** + * any + */ +export type ValueJsonMarshaler = any; + +/** + * any + */ +export type ValueMarshaler = any; + +/** + * string + */ +export type ValueTextMarshaler = string; + +// Private type creation functions +const $$createType0 = NonMarshaler.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = ImplicitNonMarshaler.createFrom; +const $$createType3 = $Create.Nullable($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts new file mode 100644 index 000000000..8e2af391b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Data> { + return $Call.ByName("main.Service.Method").then(($result: any) => { + return $$createType0($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Data.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts new file mode 100644 index 000000000..19d990dbf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +export { + HowDifferent, + Impersonator, + Person, + PrivatePerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts new file mode 100644 index 000000000..4cb328e1e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts @@ -0,0 +1,163 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + */ +export class HowDifferent { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": { [_: string]: How }[]; + + /** Creates a new HowDifferent instance. */ + constructor($$source: Partial> = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class HowDifferent. + */ + static createFrom($$createParamHow: (source: any) => How): ($$source?: any) => HowDifferent { + const $$createField1_0 = $$createType1($$createParamHow); + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new HowDifferent($$parsedSource as Partial>); + }; + } +} + +/** + * Impersonator gets their fields from other people. + */ +export const Impersonator = other$0.OtherPerson; + +/** + * Impersonator gets their fields from other people. + */ +export type Impersonator = other$0.OtherPerson; + +/** + * Person is not a number. + */ +export class Person { + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField1_0($$parsedSource["Friends"]); + } + return new Person($$parsedSource as Partial); + } +} + +export class personImpl { + /** + * Nickname conceals a person's identity. + */ + "Nickname": string; + + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; + + /** Creates a new personImpl instance. */ + constructor($$source: Partial = {}) { + if (!("Nickname" in $$source)) { + this["Nickname"] = ""; + } + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new personImpl instance from a string or object. + */ + static createFrom($$source: any = {}): personImpl { + const $$createField2_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField2_0($$parsedSource["Friends"]); + } + return new personImpl($$parsedSource as Partial); + } +} + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export const PrivatePerson = personImpl; + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export type PrivatePerson = personImpl; + +// Private type creation functions +const $$createType0 = ($$createParamHow: any) => $Create.Map($Create.Any, $$createParamHow); +const $$createType1 = ($$createParamHow: any) => $Create.Array($$createType0($$createParamHow)); +const $$createType2 = other$0.OtherPerson.createFrom($Create.Any); +const $$createType3 = $Create.Array($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.ts new file mode 100644 index 000000000..ac743f356 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + StringPtr +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.ts new file mode 100644 index 000000000..168c7fdba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * StringPtr is a nullable string. + */ +export type StringPtr = string | null; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts new file mode 100644 index 000000000..d2d973b28 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +export { + OtherPerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts new file mode 100644 index 000000000..711735a2b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts @@ -0,0 +1,52 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * OtherPerson is like a person, but different. + */ +export class OtherPerson { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": T[]; + + /** Creates a new OtherPerson instance. */ + constructor($$source: Partial> = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class OtherPerson. + */ + static createFrom($$createParamT: (source: any) => T): ($$source?: any) => OtherPerson { + const $$createField1_0 = $$createType0($$createParamT); + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new OtherPerson($$parsedSource as Partial>); + }; + } +} + +// Private type creation functions +const $$createType0 = ($$createParamT: any) => $Create.Array($$createParamT); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts new file mode 100644 index 000000000..2e4c173df --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other.OtherMethods.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts new file mode 100644 index 000000000..fb5f5ce21 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts @@ -0,0 +1,39 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOne").then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + }); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOtherOne"); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.HowDifferent.createFrom($Create.Any); +const $$createType2 = $models.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts new file mode 100644 index 000000000..c82f44866 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("main.EmbedOther.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts new file mode 100644 index 000000000..e3b2fe679 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts @@ -0,0 +1,39 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]> { + return $Call.ByName("main.EmbedService.LikeThisOne").then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + }); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("main.EmbedService.LikeThisOtherOne"); +} + +// Private type creation functions +const $$createType0 = nobindingshere$0.Person.createFrom; +const $$createType1 = nobindingshere$0.HowDifferent.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts new file mode 100644 index 000000000..192340e8e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet($0: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts new file mode 100644 index 000000000..e69bebf3b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts new file mode 100644 index 000000000..460b2b374 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts new file mode 100644 index 000000000..e437314f4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts new file mode 100644 index 000000000..460b2b374 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts new file mode 100644 index 000000000..e437314f4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts new file mode 100644 index 000000000..1a2ffb266 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts new file mode 100644 index 000000000..d76f68d9d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +export class Person { + "Name": string; + "Address": services$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
        = {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
        ); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts new file mode 100644 index 000000000..fc2efb6c1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services.OtherService.Yay").then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts new file mode 100644 index 000000000..ea2dcf8a6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts @@ -0,0 +1,209 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number }): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null }): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] }): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] }): $CancellablePromise<{ [_: `${number}`]: number[] }> { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in).then(($result: any) => { + return $$createType1($result); + }); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +export function StringArrayInputNamedOutput($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputNamedOutputs($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringArrayOut($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringOut($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in).then(($result: any) => { + return $$createType3($result); + }); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in).then(($result: any) => { + return $$createType4($result); + }); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts new file mode 100644 index 000000000..cd282c90c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + this["Parent"] = null; + } + if (!("Details" in $$source)) { + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts new file mode 100644 index 000000000..ea2dcf8a6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts @@ -0,0 +1,209 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number }): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null }): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] }): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] }): $CancellablePromise<{ [_: `${number}`]: number[] }> { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in).then(($result: any) => { + return $$createType1($result); + }); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +export function StringArrayInputNamedOutput($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputNamedOutputs($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringArrayOut($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringOut($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in).then(($result: any) => { + return $$createType3($result); + }); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in).then(($result: any) => { + return $$createType4($result); + }); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts new file mode 100644 index 000000000..cd282c90c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + this["Parent"] = null; + } + if (!("Details" in $$source)) { + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts new file mode 100644 index 000000000..6a6a881dc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts new file mode 100644 index 000000000..6a6a881dc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts new file mode 100644 index 000000000..1a2ffb266 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts new file mode 100644 index 000000000..f6eee9de8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts @@ -0,0 +1,47 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + */ +export class Person { + "Name": string; + "Address": services$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
        = {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
        ); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts new file mode 100644 index 000000000..554897ea4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services.OtherService.Yay").then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/warnings.log b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/warnings.log new file mode 100644 index 000000000..ce8369307 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/warnings.log @@ -0,0 +1,83 @@ +[warn] /testcases/complex_json/main.go:127:2: event 'collision' has one of multiple definitions here with data type map[string]int +[warn] /testcases/events_only/events.go:20:2: event 'collision' has one of multiple definitions here with data type int +[warn] /testcases/events_only/events.go:21:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:10:5: `application.RegisterEvent` is instantiated here but not called +[warn] /testcases/events_only/other.go:13:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:17:2: data type []T for event 'parametric' contains unresolved type parameters and will be ignored` +[warn] /testcases/events_only/other.go:22:2: event 'common:ApplicationStarted' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic +[warn] /testcases/marshalers/main.go:212:2: event 'collision' has one of multiple definitions here with data type *struct{Field []bool} +[warn] /testcases/marshalers/main.go:214:2: data type encoding/json.Marshaler for event 'interface' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors +[warn] dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only +[warn] event 'collision' has multiple conflicting definitions and will be ignored +[warn] events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly +[warn] generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/index.ts new file mode 100644 index 000000000..ba2885969 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + TextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/json/index.ts new file mode 100644 index 000000000..00ec01151 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/json/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + Marshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/json/models.ts new file mode 100644 index 000000000..8e7f0b3f1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/json/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + */ +export type Marshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/models.ts new file mode 100644 index 000000000..51089b14d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/models.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + */ +export type TextMarshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/eventcreate.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/eventcreate.ts new file mode 100644 index 000000000..71a208a3d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/eventcreate.ts @@ -0,0 +1,9 @@ +//@ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +Object.freeze($Create.Events); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/eventdata.d.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/eventdata.d.ts new file mode 100644 index 000000000..4384cef06 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/eventdata.d.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type { Events } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as json$0 from "../../../../../encoding/json/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as events_only$0 from "./generator/testcases/events_only/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as more$0 from "./generator/testcases/no_bindings_here/more/models.js"; + +declare module "/wails/runtime.js" { + namespace Events { + interface CustomEvents { + "events_only:class": events_only$0.SomeClass; + "events_only:map": { [_: string]: number[] | null } | null; + "events_only:nodata": void; + "events_only:other": more$0.StringPtr[] | null; + "events_only:string": string; + "interface": json$0.Marshaler; + "overlap": {"Field": boolean[] | null} | null; + } + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts new file mode 100644 index 000000000..c371520b0 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts @@ -0,0 +1,55 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + */ +export function Get(aliasValue: $models.Alias): $CancellablePromise<$models.Person> { + return $Call.ByID(1928502664, aliasValue); +} + +/** + * Apparently, aliases are all the rage right now. + */ +export function GetButAliased(p: $models.AliasedPerson): $CancellablePromise<$models.StrangelyAliasedPerson> { + return $Call.ByID(1896499664, p); +} + +/** + * Get someone quite different. + */ +export function GetButDifferent(): $CancellablePromise<$models.GenericPerson> { + return $Call.ByID(2240931744); +} + +export function GetButForeignPrivateAlias(): $CancellablePromise { + return $Call.ByID(643456960); +} + +export function GetButGenericAliases(): $CancellablePromise<$models.AliasGroup> { + return $Call.ByID(914093800); +} + +/** + * Greet a lot of unusual things. + */ +export function Greet($0: $models.EmptyAliasStruct, $1: $models.EmptyStruct): $CancellablePromise<$models.AliasStruct> { + return $Call.ByID(1411160069, $0, $1); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts new file mode 100644 index 000000000..75cbdc737 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts @@ -0,0 +1,26 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Alias, + AliasGroup, + AliasStruct, + AliasedPerson, + EmptyAliasStruct, + EmptyStruct, + GenericAlias, + GenericMapAlias, + GenericPerson, + GenericPersonAlias, + GenericPtrAlias, + IndirectPersonAlias, + OtherAliasStruct, + Person, + StrangelyAliasedPerson, + TPIndirectPersonAlias +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts new file mode 100644 index 000000000..26b204c1f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts @@ -0,0 +1,125 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * A nice type Alias. + */ +export type Alias = number; + +/** + * A class whose fields have various aliased types. + */ +export interface AliasGroup { + "GAi": GenericAlias; + "GAP": GenericAlias>; + "GPAs": GenericPtrAlias; + "GPAP": GenericPtrAlias>; + "GMA": GenericMapAlias; + "GPA": GenericPersonAlias; + "IPA": IndirectPersonAlias; + "TPIPA": TPIndirectPersonAlias; +} + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + */ +export interface AliasStruct { + /** + * A field with a comment. + */ + "Foo": number[] | null; + + /** + * Definitely not Foo. + */ + "Bar"?: string; + "Baz"?: string; + + /** + * A nested alias struct. + */ + "Other": OtherAliasStruct; +} + +/** + * A class alias. + */ +export type AliasedPerson = Person; + +/** + * An empty struct alias. + */ +export interface EmptyAliasStruct { +} + +/** + * An empty struct. + */ +export interface EmptyStruct { +} + +/** + * A generic alias that forwards to a type parameter. + */ +export type GenericAlias = T; + +/** + * A generic alias that wraps a map. + */ +export type GenericMapAlias = { [_: string]: U } | null; + +/** + * A generic struct containing an alias. + */ +export interface GenericPerson { + "Name": T; + "AliasedField": Alias; +} + +/** + * A generic alias that wraps a generic struct. + */ +export type GenericPersonAlias = GenericPerson[] | null>; + +/** + * A generic alias that wraps a pointer type. + */ +export type GenericPtrAlias = GenericAlias | null; + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export type IndirectPersonAlias = GenericPersonAlias; + +/** + * Another struct alias. + */ +export interface OtherAliasStruct { + "NoMoreIdeas": number[] | null; +} + +/** + * A non-generic struct containing an alias. + */ +export interface Person { + /** + * The Person's name. + */ + "Name": string; + + /** + * A random alias field. + */ + "AliasedField": Alias; +} + +/** + * Another class alias, but ordered after its aliased class. + */ +export type StrangelyAliasedPerson = Person; + +/** + * An alias that wraps a class through a typeparam alias. + */ +export type TPIndirectPersonAlias = GenericAlias>; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts new file mode 100644 index 000000000..bdcf43c67 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts new file mode 100644 index 000000000..5cba569c9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function TestMethod(): $CancellablePromise { + return $Call.ByID(2241101727); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts new file mode 100644 index 000000000..dc425cfd5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function TestMethod2(): $CancellablePromise { + return $Call.ByID(1556848345); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts new file mode 100644 index 000000000..a5334fce3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(person: $models.Person, emb: $models.Embedded1): $CancellablePromise { + return $Call.ByID(1411160069, person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts new file mode 100644 index 000000000..f645e5730 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts @@ -0,0 +1,17 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Title +} from "./models.js"; + +export type { + Embedded1, + Embedded3, + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts new file mode 100644 index 000000000..7cdb88164 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts @@ -0,0 +1,121 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Embedded1 { + /** + * Friends should be shadowed in Person by a field of lesser depth + */ + "Friends": number; + + /** + * Vanish should be omitted from Person because there is another field with same depth and no tag + */ + "Vanish": number; + + /** + * StillThere should be shadowed in Person by other field with same depth and a json tag + */ + "StillThere": string; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; +} + +export type Embedded3 = string; + +/** + * Person represents a person + */ +export interface Person { + /** + * Titles is optional in JSON + */ + "Titles"?: Title[] | null; + + /** + * Names has a + * multiline comment + */ + "Names": string[] | null; + + /** + * Partner has a custom and complex JSON key + */ + "Partner": Person | null; + "Friends": (Person | null)[] | null; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * StillThereButRenamed should shadow in Person the other field with same depth and no json tag + */ + "StillThere": Embedded3 | null; + + /** + * StrangeNumber maps to "-" + */ + "-": number; + + /** + * Embedded3 should appear with key "Embedded3" + */ + "Embedded3": Embedded3; + + /** + * StrangerNumber is serialized as a string + */ + "StrangerNumber": `${number}`; + + /** + * StrangestString is optional and serialized as a JSON string + */ + "StrangestString"?: `"${string}"`; + + /** + * StringStrangest is serialized as a JSON string and optional + */ + "StringStrangest"?: `"${string}"`; + + /** + * embedded4 should be optional and appear with key "emb4" + */ + "emb4"?: embedded4; +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; + +export interface embedded4 { + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * Friends should not be shadowed in Person as embedded4 is not embedded + * from encoding/json's point of view; + * however, it should be shadowed in Embedded1 + */ + "Friends": boolean; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts new file mode 100644 index 000000000..99ffd566f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts @@ -0,0 +1,24 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + */ +export function Greet(str: string, people: $models.Person[] | null, $2: {"AnotherCount": number, "AnotherOne": $models.Person | null}, assoc: { [_: `${number}`]: boolean | null } | null, $4: (number | null)[] | null, ...other: string[]): $CancellablePromise<[$models.Person, any, number[] | null]> { + return $Call.ByID(1411160069, str, people, $2, assoc, $4, other); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts new file mode 100644 index 000000000..6691fb86c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Person represents a person + */ +export interface Person { + "Name": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts new file mode 100644 index 000000000..e6a04adf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.StructA, $models.StructC]> { + return $Call.ByID(440020721); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts new file mode 100644 index 000000000..bbf592890 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + StructA, + StructC, + StructE +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts new file mode 100644 index 000000000..256987ac8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts @@ -0,0 +1,21 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface StructA { + "B": structB | null; +} + +export interface StructC { + "D": structD; +} + +export interface StructE { +} + +export interface structB { + "A": StructA | null; +} + +export interface structD { + "E": StructE; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts new file mode 100644 index 000000000..965a057ca --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]> { + return $Call.ByID(440020721); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts new file mode 100644 index 000000000..16cef660c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Alias, + Cyclic, + GenericCyclic +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts new file mode 100644 index 000000000..5395305fc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type Alias = Cyclic | null; + +export type Cyclic = ({ [_: string]: Alias } | null)[] | null; + +export type GenericCyclic = {"X": GenericCyclic | null, "Y": T[] | null}[] | null; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts new file mode 100644 index 000000000..bb2426022 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Interfaces!"); +console.log("Hello TS!"); +console.log("Hello TS Interfaces!"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts new file mode 100644 index 000000000..9d2a673d6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.InternalModel): $CancellablePromise { + return $Call.ByID(538079117, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts new file mode 100644 index 000000000..efe76e58b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts @@ -0,0 +1,16 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal model. + */ +export interface InternalModel { + "Field": string; +} + +/** + * An unexported model. + */ +export interface unexportedModel { + "Field": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts new file mode 100644 index 000000000..82d89784a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + Dummy +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts new file mode 100644 index 000000000..71070d754 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts @@ -0,0 +1,5 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Dummy { +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts new file mode 100644 index 000000000..e60cbea0c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +function InternalMethod($0: string): $CancellablePromise { + return $Call.ByID(3518775569, $0); +} + +export function VisibleMethod($0: otherpackage$0.Dummy): $CancellablePromise { + return $Call.ByID(474018228, $0); +} + +export async function CustomMethod(arg: string): Promise { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js new file mode 100644 index 000000000..442f20472 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts new file mode 100644 index 000000000..253d3f2f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ti.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ti.ts new file mode 100644 index 000000000..7400e97aa --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ti.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts new file mode 100644 index 000000000..57d7f73be --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.unexportedModel): $CancellablePromise { + return $Call.ByID(37626172, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts new file mode 100644 index 000000000..5a3127774 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts @@ -0,0 +1,47 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Comment 1. + */ +export function Method1(): $CancellablePromise { + return $Call.ByID(841558284); +} + +/** + * Comment 2. + */ +export function Method2(): $CancellablePromise { + return $Call.ByID(891891141); +} + +/** + * Comment 3a. + * Comment 3b. + */ +export function Method3(): $CancellablePromise { + return $Call.ByID(875113522); +} + +/** + * Comment 4. + */ +export function Method4(): $CancellablePromise { + return $Call.ByID(791225427); +} + +/** + * Comment 5. + */ +export function Method5(): $CancellablePromise { + return $Call.ByID(774447808); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts new file mode 100644 index 000000000..eda1dd8d0 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: $models.Title): $CancellablePromise { + return $Call.ByID(1411160069, name, title); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts new file mode 100644 index 000000000..e66941c16 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts @@ -0,0 +1,16 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Title +} from "./models.js"; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts new file mode 100644 index 000000000..74d673e16 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts @@ -0,0 +1,55 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Age is an integer with some predefined values + */ +export type Age = number; + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + */ +export interface Person { + "Title": Title; + "Name": string; + "Age": Age; +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts new file mode 100644 index 000000000..ade2383a0 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: services$0.Title): $CancellablePromise { + return $Call.ByID(1411160069, name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts new file mode 100644 index 000000000..01c612edc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts new file mode 100644 index 000000000..887aee9ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.ts new file mode 100644 index 000000000..03b13d1cf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + SomeClass +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.ts new file mode 100644 index 000000000..316f1bd22 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * SomeClass renders as a TS class. + */ +export interface SomeClass { + "Field": string; + "Meadow": nobindingshere$0.HowDifferent; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts new file mode 100644 index 000000000..8519667d5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts new file mode 100644 index 000000000..99b989f07 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + */ +export interface Person { + "Name": string; + "Address": services$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts new file mode 100644 index 000000000..4cb206cc6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(2007737399); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts new file mode 100644 index 000000000..8519667d5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts new file mode 100644 index 000000000..70b85519b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +export interface Person { + "Name": string; + "Address": other$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts new file mode 100644 index 000000000..8879fcfa2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(2447353446); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts new file mode 100644 index 000000000..34c4d151a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts new file mode 100644 index 000000000..8a2cb7a70 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts new file mode 100644 index 000000000..f9b8d87e2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts @@ -0,0 +1,25 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * Greet someone + */ +export function GreetWithContext(name: string): $CancellablePromise { + return $Call.ByID(1310150960, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts new file mode 100644 index 000000000..8a2cb7a70 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts new file mode 100644 index 000000000..e3aeb8a64 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export type { + BasicCstrAlias, + ComparableCstrAlias, + EmbeddedCustomInterface, + EmbeddedOriginalInterface, + EmbeddedPointer, + EmbeddedPointerPtr, + EmbeddedValue, + EmbeddedValuePtr, + GoodTildeCstrAlias, + InterfaceCstrAlias, + Maps, + MixedCstrAlias, + NonBasicCstrAlias, + PointableCstrAlias, + PointerAlias, + PointerTextMarshaler, + StringAlias, + StringType, + ValueAlias, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts new file mode 100644 index 000000000..aaabd3502 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts @@ -0,0 +1,767 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type BasicCstrAlias = S; + +export type ComparableCstrAlias = R; + +export type EmbeddedCustomInterface = string; + +export type EmbeddedOriginalInterface = string; + +export type EmbeddedPointer = string; + +export type EmbeddedPointerPtr = string; + +export type EmbeddedValue = string; + +export type EmbeddedValuePtr = string; + +export type GoodTildeCstrAlias = U; + +export type InterfaceCstrAlias = Y; + +export interface Maps { + /** + * Reject + */ + "Bool": { [_: string]: number } | null; + + /** + * Accept + */ + "Int": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "Uint": { [_: `${number}`]: number } | null; + + /** + * Reject + */ + "Float": { [_: string]: number } | null; + + /** + * Reject + */ + "Complex": { [_: string]: number } | null; + + /** + * Accept + */ + "Byte": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "Rune": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "String": { [_: string]: number } | null; + + /** + * Reject + */ + "IntPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "UintPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "FloatPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "ComplexPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "StringPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "NTM": { [_: string]: number } | null; + + /** + * Reject + */ + "NTMPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "VTM": { [_: ValueTextMarshaler]: number } | null; + + /** + * Accept + */ + "VTMPtr": { [_: ValueTextMarshaler]: number } | null; + + /** + * Reject + */ + "PTM": { [_: string]: number } | null; + + /** + * Accept + */ + "PTMPtr": { [_: PointerTextMarshaler]: number } | null; + + /** + * Accept, hide + */ + "JTM": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "JTMPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "A": { [_: string]: number } | null; + + /** + * Reject + */ + "APtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TM": { [_: string]: number } | null; + + /** + * Reject + */ + "TMPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "CI": { [_: string]: number } | null; + + /** + * Reject + */ + "CIPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "EI": { [_: string]: number } | null; + + /** + * Reject + */ + "EIPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "EV": { [_: EmbeddedValue]: number } | null; + + /** + * Accept + */ + "EVPtr": { [_: EmbeddedValue]: number } | null; + + /** + * Accept + */ + "EVP": { [_: EmbeddedValuePtr]: number } | null; + + /** + * Accept + */ + "EVPPtr": { [_: EmbeddedValuePtr]: number } | null; + + /** + * Reject + */ + "EP": { [_: string]: number } | null; + + /** + * Accept + */ + "EPPtr": { [_: EmbeddedPointer]: number } | null; + + /** + * Accept + */ + "EPP": { [_: EmbeddedPointerPtr]: number } | null; + + /** + * Accept + */ + "EPPPtr": { [_: EmbeddedPointerPtr]: number } | null; + + /** + * Accept + */ + "ECI": { [_: EmbeddedCustomInterface]: number } | null; + + /** + * Accept + */ + "ECIPtr": { [_: EmbeddedCustomInterface]: number } | null; + + /** + * Accept + */ + "EOI": { [_: EmbeddedOriginalInterface]: number } | null; + + /** + * Accept + */ + "EOIPtr": { [_: EmbeddedOriginalInterface]: number } | null; + + /** + * Reject + */ + "WT": { [_: string]: number } | null; + + /** + * Reject + */ + "WA": { [_: string]: number } | null; + + /** + * Accept + */ + "ST": { [_: StringType]: number } | null; + + /** + * Accept + */ + "SA": { [_: StringAlias]: number } | null; + + /** + * Accept + */ + "IntT": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "IntA": { [_: `${number}`]: number } | null; + + /** + * Reject + */ + "VT": { [_: string]: number } | null; + + /** + * Reject + */ + "VTPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "VPT": { [_: string]: number } | null; + + /** + * Reject + */ + "VPTPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "VA": { [_: ValueAlias]: number } | null; + + /** + * Accept + */ + "VAPtr": { [_: ValueAlias]: number } | null; + + /** + * Accept, hide + */ + "VPA": { [_: string]: number } | null; + + /** + * Reject + */ + "VPAPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "PT": { [_: string]: number } | null; + + /** + * Reject + */ + "PTPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "PPT": { [_: string]: number } | null; + + /** + * Reject + */ + "PPTPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "PA": { [_: string]: number } | null; + + /** + * Accept + */ + "PAPtr": { [_: PointerAlias]: number } | null; + + /** + * Accept, hide + */ + "PPA": { [_: string]: number } | null; + + /** + * Reject + */ + "PPAPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "IT": { [_: string]: number } | null; + + /** + * Reject + */ + "ITPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "IPT": { [_: string]: number } | null; + + /** + * Reject + */ + "IPTPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "IA": { [_: string]: number } | null; + + /** + * Reject + */ + "IAPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "IPA": { [_: string]: number } | null; + + /** + * Reject + */ + "IPAPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPR": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPRPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPS": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPSPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPT": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPTPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPU": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPUPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPV": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPVPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPW": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPWPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPX": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPXPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPY": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPYPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPZ": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPZPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAR": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GARPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAS": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GASPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAT": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GATPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAU": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAUPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAV": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAVPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAW": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAWPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAX": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAXPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAY": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAYPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAZ": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAZPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GACi": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "GACV": { [_: ComparableCstrAlias]: number } | null; + + /** + * Reject + */ + "GACP": { [_: string]: number } | null; + + /** + * Reject + */ + "GACiPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GACVPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GACPPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GABi": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "GABs": { [_: BasicCstrAlias]: number } | null; + + /** + * Reject + */ + "GABiPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GABT": { [_: string]: number } | null; + + /** + * Reject + */ + "GABTPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "GAGT": { [_: GoodTildeCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GAGTPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "GANBV": { [_: NonBasicCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GANBP": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GANBVPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GANBPPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "GAPlV1": { [_: PointableCstrAlias]: number } | null; + + /** + * Accept + */ + "GAPlV2": { [_: PointableCstrAlias]: number } | null; + + /** + * Reject + */ + "GAPlP1": { [_: string]: number } | null; + + /** + * Accept + */ + "GAPlP2": { [_: PointableCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GAPlVPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAPlPPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAMi": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "GAMS": { [_: MixedCstrAlias]: number } | null; + + /** + * Accept + */ + "GAMV": { [_: MixedCstrAlias]: number } | null; + + /** + * Reject + */ + "GAMSPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAMVPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAII": { [_: string]: number } | null; + + /** + * Accept + */ + "GAIV": { [_: InterfaceCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GAIP": { [_: string]: number } | null; + + /** + * Reject + */ + "GAIIPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAIVPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GAIPPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAPrV": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAPrP": { [_: string]: number } | null; + + /** + * Reject + */ + "GAPrVPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GAPrPPtr": { [_: string]: number } | null; +} + +export type MixedCstrAlias = X; + +export type NonBasicCstrAlias = V; + +export type PointableCstrAlias = W; + +export type PointerAlias = PointerTextMarshaler; + +export type PointerTextMarshaler = string; + +export type StringAlias = string; + +export type StringType = string; + +export type ValueAlias = ValueTextMarshaler; + +export type ValueTextMarshaler = string; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts new file mode 100644 index 000000000..50d2f6d72 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>> { + return $Call.ByID(4021345184); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts new file mode 100644 index 000000000..7c55b767b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts @@ -0,0 +1,33 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export type { + AliasJsonMarshaler, + AliasMarshaler, + AliasNonMarshaler, + AliasTextMarshaler, + Data, + ImplicitJsonButText, + ImplicitJsonMarshaler, + ImplicitMarshaler, + ImplicitNonJson, + ImplicitNonMarshaler, + ImplicitNonText, + ImplicitTextButJson, + ImplicitTextMarshaler, + NonMarshaler, + PointerJsonMarshaler, + PointerMarshaler, + PointerTextMarshaler, + UnderlyingJsonMarshaler, + UnderlyingMarshaler, + UnderlyingTextMarshaler, + ValueJsonMarshaler, + ValueMarshaler, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts new file mode 100644 index 000000000..02d639994 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts @@ -0,0 +1,309 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + */ +export type AliasJsonMarshaler = any; + +/** + * any + */ +export type AliasMarshaler = any; + +/** + * struct{} + */ +export interface AliasNonMarshaler { +} + +/** + * string + */ +export type AliasTextMarshaler = string; + +export interface Data { + "NM": NonMarshaler; + + /** + * NonMarshaler | null + */ + "NMPtr": NonMarshaler | null; + "VJM": ValueJsonMarshaler; + + /** + * ValueJsonMarshaler | null + */ + "VJMPtr": ValueJsonMarshaler | null; + "PJM": PointerJsonMarshaler; + + /** + * PointerJsonMarshaler | null + */ + "PJMPtr": PointerJsonMarshaler | null; + "VTM": ValueTextMarshaler; + + /** + * ValueTextMarshaler | null + */ + "VTMPtr": ValueTextMarshaler | null; + "PTM": PointerTextMarshaler; + + /** + * PointerTextMarshaler | null + */ + "PTMPtr": PointerTextMarshaler | null; + "VM": ValueMarshaler; + + /** + * ValueMarshaler | null + */ + "VMPtr": ValueMarshaler | null; + "PM": PointerMarshaler; + + /** + * PointerMarshaler | null + */ + "PMPtr": PointerMarshaler | null; + "UJM": UnderlyingJsonMarshaler; + + /** + * UnderlyingJsonMarshaler | null + */ + "UJMPtr": UnderlyingJsonMarshaler | null; + "UTM": UnderlyingTextMarshaler; + + /** + * UnderlyingTextMarshaler | null + */ + "UTMPtr": UnderlyingTextMarshaler | null; + "UM": UnderlyingMarshaler; + + /** + * UnderlyingMarshaler | null + */ + "UMPtr": UnderlyingMarshaler | null; + + /** + * any + */ + "JM": any; + + /** + * any | null + */ + "JMPtr": any | null; + + /** + * string + */ + "TM": string; + + /** + * string | null + */ + "TMPtr": string | null; + + /** + * any + */ + "CJM": any; + + /** + * any | null + */ + "CJMPtr": any | null; + + /** + * string + */ + "CTM": string; + + /** + * string | null + */ + "CTMPtr": string | null; + + /** + * any + */ + "CM": any; + + /** + * any | null + */ + "CMPtr": any | null; + "ANM": AliasNonMarshaler; + + /** + * AliasNonMarshaler | null + */ + "ANMPtr": AliasNonMarshaler | null; + "AJM": AliasJsonMarshaler; + + /** + * AliasJsonMarshaler | null + */ + "AJMPtr": AliasJsonMarshaler | null; + "ATM": AliasTextMarshaler; + + /** + * AliasTextMarshaler | null + */ + "ATMPtr": AliasTextMarshaler | null; + "AM": AliasMarshaler; + + /** + * AliasMarshaler | null + */ + "AMPtr": AliasMarshaler | null; + "ImJM": ImplicitJsonMarshaler; + + /** + * ImplicitJsonMarshaler | null + */ + "ImJMPtr": ImplicitJsonMarshaler | null; + "ImTM": ImplicitTextMarshaler; + + /** + * ImplicitTextMarshaler | null + */ + "ImTMPtr": ImplicitTextMarshaler | null; + "ImM": ImplicitMarshaler; + + /** + * ImplicitMarshaler | null + */ + "ImMPtr": ImplicitMarshaler | null; + "ImNJ": ImplicitNonJson; + + /** + * ImplicitNonJson | null + */ + "ImNJPtr": ImplicitNonJson | null; + "ImNT": ImplicitNonText; + + /** + * ImplicitNonText | null + */ + "ImNTPtr": ImplicitNonText | null; + "ImNM": ImplicitNonMarshaler; + + /** + * ImplicitNonMarshaler | null + */ + "ImNMPtr": ImplicitNonMarshaler | null; + "ImJbT": ImplicitJsonButText; + + /** + * ImplicitJsonButText | null + */ + "ImJbTPtr": ImplicitJsonButText | null; + "ImTbJ": ImplicitTextButJson; + + /** + * ImplicitTextButJson | null + */ + "ImTbJPtr": ImplicitTextButJson | null; +} + +/** + * any + */ +export type ImplicitJsonButText = any; + +/** + * any + */ +export type ImplicitJsonMarshaler = any; + +/** + * any + */ +export type ImplicitMarshaler = any; + +/** + * string + */ +export type ImplicitNonJson = string; + +/** + * class{ Marshaler, TextMarshaler } + */ +export interface ImplicitNonMarshaler { + "Marshaler": json$0.Marshaler; + "TextMarshaler": encoding$0.TextMarshaler; +} + +/** + * any + */ +export type ImplicitNonText = any; + +/** + * any + */ +export type ImplicitTextButJson = any; + +/** + * string + */ +export type ImplicitTextMarshaler = string; + +/** + * class {} + */ +export interface NonMarshaler { +} + +/** + * any + */ +export type PointerJsonMarshaler = any; + +/** + * any + */ +export type PointerMarshaler = any; + +/** + * string + */ +export type PointerTextMarshaler = string; + +/** + * any + */ +export type UnderlyingJsonMarshaler = any; + +/** + * any + */ +export type UnderlyingMarshaler = any; + +/** + * string + */ +export type UnderlyingTextMarshaler = string; + +/** + * any + */ +export type ValueJsonMarshaler = any; + +/** + * any + */ +export type ValueMarshaler = any; + +/** + * string + */ +export type ValueTextMarshaler = string; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts new file mode 100644 index 000000000..b175ebe96 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Data> { + return $Call.ByID(4021345184); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts new file mode 100644 index 000000000..bf611e486 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +export type { + HowDifferent, + Impersonator, + Person, + PrivatePerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts new file mode 100644 index 000000000..c42cce373 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts @@ -0,0 +1,64 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + */ +export interface HowDifferent { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": ({ [_: string]: How } | null)[] | null; +} + +/** + * Impersonator gets their fields from other people. + */ +export type Impersonator = other$0.OtherPerson; + +/** + * Person is not a number. + */ +export interface Person { + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; +} + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export type PrivatePerson = personImpl; + +export interface personImpl { + /** + * Nickname conceals a person's identity. + */ + "Nickname": string; + + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.ts new file mode 100644 index 000000000..ac743f356 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + StringPtr +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.ts new file mode 100644 index 000000000..6a5a12300 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * StringPtr is a nullable string. + */ +export type StringPtr = string | null; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts new file mode 100644 index 000000000..b9f2889db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +export type { + OtherPerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts new file mode 100644 index 000000000..6ca5b82a2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts @@ -0,0 +1,17 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherPerson is like a person, but different. + */ +export interface OtherPerson { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": T[] | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts new file mode 100644 index 000000000..c2fb7bc6a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(3606939272); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts new file mode 100644 index 000000000..4f623616d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]> { + return $Call.ByID(2124352079); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(4281222271); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts new file mode 100644 index 000000000..d155adc30 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(3566862802); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts new file mode 100644 index 000000000..65098990f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]> { + return $Call.ByID(2590614085); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(773650321); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts new file mode 100644 index 000000000..76375250e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet($0: string): $CancellablePromise { + return $Call.ByID(1411160069, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts new file mode 100644 index 000000000..e69bebf3b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts new file mode 100644 index 000000000..34c4d151a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts new file mode 100644 index 000000000..d27e71304 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts new file mode 100644 index 000000000..34c4d151a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts new file mode 100644 index 000000000..d27e71304 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts new file mode 100644 index 000000000..8519667d5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts new file mode 100644 index 000000000..3ab21d612 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +export interface Person { + "Name": string; + "Address": services$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts new file mode 100644 index 000000000..50e62daa4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(3568225479); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts new file mode 100644 index 000000000..ef5015467 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts @@ -0,0 +1,190 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByID(3862002418, $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByID(2424639793, $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByID(3132595881, $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByID(3306292566, $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1754277916, $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1909469092, $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(4251088558, $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1343888303, $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2205561041, $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByID(572240879, $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2189402897, $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByID(642881729, $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1066151743, $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByID(2718999663, $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByID(2386486356, $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null } | null): $CancellablePromise { + return $Call.ByID(2163571325, $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise { + return $Call.ByID(2900172572, $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise<{ [_: `${number}`]: number[] | null } | null> { + return $Call.ByID(881980169, $in); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByID(1075577233); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByID(3589606958, $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByID(224675106, $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByID(2124953624, $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByID(3516977899, $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByID(229603958, $in); +} + +export function StringArrayInputNamedOutput($in: string[] | null): $CancellablePromise { + return $Call.ByID(3678582682, $in); +} + +export function StringArrayInputNamedOutputs($in: string[] | null): $CancellablePromise { + return $Call.ByID(319259595, $in); +} + +export function StringArrayInputStringArrayOut($in: string[] | null): $CancellablePromise { + return $Call.ByID(383995060, $in); +} + +export function StringArrayInputStringOut($in: string[] | null): $CancellablePromise { + return $Call.ByID(1091960237, $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByID(3835643147, $in); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByID(2447692557, $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByID(2943477349, $in); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(3401034892, $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1236957573, $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(1160383782, $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1739300671, $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(793803239, $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1403757716, $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2988345717, $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(518250834, $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2836661285, $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1367187362, $in); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts new file mode 100644 index 000000000..5664b79ab --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts new file mode 100644 index 000000000..ef5015467 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts @@ -0,0 +1,190 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByID(3862002418, $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByID(2424639793, $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByID(3132595881, $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByID(3306292566, $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1754277916, $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1909469092, $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(4251088558, $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1343888303, $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2205561041, $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByID(572240879, $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2189402897, $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByID(642881729, $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1066151743, $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByID(2718999663, $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByID(2386486356, $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null } | null): $CancellablePromise { + return $Call.ByID(2163571325, $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise { + return $Call.ByID(2900172572, $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise<{ [_: `${number}`]: number[] | null } | null> { + return $Call.ByID(881980169, $in); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByID(1075577233); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByID(3589606958, $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByID(224675106, $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByID(2124953624, $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByID(3516977899, $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByID(229603958, $in); +} + +export function StringArrayInputNamedOutput($in: string[] | null): $CancellablePromise { + return $Call.ByID(3678582682, $in); +} + +export function StringArrayInputNamedOutputs($in: string[] | null): $CancellablePromise { + return $Call.ByID(319259595, $in); +} + +export function StringArrayInputStringArrayOut($in: string[] | null): $CancellablePromise { + return $Call.ByID(383995060, $in); +} + +export function StringArrayInputStringOut($in: string[] | null): $CancellablePromise { + return $Call.ByID(1091960237, $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByID(3835643147, $in); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByID(2447692557, $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByID(2943477349, $in); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(3401034892, $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1236957573, $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(1160383782, $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1739300671, $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(793803239, $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1403757716, $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2988345717, $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(518250834, $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2836661285, $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1367187362, $in); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts new file mode 100644 index 000000000..5664b79ab --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts new file mode 100644 index 000000000..8a2cb7a70 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts new file mode 100644 index 000000000..8a2cb7a70 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts new file mode 100644 index 000000000..8519667d5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts new file mode 100644 index 000000000..f29117203 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts @@ -0,0 +1,15 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + */ +export interface Person { + "Name": string; + "Address": services$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts new file mode 100644 index 000000000..79c8907f9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(1491748400); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/warnings.log b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/warnings.log new file mode 100644 index 000000000..ce8369307 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/warnings.log @@ -0,0 +1,83 @@ +[warn] /testcases/complex_json/main.go:127:2: event 'collision' has one of multiple definitions here with data type map[string]int +[warn] /testcases/events_only/events.go:20:2: event 'collision' has one of multiple definitions here with data type int +[warn] /testcases/events_only/events.go:21:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:10:5: `application.RegisterEvent` is instantiated here but not called +[warn] /testcases/events_only/other.go:13:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:17:2: data type []T for event 'parametric' contains unresolved type parameters and will be ignored` +[warn] /testcases/events_only/other.go:22:2: event 'common:ApplicationStarted' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic +[warn] /testcases/marshalers/main.go:212:2: event 'collision' has one of multiple definitions here with data type *struct{Field []bool} +[warn] /testcases/marshalers/main.go:214:2: data type encoding/json.Marshaler for event 'interface' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors +[warn] dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only +[warn] event 'collision' has multiple conflicting definitions and will be ignored +[warn] events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly +[warn] generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/index.ts new file mode 100644 index 000000000..ba2885969 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + TextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/json/index.ts new file mode 100644 index 000000000..00ec01151 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/json/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + Marshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/json/models.ts new file mode 100644 index 000000000..8e7f0b3f1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/json/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + */ +export type Marshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/models.ts new file mode 100644 index 000000000..51089b14d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/models.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + */ +export type TextMarshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/eventcreate.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/eventcreate.ts new file mode 100644 index 000000000..71a208a3d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/eventcreate.ts @@ -0,0 +1,9 @@ +//@ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +Object.freeze($Create.Events); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/eventdata.d.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/eventdata.d.ts new file mode 100644 index 000000000..4384cef06 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/eventdata.d.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type { Events } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as json$0 from "../../../../../encoding/json/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as events_only$0 from "./generator/testcases/events_only/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import type * as more$0 from "./generator/testcases/no_bindings_here/more/models.js"; + +declare module "/wails/runtime.js" { + namespace Events { + interface CustomEvents { + "events_only:class": events_only$0.SomeClass; + "events_only:map": { [_: string]: number[] | null } | null; + "events_only:nodata": void; + "events_only:other": more$0.StringPtr[] | null; + "events_only:string": string; + "interface": json$0.Marshaler; + "overlap": {"Field": boolean[] | null} | null; + } + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts new file mode 100644 index 000000000..d19c65d22 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts @@ -0,0 +1,55 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + */ +export function Get(aliasValue: $models.Alias): $CancellablePromise<$models.Person> { + return $Call.ByName("main.GreetService.Get", aliasValue); +} + +/** + * Apparently, aliases are all the rage right now. + */ +export function GetButAliased(p: $models.AliasedPerson): $CancellablePromise<$models.StrangelyAliasedPerson> { + return $Call.ByName("main.GreetService.GetButAliased", p); +} + +/** + * Get someone quite different. + */ +export function GetButDifferent(): $CancellablePromise<$models.GenericPerson> { + return $Call.ByName("main.GreetService.GetButDifferent"); +} + +export function GetButForeignPrivateAlias(): $CancellablePromise { + return $Call.ByName("main.GreetService.GetButForeignPrivateAlias"); +} + +export function GetButGenericAliases(): $CancellablePromise<$models.AliasGroup> { + return $Call.ByName("main.GreetService.GetButGenericAliases"); +} + +/** + * Greet a lot of unusual things. + */ +export function Greet($0: $models.EmptyAliasStruct, $1: $models.EmptyStruct): $CancellablePromise<$models.AliasStruct> { + return $Call.ByName("main.GreetService.Greet", $0, $1); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts new file mode 100644 index 000000000..75cbdc737 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts @@ -0,0 +1,26 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Alias, + AliasGroup, + AliasStruct, + AliasedPerson, + EmptyAliasStruct, + EmptyStruct, + GenericAlias, + GenericMapAlias, + GenericPerson, + GenericPersonAlias, + GenericPtrAlias, + IndirectPersonAlias, + OtherAliasStruct, + Person, + StrangelyAliasedPerson, + TPIndirectPersonAlias +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts new file mode 100644 index 000000000..26b204c1f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts @@ -0,0 +1,125 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * A nice type Alias. + */ +export type Alias = number; + +/** + * A class whose fields have various aliased types. + */ +export interface AliasGroup { + "GAi": GenericAlias; + "GAP": GenericAlias>; + "GPAs": GenericPtrAlias; + "GPAP": GenericPtrAlias>; + "GMA": GenericMapAlias; + "GPA": GenericPersonAlias; + "IPA": IndirectPersonAlias; + "TPIPA": TPIndirectPersonAlias; +} + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + */ +export interface AliasStruct { + /** + * A field with a comment. + */ + "Foo": number[] | null; + + /** + * Definitely not Foo. + */ + "Bar"?: string; + "Baz"?: string; + + /** + * A nested alias struct. + */ + "Other": OtherAliasStruct; +} + +/** + * A class alias. + */ +export type AliasedPerson = Person; + +/** + * An empty struct alias. + */ +export interface EmptyAliasStruct { +} + +/** + * An empty struct. + */ +export interface EmptyStruct { +} + +/** + * A generic alias that forwards to a type parameter. + */ +export type GenericAlias = T; + +/** + * A generic alias that wraps a map. + */ +export type GenericMapAlias = { [_: string]: U } | null; + +/** + * A generic struct containing an alias. + */ +export interface GenericPerson { + "Name": T; + "AliasedField": Alias; +} + +/** + * A generic alias that wraps a generic struct. + */ +export type GenericPersonAlias = GenericPerson[] | null>; + +/** + * A generic alias that wraps a pointer type. + */ +export type GenericPtrAlias = GenericAlias | null; + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export type IndirectPersonAlias = GenericPersonAlias; + +/** + * Another struct alias. + */ +export interface OtherAliasStruct { + "NoMoreIdeas": number[] | null; +} + +/** + * A non-generic struct containing an alias. + */ +export interface Person { + /** + * The Person's name. + */ + "Name": string; + + /** + * A random alias field. + */ + "AliasedField": Alias; +} + +/** + * Another class alias, but ordered after its aliased class. + */ +export type StrangelyAliasedPerson = Person; + +/** + * An alias that wraps a class through a typeparam alias. + */ +export type TPIndirectPersonAlias = GenericAlias>; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts new file mode 100644 index 000000000..bdcf43c67 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts new file mode 100644 index 000000000..e4281d925 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function TestMethod(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service7.TestMethod"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts new file mode 100644 index 000000000..1c0154b5e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function TestMethod2(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service9.TestMethod2"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts new file mode 100644 index 000000000..9eff81e94 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(person: $models.Person, emb: $models.Embedded1): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts new file mode 100644 index 000000000..f645e5730 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts @@ -0,0 +1,17 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Title +} from "./models.js"; + +export type { + Embedded1, + Embedded3, + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts new file mode 100644 index 000000000..7cdb88164 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts @@ -0,0 +1,121 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Embedded1 { + /** + * Friends should be shadowed in Person by a field of lesser depth + */ + "Friends": number; + + /** + * Vanish should be omitted from Person because there is another field with same depth and no tag + */ + "Vanish": number; + + /** + * StillThere should be shadowed in Person by other field with same depth and a json tag + */ + "StillThere": string; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; +} + +export type Embedded3 = string; + +/** + * Person represents a person + */ +export interface Person { + /** + * Titles is optional in JSON + */ + "Titles"?: Title[] | null; + + /** + * Names has a + * multiline comment + */ + "Names": string[] | null; + + /** + * Partner has a custom and complex JSON key + */ + "Partner": Person | null; + "Friends": (Person | null)[] | null; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * StillThereButRenamed should shadow in Person the other field with same depth and no json tag + */ + "StillThere": Embedded3 | null; + + /** + * StrangeNumber maps to "-" + */ + "-": number; + + /** + * Embedded3 should appear with key "Embedded3" + */ + "Embedded3": Embedded3; + + /** + * StrangerNumber is serialized as a string + */ + "StrangerNumber": `${number}`; + + /** + * StrangestString is optional and serialized as a JSON string + */ + "StrangestString"?: `"${string}"`; + + /** + * StringStrangest is serialized as a JSON string and optional + */ + "StringStrangest"?: `"${string}"`; + + /** + * embedded4 should be optional and appear with key "emb4" + */ + "emb4"?: embedded4; +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; + +export interface embedded4 { + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * Friends should not be shadowed in Person as embedded4 is not embedded + * from encoding/json's point of view; + * however, it should be shadowed in Embedded1 + */ + "Friends": boolean; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts new file mode 100644 index 000000000..0c3ef75cb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts @@ -0,0 +1,24 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + */ +export function Greet(str: string, people: $models.Person[] | null, $2: {"AnotherCount": number, "AnotherOne": $models.Person | null}, assoc: { [_: `${number}`]: boolean | null } | null, $4: (number | null)[] | null, ...other: string[]): $CancellablePromise<[$models.Person, any, number[] | null]> { + return $Call.ByName("main.GreetService.Greet", str, people, $2, assoc, $4, other); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts new file mode 100644 index 000000000..6691fb86c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Person represents a person + */ +export interface Person { + "Name": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts new file mode 100644 index 000000000..52f87997c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.StructA, $models.StructC]> { + return $Call.ByName("main.GreetService.MakeCycles"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts new file mode 100644 index 000000000..bbf592890 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + StructA, + StructC, + StructE +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts new file mode 100644 index 000000000..256987ac8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts @@ -0,0 +1,21 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface StructA { + "B": structB | null; +} + +export interface StructC { + "D": structD; +} + +export interface StructE { +} + +export interface structB { + "A": StructA | null; +} + +export interface structD { + "E": StructE; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts new file mode 100644 index 000000000..b7ef0ae3e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]> { + return $Call.ByName("main.GreetService.MakeCycles"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts new file mode 100644 index 000000000..16cef660c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Alias, + Cyclic, + GenericCyclic +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts new file mode 100644 index 000000000..5395305fc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type Alias = Cyclic | null; + +export type Cyclic = ({ [_: string]: Alias } | null)[] | null; + +export type GenericCyclic = {"X": GenericCyclic | null, "Y": T[] | null}[] | null; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts new file mode 100644 index 000000000..bb2426022 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Interfaces!"); +console.log("Hello TS!"); +console.log("Hello TS Interfaces!"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts new file mode 100644 index 000000000..faca61bc2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.InternalModel): $CancellablePromise { + return $Call.ByName("main.InternalService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts new file mode 100644 index 000000000..efe76e58b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts @@ -0,0 +1,16 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal model. + */ +export interface InternalModel { + "Field": string; +} + +/** + * An unexported model. + */ +export interface unexportedModel { + "Field": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts new file mode 100644 index 000000000..82d89784a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + Dummy +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts new file mode 100644 index 000000000..71070d754 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts @@ -0,0 +1,5 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Dummy { +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts new file mode 100644 index 000000000..84ac0538c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +function InternalMethod($0: string): $CancellablePromise { + return $Call.ByName("main.Service.InternalMethod", $0); +} + +export function VisibleMethod($0: otherpackage$0.Dummy): $CancellablePromise { + return $Call.ByName("main.Service.VisibleMethod", $0); +} + +export async function CustomMethod(arg: string): Promise { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js new file mode 100644 index 000000000..442f20472 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts new file mode 100644 index 000000000..253d3f2f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ti.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ti.ts new file mode 100644 index 000000000..7400e97aa --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ti.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts new file mode 100644 index 000000000..b49239ada --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.unexportedModel): $CancellablePromise { + return $Call.ByName("main.unexportedService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts new file mode 100644 index 000000000..4951af01d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts @@ -0,0 +1,47 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Comment 1. + */ +export function Method1(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method1"); +} + +/** + * Comment 2. + */ +export function Method2(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method2"); +} + +/** + * Comment 3a. + * Comment 3b. + */ +export function Method3(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method3"); +} + +/** + * Comment 4. + */ +export function Method4(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method4"); +} + +/** + * Comment 5. + */ +export function Method5(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method5"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts new file mode 100644 index 000000000..d857aa9e7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: $models.Title): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name, title); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts new file mode 100644 index 000000000..e66941c16 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts @@ -0,0 +1,16 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Title +} from "./models.js"; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts new file mode 100644 index 000000000..74d673e16 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts @@ -0,0 +1,55 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Age is an integer with some predefined values + */ +export type Age = number; + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + */ +export interface Person { + "Title": Title; + "Name": string; + "Age": Age; +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts new file mode 100644 index 000000000..73469c39d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: services$0.Title): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts new file mode 100644 index 000000000..01c612edc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts new file mode 100644 index 000000000..887aee9ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.ts new file mode 100644 index 000000000..03b13d1cf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + SomeClass +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.ts new file mode 100644 index 000000000..316f1bd22 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/events_only/models.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * SomeClass renders as a TS class. + */ +export interface SomeClass { + "Field": string; + "Meadow": nobindingshere$0.HowDifferent; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts new file mode 100644 index 000000000..5a8e9adc3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts new file mode 100644 index 000000000..99b989f07 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + */ +export interface Person { + "Name": string; + "Address": services$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts new file mode 100644 index 000000000..c6db3803b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts new file mode 100644 index 000000000..5a8e9adc3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts new file mode 100644 index 000000000..70b85519b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +export interface Person { + "Name": string; + "Address": other$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts new file mode 100644 index 000000000..b83ce5234 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts new file mode 100644 index 000000000..41664b850 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts new file mode 100644 index 000000000..80bebe425 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts new file mode 100644 index 000000000..63a65248c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts @@ -0,0 +1,25 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * Greet someone + */ +export function GreetWithContext(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.GreetWithContext", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts new file mode 100644 index 000000000..80bebe425 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts new file mode 100644 index 000000000..e3aeb8a64 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export type { + BasicCstrAlias, + ComparableCstrAlias, + EmbeddedCustomInterface, + EmbeddedOriginalInterface, + EmbeddedPointer, + EmbeddedPointerPtr, + EmbeddedValue, + EmbeddedValuePtr, + GoodTildeCstrAlias, + InterfaceCstrAlias, + Maps, + MixedCstrAlias, + NonBasicCstrAlias, + PointableCstrAlias, + PointerAlias, + PointerTextMarshaler, + StringAlias, + StringType, + ValueAlias, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts new file mode 100644 index 000000000..aaabd3502 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts @@ -0,0 +1,767 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type BasicCstrAlias = S; + +export type ComparableCstrAlias = R; + +export type EmbeddedCustomInterface = string; + +export type EmbeddedOriginalInterface = string; + +export type EmbeddedPointer = string; + +export type EmbeddedPointerPtr = string; + +export type EmbeddedValue = string; + +export type EmbeddedValuePtr = string; + +export type GoodTildeCstrAlias = U; + +export type InterfaceCstrAlias = Y; + +export interface Maps { + /** + * Reject + */ + "Bool": { [_: string]: number } | null; + + /** + * Accept + */ + "Int": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "Uint": { [_: `${number}`]: number } | null; + + /** + * Reject + */ + "Float": { [_: string]: number } | null; + + /** + * Reject + */ + "Complex": { [_: string]: number } | null; + + /** + * Accept + */ + "Byte": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "Rune": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "String": { [_: string]: number } | null; + + /** + * Reject + */ + "IntPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "UintPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "FloatPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "ComplexPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "StringPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "NTM": { [_: string]: number } | null; + + /** + * Reject + */ + "NTMPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "VTM": { [_: ValueTextMarshaler]: number } | null; + + /** + * Accept + */ + "VTMPtr": { [_: ValueTextMarshaler]: number } | null; + + /** + * Reject + */ + "PTM": { [_: string]: number } | null; + + /** + * Accept + */ + "PTMPtr": { [_: PointerTextMarshaler]: number } | null; + + /** + * Accept, hide + */ + "JTM": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "JTMPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "A": { [_: string]: number } | null; + + /** + * Reject + */ + "APtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TM": { [_: string]: number } | null; + + /** + * Reject + */ + "TMPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "CI": { [_: string]: number } | null; + + /** + * Reject + */ + "CIPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "EI": { [_: string]: number } | null; + + /** + * Reject + */ + "EIPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "EV": { [_: EmbeddedValue]: number } | null; + + /** + * Accept + */ + "EVPtr": { [_: EmbeddedValue]: number } | null; + + /** + * Accept + */ + "EVP": { [_: EmbeddedValuePtr]: number } | null; + + /** + * Accept + */ + "EVPPtr": { [_: EmbeddedValuePtr]: number } | null; + + /** + * Reject + */ + "EP": { [_: string]: number } | null; + + /** + * Accept + */ + "EPPtr": { [_: EmbeddedPointer]: number } | null; + + /** + * Accept + */ + "EPP": { [_: EmbeddedPointerPtr]: number } | null; + + /** + * Accept + */ + "EPPPtr": { [_: EmbeddedPointerPtr]: number } | null; + + /** + * Accept + */ + "ECI": { [_: EmbeddedCustomInterface]: number } | null; + + /** + * Accept + */ + "ECIPtr": { [_: EmbeddedCustomInterface]: number } | null; + + /** + * Accept + */ + "EOI": { [_: EmbeddedOriginalInterface]: number } | null; + + /** + * Accept + */ + "EOIPtr": { [_: EmbeddedOriginalInterface]: number } | null; + + /** + * Reject + */ + "WT": { [_: string]: number } | null; + + /** + * Reject + */ + "WA": { [_: string]: number } | null; + + /** + * Accept + */ + "ST": { [_: StringType]: number } | null; + + /** + * Accept + */ + "SA": { [_: StringAlias]: number } | null; + + /** + * Accept + */ + "IntT": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "IntA": { [_: `${number}`]: number } | null; + + /** + * Reject + */ + "VT": { [_: string]: number } | null; + + /** + * Reject + */ + "VTPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "VPT": { [_: string]: number } | null; + + /** + * Reject + */ + "VPTPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "VA": { [_: ValueAlias]: number } | null; + + /** + * Accept + */ + "VAPtr": { [_: ValueAlias]: number } | null; + + /** + * Accept, hide + */ + "VPA": { [_: string]: number } | null; + + /** + * Reject + */ + "VPAPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "PT": { [_: string]: number } | null; + + /** + * Reject + */ + "PTPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "PPT": { [_: string]: number } | null; + + /** + * Reject + */ + "PPTPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "PA": { [_: string]: number } | null; + + /** + * Accept + */ + "PAPtr": { [_: PointerAlias]: number } | null; + + /** + * Accept, hide + */ + "PPA": { [_: string]: number } | null; + + /** + * Reject + */ + "PPAPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "IT": { [_: string]: number } | null; + + /** + * Reject + */ + "ITPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "IPT": { [_: string]: number } | null; + + /** + * Reject + */ + "IPTPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "IA": { [_: string]: number } | null; + + /** + * Reject + */ + "IAPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "IPA": { [_: string]: number } | null; + + /** + * Reject + */ + "IPAPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPR": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPRPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPS": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPSPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPT": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPTPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPU": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPUPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPV": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPVPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPW": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPWPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPX": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPXPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPY": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPYPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPZ": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPZPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAR": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GARPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAS": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GASPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAT": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GATPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAU": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAUPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAV": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAVPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAW": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAWPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAX": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAXPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAY": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAYPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAZ": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAZPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GACi": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "GACV": { [_: ComparableCstrAlias]: number } | null; + + /** + * Reject + */ + "GACP": { [_: string]: number } | null; + + /** + * Reject + */ + "GACiPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GACVPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GACPPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GABi": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "GABs": { [_: BasicCstrAlias]: number } | null; + + /** + * Reject + */ + "GABiPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GABT": { [_: string]: number } | null; + + /** + * Reject + */ + "GABTPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "GAGT": { [_: GoodTildeCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GAGTPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "GANBV": { [_: NonBasicCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GANBP": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GANBVPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GANBPPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "GAPlV1": { [_: PointableCstrAlias]: number } | null; + + /** + * Accept + */ + "GAPlV2": { [_: PointableCstrAlias]: number } | null; + + /** + * Reject + */ + "GAPlP1": { [_: string]: number } | null; + + /** + * Accept + */ + "GAPlP2": { [_: PointableCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GAPlVPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAPlPPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAMi": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "GAMS": { [_: MixedCstrAlias]: number } | null; + + /** + * Accept + */ + "GAMV": { [_: MixedCstrAlias]: number } | null; + + /** + * Reject + */ + "GAMSPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAMVPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAII": { [_: string]: number } | null; + + /** + * Accept + */ + "GAIV": { [_: InterfaceCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GAIP": { [_: string]: number } | null; + + /** + * Reject + */ + "GAIIPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAIVPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GAIPPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAPrV": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAPrP": { [_: string]: number } | null; + + /** + * Reject + */ + "GAPrVPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GAPrPPtr": { [_: string]: number } | null; +} + +export type MixedCstrAlias = X; + +export type NonBasicCstrAlias = V; + +export type PointableCstrAlias = W; + +export type PointerAlias = PointerTextMarshaler; + +export type PointerTextMarshaler = string; + +export type StringAlias = string; + +export type StringType = string; + +export type ValueAlias = ValueTextMarshaler; + +export type ValueTextMarshaler = string; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts new file mode 100644 index 000000000..7b59b53ed --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>> { + return $Call.ByName("main.Service.Method"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts new file mode 100644 index 000000000..7c55b767b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts @@ -0,0 +1,33 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export type { + AliasJsonMarshaler, + AliasMarshaler, + AliasNonMarshaler, + AliasTextMarshaler, + Data, + ImplicitJsonButText, + ImplicitJsonMarshaler, + ImplicitMarshaler, + ImplicitNonJson, + ImplicitNonMarshaler, + ImplicitNonText, + ImplicitTextButJson, + ImplicitTextMarshaler, + NonMarshaler, + PointerJsonMarshaler, + PointerMarshaler, + PointerTextMarshaler, + UnderlyingJsonMarshaler, + UnderlyingMarshaler, + UnderlyingTextMarshaler, + ValueJsonMarshaler, + ValueMarshaler, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts new file mode 100644 index 000000000..02d639994 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts @@ -0,0 +1,309 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + */ +export type AliasJsonMarshaler = any; + +/** + * any + */ +export type AliasMarshaler = any; + +/** + * struct{} + */ +export interface AliasNonMarshaler { +} + +/** + * string + */ +export type AliasTextMarshaler = string; + +export interface Data { + "NM": NonMarshaler; + + /** + * NonMarshaler | null + */ + "NMPtr": NonMarshaler | null; + "VJM": ValueJsonMarshaler; + + /** + * ValueJsonMarshaler | null + */ + "VJMPtr": ValueJsonMarshaler | null; + "PJM": PointerJsonMarshaler; + + /** + * PointerJsonMarshaler | null + */ + "PJMPtr": PointerJsonMarshaler | null; + "VTM": ValueTextMarshaler; + + /** + * ValueTextMarshaler | null + */ + "VTMPtr": ValueTextMarshaler | null; + "PTM": PointerTextMarshaler; + + /** + * PointerTextMarshaler | null + */ + "PTMPtr": PointerTextMarshaler | null; + "VM": ValueMarshaler; + + /** + * ValueMarshaler | null + */ + "VMPtr": ValueMarshaler | null; + "PM": PointerMarshaler; + + /** + * PointerMarshaler | null + */ + "PMPtr": PointerMarshaler | null; + "UJM": UnderlyingJsonMarshaler; + + /** + * UnderlyingJsonMarshaler | null + */ + "UJMPtr": UnderlyingJsonMarshaler | null; + "UTM": UnderlyingTextMarshaler; + + /** + * UnderlyingTextMarshaler | null + */ + "UTMPtr": UnderlyingTextMarshaler | null; + "UM": UnderlyingMarshaler; + + /** + * UnderlyingMarshaler | null + */ + "UMPtr": UnderlyingMarshaler | null; + + /** + * any + */ + "JM": any; + + /** + * any | null + */ + "JMPtr": any | null; + + /** + * string + */ + "TM": string; + + /** + * string | null + */ + "TMPtr": string | null; + + /** + * any + */ + "CJM": any; + + /** + * any | null + */ + "CJMPtr": any | null; + + /** + * string + */ + "CTM": string; + + /** + * string | null + */ + "CTMPtr": string | null; + + /** + * any + */ + "CM": any; + + /** + * any | null + */ + "CMPtr": any | null; + "ANM": AliasNonMarshaler; + + /** + * AliasNonMarshaler | null + */ + "ANMPtr": AliasNonMarshaler | null; + "AJM": AliasJsonMarshaler; + + /** + * AliasJsonMarshaler | null + */ + "AJMPtr": AliasJsonMarshaler | null; + "ATM": AliasTextMarshaler; + + /** + * AliasTextMarshaler | null + */ + "ATMPtr": AliasTextMarshaler | null; + "AM": AliasMarshaler; + + /** + * AliasMarshaler | null + */ + "AMPtr": AliasMarshaler | null; + "ImJM": ImplicitJsonMarshaler; + + /** + * ImplicitJsonMarshaler | null + */ + "ImJMPtr": ImplicitJsonMarshaler | null; + "ImTM": ImplicitTextMarshaler; + + /** + * ImplicitTextMarshaler | null + */ + "ImTMPtr": ImplicitTextMarshaler | null; + "ImM": ImplicitMarshaler; + + /** + * ImplicitMarshaler | null + */ + "ImMPtr": ImplicitMarshaler | null; + "ImNJ": ImplicitNonJson; + + /** + * ImplicitNonJson | null + */ + "ImNJPtr": ImplicitNonJson | null; + "ImNT": ImplicitNonText; + + /** + * ImplicitNonText | null + */ + "ImNTPtr": ImplicitNonText | null; + "ImNM": ImplicitNonMarshaler; + + /** + * ImplicitNonMarshaler | null + */ + "ImNMPtr": ImplicitNonMarshaler | null; + "ImJbT": ImplicitJsonButText; + + /** + * ImplicitJsonButText | null + */ + "ImJbTPtr": ImplicitJsonButText | null; + "ImTbJ": ImplicitTextButJson; + + /** + * ImplicitTextButJson | null + */ + "ImTbJPtr": ImplicitTextButJson | null; +} + +/** + * any + */ +export type ImplicitJsonButText = any; + +/** + * any + */ +export type ImplicitJsonMarshaler = any; + +/** + * any + */ +export type ImplicitMarshaler = any; + +/** + * string + */ +export type ImplicitNonJson = string; + +/** + * class{ Marshaler, TextMarshaler } + */ +export interface ImplicitNonMarshaler { + "Marshaler": json$0.Marshaler; + "TextMarshaler": encoding$0.TextMarshaler; +} + +/** + * any + */ +export type ImplicitNonText = any; + +/** + * any + */ +export type ImplicitTextButJson = any; + +/** + * string + */ +export type ImplicitTextMarshaler = string; + +/** + * class {} + */ +export interface NonMarshaler { +} + +/** + * any + */ +export type PointerJsonMarshaler = any; + +/** + * any + */ +export type PointerMarshaler = any; + +/** + * string + */ +export type PointerTextMarshaler = string; + +/** + * any + */ +export type UnderlyingJsonMarshaler = any; + +/** + * any + */ +export type UnderlyingMarshaler = any; + +/** + * string + */ +export type UnderlyingTextMarshaler = string; + +/** + * any + */ +export type ValueJsonMarshaler = any; + +/** + * any + */ +export type ValueMarshaler = any; + +/** + * string + */ +export type ValueTextMarshaler = string; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts new file mode 100644 index 000000000..0de2fecd5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Data> { + return $Call.ByName("main.Service.Method"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts new file mode 100644 index 000000000..bf611e486 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +export type { + HowDifferent, + Impersonator, + Person, + PrivatePerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts new file mode 100644 index 000000000..c42cce373 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts @@ -0,0 +1,64 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + */ +export interface HowDifferent { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": ({ [_: string]: How } | null)[] | null; +} + +/** + * Impersonator gets their fields from other people. + */ +export type Impersonator = other$0.OtherPerson; + +/** + * Person is not a number. + */ +export interface Person { + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; +} + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export type PrivatePerson = personImpl; + +export interface personImpl { + /** + * Nickname conceals a person's identity. + */ + "Nickname": string; + + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.ts new file mode 100644 index 000000000..ac743f356 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + StringPtr +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.ts new file mode 100644 index 000000000..6a5a12300 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more/models.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * StringPtr is a nullable string. + */ +export type StringPtr = string | null; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts new file mode 100644 index 000000000..b9f2889db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +export type { + OtherPerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts new file mode 100644 index 000000000..6ca5b82a2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts @@ -0,0 +1,17 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherPerson is like a person, but different. + */ +export interface OtherPerson { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": T[] | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts new file mode 100644 index 000000000..e6638332a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other.OtherMethods.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts new file mode 100644 index 000000000..e37a596df --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOne"); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts new file mode 100644 index 000000000..ec7619bfc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("main.EmbedOther.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts new file mode 100644 index 000000000..680b028b4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]> { + return $Call.ByName("main.EmbedService.LikeThisOne"); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("main.EmbedService.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts new file mode 100644 index 000000000..4166b32f7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet($0: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts new file mode 100644 index 000000000..e69bebf3b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts new file mode 100644 index 000000000..41664b850 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts new file mode 100644 index 000000000..5a733e6c9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts new file mode 100644 index 000000000..41664b850 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts new file mode 100644 index 000000000..5a733e6c9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts new file mode 100644 index 000000000..5a8e9adc3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts new file mode 100644 index 000000000..3ab21d612 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +export interface Person { + "Name": string; + "Address": services$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts new file mode 100644 index 000000000..018d8df30 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts new file mode 100644 index 000000000..b8abcb9a6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts @@ -0,0 +1,190 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise<{ [_: `${number}`]: number[] | null } | null> { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +export function StringArrayInputNamedOutput($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in); +} + +export function StringArrayInputNamedOutputs($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in); +} + +export function StringArrayInputStringArrayOut($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in); +} + +export function StringArrayInputStringOut($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts new file mode 100644 index 000000000..5664b79ab --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts new file mode 100644 index 000000000..b8abcb9a6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts @@ -0,0 +1,190 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise<{ [_: `${number}`]: number[] | null } | null> { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +export function StringArrayInputNamedOutput($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in); +} + +export function StringArrayInputNamedOutputs($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in); +} + +export function StringArrayInputStringArrayOut($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in); +} + +export function StringArrayInputStringOut($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts new file mode 100644 index 000000000..5664b79ab --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts new file mode 100644 index 000000000..80bebe425 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts new file mode 100644 index 000000000..80bebe425 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts new file mode 100644 index 000000000..5a8e9adc3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts new file mode 100644 index 000000000..f29117203 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts @@ -0,0 +1,15 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + */ +export interface Person { + "Name": string; + "Address": services$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts new file mode 100644 index 000000000..c93f85314 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/warnings.log b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/warnings.log new file mode 100644 index 000000000..ce8369307 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/warnings.log @@ -0,0 +1,83 @@ +[warn] /testcases/complex_json/main.go:127:2: event 'collision' has one of multiple definitions here with data type map[string]int +[warn] /testcases/events_only/events.go:20:2: event 'collision' has one of multiple definitions here with data type int +[warn] /testcases/events_only/events.go:21:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:10:5: `application.RegisterEvent` is instantiated here but not called +[warn] /testcases/events_only/other.go:13:2: `application.RegisterEvent` called here with non-constant event name +[warn] /testcases/events_only/other.go:17:2: data type []T for event 'parametric' contains unresolved type parameters and will be ignored` +[warn] /testcases/events_only/other.go:22:2: event 'common:ApplicationStarted' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic +[warn] /testcases/marshalers/main.go:212:2: event 'collision' has one of multiple definitions here with data type *struct{Field []bool} +[warn] /testcases/marshalers/main.go:214:2: data type encoding/json.Marshaler for event 'interface' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors +[warn] dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only +[warn] event 'collision' has multiple conflicting definitions and will be ignored +[warn] events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly +[warn] generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +[warn] package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/package-lock.json b/v3/internal/generator/testdata/package-lock.json new file mode 100644 index 000000000..80f5e0882 --- /dev/null +++ b/v3/internal/generator/testdata/package-lock.json @@ -0,0 +1,2274 @@ +{ + "name": "testdata", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "testdata", + "version": "0.0.0", + "devDependencies": { + "madge": "^8.0.0", + "typescript": "^5.7.3" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", + "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", + "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@dependents/detective-less": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@dependents/detective-less/-/detective-less-5.0.0.tgz", + "integrity": "sha512-D/9dozteKcutI5OdxJd8rU+fL6XgaaRg60sPPJWkT33OCiRfkCu5wO5B/yXTaaL2e6EB0lcCBGe5E0XscZCvvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@ts-graphviz/adapter": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ts-graphviz/adapter/-/adapter-2.0.6.tgz", + "integrity": "sha512-kJ10lIMSWMJkLkkCG5gt927SnGZcBuG0s0HHswGzcHTgvtUe7yk5/3zTEr0bafzsodsOq5Gi6FhQeV775nC35Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/common": "^2.1.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/ast": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@ts-graphviz/ast/-/ast-2.0.7.tgz", + "integrity": "sha512-e6+2qtNV99UT6DJSoLbHfkzfyqY84aIuoV8Xlb9+hZAjgpum8iVHprGeAMQ4rF6sKUAxrmY8rfF/vgAwoPc3gw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/common": "^2.1.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/common": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@ts-graphviz/common/-/common-2.1.5.tgz", + "integrity": "sha512-S6/9+T6x8j6cr/gNhp+U2olwo1n0jKj/682QVqsh7yXWV6ednHYqxFw0ZsY3LyzT0N8jaZ6jQY9YD99le3cmvg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/core": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@ts-graphviz/core/-/core-2.0.7.tgz", + "integrity": "sha512-w071DSzP94YfN6XiWhOxnLpYT3uqtxJBDYdh6Jdjzt+Ce6DNspJsPQgpC7rbts/B8tEkq0LHoYuIF/O5Jh5rPg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/ast": "^2.0.7", + "@ts-graphviz/common": "^2.1.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-module-path": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz", + "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ast-module-types": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-6.0.0.tgz", + "integrity": "sha512-LFRg7178Fw5R4FAEwZxVqiRI8IxSM+Ay2UBrHoCerXNme+kMMMfz7T3xDGV/c2fer87hcrtgJGsnSOfUrPK6ng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dependency-tree": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/dependency-tree/-/dependency-tree-11.0.1.tgz", + "integrity": "sha512-eCt7HSKIC9NxgIykG2DRq3Aewn9UhVS14MB3rEn6l/AsEI1FBg6ZGSlCU0SZ6Tjm2kkhj6/8c2pViinuyKELhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^12.0.0", + "filing-cabinet": "^5.0.1", + "precinct": "^12.0.2", + "typescript": "^5.4.5" + }, + "bin": { + "dependency-tree": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/dependency-tree/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-amd": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-6.0.0.tgz", + "integrity": "sha512-NTqfYfwNsW7AQltKSEaWR66hGkTeD52Kz3eRQ+nfkA9ZFZt3iifRCWh+yZ/m6t3H42JFwVFTrml/D64R2PAIOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.0", + "escodegen": "^2.1.0", + "get-amd-module-type": "^6.0.0", + "node-source-walk": "^7.0.0" + }, + "bin": { + "detective-amd": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-cjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detective-cjs/-/detective-cjs-6.0.0.tgz", + "integrity": "sha512-R55jTS6Kkmy6ukdrbzY4x+I7KkXiuDPpFzUViFV/tm2PBGtTCjkh9ZmTuJc1SaziMHJOe636dtiZLEuzBL9drg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-es6": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detective-es6/-/detective-es6-5.0.0.tgz", + "integrity": "sha512-NGTnzjvgeMW1khUSEXCzPDoraLenWbUjCFjwxReH+Ir+P6LGjYtaBbAvITWn2H0VSC+eM7/9LFOTAkrta6hNYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-postcss": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-7.0.0.tgz", + "integrity": "sha512-pSXA6dyqmBPBuERpoOKKTUUjQCZwZPLRbd1VdsTbt6W+m/+6ROl4BbE87yQBUtLoK7yX8pvXHdKyM/xNIW9F7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-url": "^1.2.4", + "postcss-values-parser": "^6.0.2" + }, + "engines": { + "node": "^14.0.0 || >=16.0.0" + }, + "peerDependencies": { + "postcss": "^8.4.38" + } + }, + "node_modules/detective-sass": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detective-sass/-/detective-sass-6.0.0.tgz", + "integrity": "sha512-h5GCfFMkPm4ZUUfGHVPKNHKT8jV7cSmgK+s4dgQH4/dIUNh9/huR1fjEQrblOQNDalSU7k7g+tiW9LJ+nVEUhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-scss": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detective-scss/-/detective-scss-5.0.0.tgz", + "integrity": "sha512-Y64HyMqntdsCh1qAH7ci95dk0nnpA29g319w/5d/oYcHolcGUVJbIhOirOFjfN1KnMAXAFm5FIkZ4l2EKFGgxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-stylus": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detective-stylus/-/detective-stylus-5.0.0.tgz", + "integrity": "sha512-KMHOsPY6aq3196WteVhkY5FF+6Nnc/r7q741E+Gq+Ax9mhE2iwj8Hlw8pl+749hPDRDBHZ2WlgOjP+twIG61vQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-typescript": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/detective-typescript/-/detective-typescript-13.0.0.tgz", + "integrity": "sha512-tcMYfiFWoUejSbvSblw90NDt76/4mNftYCX0SMnVRYzSXv8Fvo06hi4JOPdNvVNxRtCAKg3MJ3cBJh+ygEMH+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "^7.6.0", + "ast-module-types": "^6.0.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": "^14.14.0 || >=16.0.0" + }, + "peerDependencies": { + "typescript": "^5.4.4" + } + }, + "node_modules/detective-vue2": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/detective-vue2/-/detective-vue2-2.1.1.tgz", + "integrity": "sha512-/TQ+cs4qmSyhgESjyBXxoUuh36XjS06+UhCItWcGGOpXmU3KBRGRknG+tDzv2dASn1+UJUm2rhpDFa9TWT0dFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dependents/detective-less": "^5.0.0", + "@vue/compiler-sfc": "^3.5.13", + "detective-es6": "^5.0.0", + "detective-sass": "^6.0.0", + "detective-scss": "^5.0.0", + "detective-stylus": "^5.0.0", + "detective-typescript": "^13.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "typescript": "^5.4.4" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/filing-cabinet": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-5.0.2.tgz", + "integrity": "sha512-RZlFj8lzyu6jqtFBeXNqUjjNG6xm+gwXue3T70pRxw1W40kJwlgq0PSWAmh0nAnn5DHuBIecLXk9+1VKS9ICXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-module-path": "^2.2.0", + "commander": "^12.0.0", + "enhanced-resolve": "^5.16.0", + "module-definition": "^6.0.0", + "module-lookup-amd": "^9.0.1", + "resolve": "^1.22.8", + "resolve-dependency-path": "^4.0.0", + "sass-lookup": "^6.0.1", + "stylus-lookup": "^6.0.0", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.4.4" + }, + "bin": { + "filing-cabinet": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/filing-cabinet/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-amd-module-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-amd-module-type/-/get-amd-module-type-6.0.0.tgz", + "integrity": "sha512-hFM7oivtlgJ3d6XWD6G47l8Wyh/C6vFw5G24Kk1Tbq85yh5gcM8Fne5/lFhiuxB+RT6+SI7I1ThB9lG4FBh3jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gonzales-pe": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", + "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "gonzales": "bin/gonzales.js" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-url-superb": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz", + "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/madge": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/madge/-/madge-8.0.0.tgz", + "integrity": "sha512-9sSsi3TBPhmkTCIpVQF0SPiChj1L7Rq9kU2KDG1o6v2XH9cCw086MopjVCD+vuoL5v8S77DTbVopTO8OUiQpIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "commander": "^7.2.0", + "commondir": "^1.0.1", + "debug": "^4.3.4", + "dependency-tree": "^11.0.0", + "ora": "^5.4.1", + "pluralize": "^8.0.0", + "pretty-ms": "^7.0.1", + "rc": "^1.2.8", + "stream-to-array": "^2.3.0", + "ts-graphviz": "^2.1.2", + "walkdir": "^0.4.1" + }, + "bin": { + "madge": "bin/cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/pahen" + }, + "peerDependencies": { + "typescript": "^5.4.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/module-definition": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-6.0.0.tgz", + "integrity": "sha512-sEGP5nKEXU7fGSZUML/coJbrO+yQtxcppDAYWRE9ovWsTbFoUHB2qDUx564WUzDaBHXsD46JBbIK5WVTwCyu3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.0", + "node-source-walk": "^7.0.0" + }, + "bin": { + "module-definition": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/module-lookup-amd": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/module-lookup-amd/-/module-lookup-amd-9.0.2.tgz", + "integrity": "sha512-p7PzSVEWiW9fHRX9oM+V4aV5B2nCVddVNv4DZ/JB6t9GsXY4E+ZVhPpnwUX7bbJyGeeVZqhS8q/JZ/H77IqPFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^12.1.0", + "glob": "^7.2.3", + "requirejs": "^2.3.7", + "requirejs-config-file": "^4.0.0" + }, + "bin": { + "lookup-amd": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/module-lookup-amd/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-source-walk": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-source-walk/-/node-source-walk-7.0.0.tgz", + "integrity": "sha512-1uiY543L+N7Og4yswvlm5NCKgPKDEXd9AUR9Jh3gen6oOeBsesr6LqhXom1er3eRzSUcVRWXzhv8tSNrIfGHKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.24.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-values-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz", + "integrity": "sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "color-name": "^1.1.4", + "is-url-superb": "^4.0.0", + "quote-unquote": "^1.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.2.9" + } + }, + "node_modules/precinct": { + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/precinct/-/precinct-12.1.2.tgz", + "integrity": "sha512-x2qVN3oSOp3D05ihCd8XdkIPuEQsyte7PSxzLqiRgktu79S5Dr1I75/S+zAup8/0cwjoiJTQztE9h0/sWp9bJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dependents/detective-less": "^5.0.0", + "commander": "^12.1.0", + "detective-amd": "^6.0.0", + "detective-cjs": "^6.0.0", + "detective-es6": "^5.0.0", + "detective-postcss": "^7.0.0", + "detective-sass": "^6.0.0", + "detective-scss": "^5.0.0", + "detective-stylus": "^5.0.0", + "detective-typescript": "^13.0.0", + "detective-vue2": "^2.0.3", + "module-definition": "^6.0.0", + "node-source-walk": "^7.0.0", + "postcss": "^8.4.40", + "typescript": "^5.5.4" + }, + "bin": { + "precinct": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/precinct/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quote-unquote": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quote-unquote/-/quote-unquote-1.0.0.tgz", + "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/requirejs": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.7.tgz", + "integrity": "sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw==", + "dev": true, + "license": "MIT", + "bin": { + "r_js": "bin/r.js", + "r.js": "bin/r.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/requirejs-config-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz", + "integrity": "sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esprima": "^4.0.0", + "stringify-object": "^3.2.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-dependency-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-dependency-path/-/resolve-dependency-path-4.0.0.tgz", + "integrity": "sha512-hlY1SybBGm5aYN3PC4rp15MzsJLM1w+MEA/4KU3UBPfz4S0lL3FL6mgv7JgaA8a+ZTeEQAiF1a1BuN2nkqiIlg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/sass-lookup": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/sass-lookup/-/sass-lookup-6.0.1.tgz", + "integrity": "sha512-nl9Wxbj9RjEJA5SSV0hSDoU2zYGtE+ANaDS4OFUR7nYrquvBFvPKZZtQHe3lvnxCcylEDV00KUijjdMTUElcVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^12.0.0" + }, + "bin": { + "sass-lookup": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/sass-lookup/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-to-array": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz", + "integrity": "sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylus-lookup": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/stylus-lookup/-/stylus-lookup-6.0.0.tgz", + "integrity": "sha512-RaWKxAvPnIXrdby+UWCr1WRfa+lrPMSJPySte4Q6a+rWyjeJyFOLJxr5GrAVfcMCsfVlCuzTAJ/ysYT8p8do7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^12.0.0" + }, + "bin": { + "stylus-lookup": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/stylus-lookup/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-graphviz": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/ts-graphviz/-/ts-graphviz-2.1.6.tgz", + "integrity": "sha512-XyLVuhBVvdJTJr2FJJV2L1pc4MwSjMhcunRVgDE9k4wbb2ee7ORYnPewxMWUav12vxyfUM686MSGsqnVRIInuw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/adapter": "^2.0.6", + "@ts-graphviz/ast": "^2.0.7", + "@ts-graphviz/common": "^2.1.5", + "@ts-graphviz/core": "^2.0.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/walkdir": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", + "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/v3/internal/generator/testdata/package.json b/v3/internal/generator/testdata/package.json new file mode 100644 index 000000000..4ad95cc64 --- /dev/null +++ b/v3/internal/generator/testdata/package.json @@ -0,0 +1,10 @@ +{ + "name": "testdata", + "version": "0.0.0", + "description": "Output from generator testcases. This package.json is here only to pull in the Typescript compiler as a dependency.", + "type": "module", + "devDependencies": { + "typescript": "^5.7.3", + "madge": "^8.0.0" + } +} diff --git a/v3/internal/generator/testdata/tsconfig.json b/v3/internal/generator/testdata/tsconfig.json new file mode 100644 index 000000000..20c5ee1e0 --- /dev/null +++ b/v3/internal/generator/testdata/tsconfig.json @@ -0,0 +1,41 @@ +{ + "include": [ + "output/**/*.js", + "output/**/*.ts" + ], + "exclude": [ + "output/**/*.d.ts", + "output/**/*.got.?s" + ], + "references": [ + { "path": "../../runtime/desktop/@wailsio/runtime" } + ], + "compilerOptions": { + "allowJs": true, + + "noEmit": true, + "skipLibCheck": false, + + "target": "ES2015", + "module": "ES2015", + "moduleResolution": "bundler", + "isolatedModules": true, + "verbatimModuleSyntax": true, + + "lib": [ + "DOM", + "ESNext" + ], + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + + "paths": { + "/wails/runtime.js": ["../../runtime/desktop/@wailsio/runtime"] + } + } +} diff --git a/v3/internal/github/github.go b/v3/internal/github/github.go new file mode 100644 index 000000000..da534177a --- /dev/null +++ b/v3/internal/github/github.go @@ -0,0 +1,142 @@ +package github + +import ( + "encoding/json" + "fmt" + "github.com/charmbracelet/glamour/styles" + "io" + "net/http" + "net/url" + "sort" + "strings" + + "github.com/charmbracelet/glamour" +) + +func GetReleaseNotes(tagVersion string, noColour bool) string { + resp, err := http.Get("https://api.github.com/repos/wailsapp/wails/releases/tags/" + url.PathEscape(tagVersion)) + if err != nil { + return "Unable to retrieve release notes. Please check your network connection" + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return "Unable to retrieve release notes. Please check your network connection" + } + + data := map[string]interface{}{} + err = json.Unmarshal(body, &data) + if err != nil { + return "Unable to retrieve release notes. Please check your network connection" + } + + if data["body"] == nil { + return "No release notes found" + } + + result := "# Release Notes for " + tagVersion + "\n" + data["body"].(string) + var renderer *glamour.TermRenderer + + if noColour { + renderer, err = glamour.NewTermRenderer(glamour.WithStyles(styles.NoTTYStyleConfig)) + } else { + renderer, err = glamour.NewTermRenderer(glamour.WithAutoStyle()) + } + if err != nil { + return result + } + result, err = renderer.Render(result) + if err != nil { + return err.Error() + } + return result +} + +// GetVersionTags gets the list of tags on the Wails repo +// It returns a list of sorted tags in descending order +func GetVersionTags() ([]*SemanticVersion, error) { + result := []*SemanticVersion{} + var err error + + resp, err := http.Get("https://api.github.com/repos/wailsapp/wails/tags") + if err != nil { + return result, err + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return result, err + } + + data := []map[string]interface{}{} + err = json.Unmarshal(body, &data) + if err != nil { + return result, err + } + + // Convert tag data to Version structs + for _, tag := range data { + version := tag["name"].(string) + if !strings.HasPrefix(version, "v2") { + continue + } + semver, err := NewSemanticVersion(version) + if err != nil { + return result, err + } + result = append(result, semver) + } + + // Reverse Sort + sort.Sort(sort.Reverse(SemverCollection(result))) + + return result, err +} + +// GetLatestStableRelease gets the latest stable release on GitHub +func GetLatestStableRelease() (result *SemanticVersion, err error) { + tags, err := GetVersionTags() + if err != nil { + return nil, err + } + + for _, tag := range tags { + if tag.IsRelease() { + return tag, nil + } + } + + return nil, fmt.Errorf("no release tag found") +} + +// GetLatestPreRelease gets the latest prerelease on GitHub +func GetLatestPreRelease() (result *SemanticVersion, err error) { + tags, err := GetVersionTags() + if err != nil { + return nil, err + } + + for _, tag := range tags { + if tag.IsPreRelease() { + return tag, nil + } + } + + return nil, fmt.Errorf("no prerelease tag found") +} + +// IsValidTag returns true if the given string is a valid tag +func IsValidTag(tagVersion string) (bool, error) { + if tagVersion[0] == 'v' { + tagVersion = tagVersion[1:] + } + tags, err := GetVersionTags() + if err != nil { + return false, err + } + + for _, tag := range tags { + if tag.String() == tagVersion { + return true, nil + } + } + return false, nil +} diff --git a/v3/internal/github/semver.go b/v3/internal/github/semver.go new file mode 100644 index 000000000..9062f8820 --- /dev/null +++ b/v3/internal/github/semver.go @@ -0,0 +1,106 @@ +package github + +import ( + "fmt" + + "github.com/Masterminds/semver" +) + +const majorVersion = 3 + +// SemanticVersion is a struct containing a semantic version +type SemanticVersion struct { + Version *semver.Version +} + +// NewSemanticVersion creates a new SemanticVersion object with the given version string +func NewSemanticVersion(version string) (*SemanticVersion, error) { + semverVersion, err := semver.NewVersion(version) + if err != nil { + return nil, err + } + return &SemanticVersion{ + Version: semverVersion, + }, nil +} + +// IsRelease returns true if it's a release version +func (s *SemanticVersion) IsRelease() bool { + if s.Version.Major() != majorVersion { + return false + } + return len(s.Version.Prerelease()) == 0 && len(s.Version.Metadata()) == 0 +} + +// IsPreRelease returns true if it's a prerelease version +func (s *SemanticVersion) IsPreRelease() bool { + if s.Version.Major() != majorVersion { + return false + } + return len(s.Version.Prerelease()) > 0 +} + +func (s *SemanticVersion) String() string { + return s.Version.String() +} + +// IsGreaterThan returns true if this version is greater than the given version +func (s *SemanticVersion) IsGreaterThan(version *SemanticVersion) (bool, error) { + // Set up new constraint + constraint, err := semver.NewConstraint("> " + version.Version.String()) + if err != nil { + return false, err + } + + // Check if the desired one is greater than the requested on + success, msgs := constraint.Validate(s.Version) + if !success { + return false, msgs[0] + } + return true, nil +} + +// IsGreaterThanOrEqual returns true if this version is greater than or equal the given version +func (s *SemanticVersion) IsGreaterThanOrEqual(version *SemanticVersion) (bool, error) { + // Set up new constraint + constraint, err := semver.NewConstraint(">= " + version.Version.String()) + if err != nil { + return false, err + } + + // Check if the desired one is greater than the requested on + success, msgs := constraint.Validate(s.Version) + if !success { + return false, msgs[0] + } + return true, nil +} + +// MainVersion returns the main version of any version+prerelease+metadata +// EG: MainVersion("1.2.3-pre") => "1.2.3" +func (s *SemanticVersion) MainVersion() *SemanticVersion { + mainVersion := fmt.Sprintf("%d.%d.%d", s.Version.Major(), s.Version.Minor(), s.Version.Patch()) + result, _ := NewSemanticVersion(mainVersion) + return result +} + +// SemverCollection is a collection of SemanticVersion objects +type SemverCollection []*SemanticVersion + +// Len returns the length of a collection. The number of Version instances +// on the slice. +func (c SemverCollection) Len() int { + return len(c) +} + +// Less is needed for the sort interface to compare two Version objects on the +// slice. If checks if one is less than the other. +func (c SemverCollection) Less(i, j int) bool { + return c[i].Version.LessThan(c[j].Version) +} + +// Swap is needed for the sort interface to replace the Version objects +// at two different positions in the slice. +func (c SemverCollection) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} diff --git a/v3/internal/github/semver_test.go b/v3/internal/github/semver_test.go new file mode 100644 index 000000000..b45e1b3aa --- /dev/null +++ b/v3/internal/github/semver_test.go @@ -0,0 +1,43 @@ +package github + +import ( + "github.com/matryer/is" + "testing" +) + +func TestSemanticVersion_IsGreaterThan(t *testing.T) { + is2 := is.New(t) + + alpha1, err := NewSemanticVersion("v3.0.0-alpha.1") + is2.NoErr(err) + + beta1, err := NewSemanticVersion("v3.0.0-beta.1") + is2.NoErr(err) + + v2, err := NewSemanticVersion("v3.0.0") + is2.NoErr(err) + + is2.True(alpha1.IsPreRelease()) + is2.True(beta1.IsPreRelease()) + is2.True(!v2.IsPreRelease()) + is2.True(v2.IsRelease()) + + result, err := beta1.IsGreaterThan(alpha1) + is2.NoErr(err) + is2.True(result) + + result, err = v2.IsGreaterThan(beta1) + is2.NoErr(err) + is2.True(result) + + beta44, err := NewSemanticVersion("v2.0.0-beta.44.2") + is2.NoErr(err) + + rc1, err := NewSemanticVersion("v2.0.0-rc.1") + is2.NoErr(err) + + result, err = rc1.IsGreaterThan(beta44) + is2.NoErr(err) + is2.True(result) + +} diff --git a/v3/internal/go-common-file-dialog/LICENSE b/v3/internal/go-common-file-dialog/LICENSE new file mode 100644 index 000000000..508b6978e --- /dev/null +++ b/v3/internal/go-common-file-dialog/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Harry Phillips + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/v3/internal/go-common-file-dialog/README.md b/v3/internal/go-common-file-dialog/README.md new file mode 100644 index 000000000..1cb5902d1 --- /dev/null +++ b/v3/internal/go-common-file-dialog/README.md @@ -0,0 +1,31 @@ +# Common File Dialog bindings for Golang + +[Project Home](https://github.com/harry1453/go-common-file-dialog) + +This library contains bindings for Windows Vista and +newer's [Common File Dialogs](https://docs.microsoft.com/en-us/windows/win32/shell/common-file-dialog), which is the +standard system dialog for selecting files or folders to open or save. + +The Common File Dialogs have to be accessed via +the [COM Interface](https://en.wikipedia.org/wiki/Component_Object_Model), normally via C++ or via bindings (like in C#) +. + +This library contains bindings for Golang. **It does not require CGO**, and contains empty stubs for non-windows +platforms (so is safe to compile and run on platforms other than windows, but will just return errors at runtime). + +This can be very useful if you want to quickly get a file selector in your Golang application. The `cfdutil` package +contains utility functions with a single call to open and configure a dialog, and then get the result from it. Examples +for this are in [`_examples/usingutil`](_examples/usingutil). Or, if you want finer control over the dialog's operation, +you can use the base package. Examples for this are in [`_examples/notusingutil`](_examples/notusingutil). + +This library is available under the MIT license. + +Currently supported features: + +* Open File Dialog (to open a single file) +* Open Multiple Files Dialog (to open multiple files) +* Open Folder Dialog +* Save File Dialog +* Dialog "roles" to allow Windows to remember different "last locations" for different types of dialog +* Set dialog Title, Default Folder and Initial Folder +* Set dialog File Filters diff --git a/v3/internal/go-common-file-dialog/cfd/CommonFileDialog.go b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog.go new file mode 100644 index 000000000..58e97aa4e --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog.go @@ -0,0 +1,72 @@ +// Cross-platform. + +// Common File Dialogs +package cfd + +type Dialog interface { + // Show the dialog to the user. + // Blocks until the user has closed the dialog. + Show() error + // Sets the dialog's parent window. Use 0 to set the dialog to have no parent window. + SetParentWindowHandle(hwnd uintptr) + // Show the dialog to the user. + // Blocks until the user has closed the dialog and returns their selection. + // Returns an error if the user cancelled the dialog. + // Do not use for the Open Multiple Files dialog. Use ShowAndGetResults instead. + ShowAndGetResult() (string, error) + // Sets the title of the dialog window. + SetTitle(title string) error + // Sets the "role" of the dialog. This is used to derive the dialog's GUID, which the + // OS will use to differentiate it from dialogs that are intended for other purposes. + // This means that, for example, a dialog with role "Import" will have a different + // previous location that it will open to than a dialog with role "Open". Can be any string. + SetRole(role string) error + // Sets the folder used as a default if there is not a recently used folder value available + SetDefaultFolder(defaultFolder string) error + // Sets the folder that the dialog always opens to. + // If this is set, it will override the "default folder" behaviour and the dialog will always open to this folder. + SetFolder(folder string) error + // Gets the selected file or folder path, as an absolute path eg. "C:\Folder\file.txt" + // Do not use for the Open Multiple Files dialog. Use GetResults instead. + GetResult() (string, error) + // Sets the file name, I.E. the contents of the file name text box. + // For Select Folder Dialog, sets folder name. + SetFileName(fileName string) error + // Release the resources allocated to this Dialog. + // Should be called when the dialog is finished with. + Release() error +} + +type FileDialog interface { + Dialog + // Set the list of file filters that the user can select. + SetFileFilters(fileFilter []FileFilter) error + // Set the selected item from the list of file filters (set using SetFileFilters) by its index. Defaults to 0 (the first item in the list) if not called. + SetSelectedFileFilterIndex(index uint) error + // Sets the default extension applied when a user does not provide one as part of the file name. + // If the user selects a different file filter, the default extension will be automatically updated to match the new file filter. + // For Open / Open Multiple File Dialog, this only has an effect when the user specifies a file name with no extension and a file with the default extension exists. + // For Save File Dialog, this extension will be used whenever a user does not specify an extension. + SetDefaultExtension(defaultExtension string) error +} + +type OpenFileDialog interface { + FileDialog +} + +type OpenMultipleFilesDialog interface { + FileDialog + // Show the dialog to the user. + // Blocks until the user has closed the dialog and returns the selected files. + ShowAndGetResults() ([]string, error) + // Gets the selected file paths, as absolute paths eg. "C:\Folder\file.txt" + GetResults() ([]string, error) +} + +type SelectFolderDialog interface { + Dialog +} + +type SaveFileDialog interface { // TODO Properties + FileDialog +} diff --git a/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_nonWindows.go b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_nonWindows.go new file mode 100644 index 000000000..3ab969850 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_nonWindows.go @@ -0,0 +1,28 @@ +//go:build !windows +// +build !windows + +package cfd + +import "fmt" + +var unsupportedError = fmt.Errorf("common file dialogs are only available on windows") + +// TODO doc +func NewOpenFileDialog(config DialogConfig) (OpenFileDialog, error) { + return nil, unsupportedError +} + +// TODO doc +func NewOpenMultipleFilesDialog(config DialogConfig) (OpenMultipleFilesDialog, error) { + return nil, unsupportedError +} + +// TODO doc +func NewSelectFolderDialog(config DialogConfig) (SelectFolderDialog, error) { + return nil, unsupportedError +} + +// TODO doc +func NewSaveFileDialog(config DialogConfig) (SaveFileDialog, error) { + return nil, unsupportedError +} diff --git a/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_windows.go b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_windows.go new file mode 100644 index 000000000..69f46118e --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_windows.go @@ -0,0 +1,79 @@ +//go:build windows +// +build windows + +package cfd + +import "github.com/go-ole/go-ole" + +func initialize() { + // Swallow error + _ = ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_DISABLE_OLE1DDE) +} + +// TODO doc +func NewOpenFileDialog(config DialogConfig) (OpenFileDialog, error) { + initialize() + + openDialog, err := newIFileOpenDialog() + if err != nil { + return nil, err + } + err = config.apply(openDialog) + if err != nil { + return nil, err + } + return openDialog, nil +} + +// TODO doc +func NewOpenMultipleFilesDialog(config DialogConfig) (OpenMultipleFilesDialog, error) { + initialize() + + openDialog, err := newIFileOpenDialog() + if err != nil { + return nil, err + } + err = config.apply(openDialog) + if err != nil { + return nil, err + } + err = openDialog.setIsMultiselect(true) + if err != nil { + return nil, err + } + return openDialog, nil +} + +// TODO doc +func NewSelectFolderDialog(config DialogConfig) (SelectFolderDialog, error) { + initialize() + + openDialog, err := newIFileOpenDialog() + if err != nil { + return nil, err + } + err = config.apply(openDialog) + if err != nil { + return nil, err + } + err = openDialog.setPickFolders(true) + if err != nil { + return nil, err + } + return openDialog, nil +} + +// TODO doc +func NewSaveFileDialog(config DialogConfig) (SaveFileDialog, error) { + initialize() + + saveDialog, err := newIFileSaveDialog() + if err != nil { + return nil, err + } + err = config.apply(saveDialog) + if err != nil { + return nil, err + } + return saveDialog, nil +} diff --git a/v3/internal/go-common-file-dialog/cfd/DialogConfig.go b/v3/internal/go-common-file-dialog/cfd/DialogConfig.go new file mode 100644 index 000000000..800573ed2 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/DialogConfig.go @@ -0,0 +1,141 @@ +// Cross-platform. + +package cfd + +import ( + "reflect" + "fmt" + "os" +) + +type FileFilter struct { + // The display name of the filter (That is shown to the user) + DisplayName string + // The filter pattern. Eg. "*.txt;*.png" to select all txt and png files, "*.*" to select any files, etc. + Pattern string +} + +// Never obfuscate the FileFilter type. +var _ = reflect.TypeOf(FileFilter{}) + +type DialogConfig struct { + // The title of the dialog + Title string + // The role of the dialog. This is used to derive the dialog's GUID, which the + // OS will use to differentiate it from dialogs that are intended for other purposes. + // This means that, for example, a dialog with role "Import" will have a different + // previous location that it will open to than a dialog with role "Open". Can be any string. + Role string + // The default folder - the folder that is used the first time the user opens it + // (after the first time their last used location is used). + DefaultFolder string + // The initial folder - the folder that the dialog always opens to if not empty. + // If this is not empty, it will override the "default folder" behaviour and + // the dialog will always open to this folder. + Folder string + // The file filters that restrict which types of files the dialog is able to choose. + // Ignored by Select Folder Dialog. + FileFilters []FileFilter + // Sets the initially selected file filter. This is an index of FileFilters. + // Ignored by Select Folder Dialog. + SelectedFileFilterIndex uint + // The initial name of the file (I.E. the text in the file name text box) when the user opens the dialog. + // For the Select Folder Dialog, this sets the initial folder name. + FileName string + // The default extension applied when a user does not provide one as part of the file name. + // If the user selects a different file filter, the default extension will be automatically updated to match the new file filter. + // For Open / Open Multiple File Dialog, this only has an effect when the user specifies a file name with no extension and a file with the default extension exists. + // For Save File Dialog, this extension will be used whenever a user does not specify an extension. + // Ignored by Select Folder Dialog. + DefaultExtension string + // ParentWindowHandle is the handle (HWND) to the parent window of the dialog. + // If left as 0 / nil, the dialog will have no parent window. + ParentWindowHandle uintptr +} + +var defaultFilters = []FileFilter{ + { + DisplayName: "All Files (*.*)", + Pattern: "*.*", + }, +} + +func (config *DialogConfig) apply(dialog Dialog) (err error) { + if config.Title != "" { + err = dialog.SetTitle(config.Title) + if err != nil { + return + } + } + + if config.Role != "" { + err = dialog.SetRole(config.Role) + if err != nil { + return + } + } + + if config.Folder != "" { + _, err = os.Stat(config.Folder) + if err != nil { + return + } + err = dialog.SetFolder(config.Folder) + if err != nil { + return + } + } + + if config.DefaultFolder != "" { + _, err = os.Stat(config.DefaultFolder) + if err != nil { + return + } + err = dialog.SetDefaultFolder(config.DefaultFolder) + if err != nil { + return + } + } + + if config.FileName != "" { + err = dialog.SetFileName(config.FileName) + if err != nil { + return + } + } + + dialog.SetParentWindowHandle(config.ParentWindowHandle) + + if dialog, ok := dialog.(FileDialog); ok { + var fileFilters []FileFilter + if config.FileFilters != nil && len(config.FileFilters) > 0 { + fileFilters = config.FileFilters + } else { + fileFilters = defaultFilters + } + err = dialog.SetFileFilters(fileFilters) + if err != nil { + return + } + + if config.SelectedFileFilterIndex != 0 { + if config.SelectedFileFilterIndex > uint(len(fileFilters)) { + err = fmt.Errorf("selected file filter index out of range") + return + } + err = dialog.SetSelectedFileFilterIndex(config.SelectedFileFilterIndex) + if err != nil { + return + } + } + + if config.DefaultExtension != "" { + err = dialog.SetDefaultExtension(config.DefaultExtension) + if err != nil { + return + } + } + } + + return +} diff --git a/v3/internal/go-common-file-dialog/cfd/errors.go b/v3/internal/go-common-file-dialog/cfd/errors.go new file mode 100644 index 000000000..c097c8eb2 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/errors.go @@ -0,0 +1,7 @@ +package cfd + +import "errors" + +var ( + ErrorCancelled = errors.New("cancelled by user") +) diff --git a/v3/internal/go-common-file-dialog/cfd/iFileOpenDialog.go b/v3/internal/go-common-file-dialog/cfd/iFileOpenDialog.go new file mode 100644 index 000000000..404dedc22 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/iFileOpenDialog.go @@ -0,0 +1,200 @@ +//go:build windows +// +build windows + +package cfd + +import ( + "github.com/go-ole/go-ole" + "github.com/google/uuid" + "syscall" + "unsafe" +) + +var ( + fileOpenDialogCLSID = ole.NewGUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}") + fileOpenDialogIID = ole.NewGUID("{d57c7288-d4ad-4768-be02-9d969532d960}") +) + +type iFileOpenDialog struct { + vtbl *iFileOpenDialogVtbl + parentWindowHandle uintptr +} + +type iFileOpenDialogVtbl struct { + iFileDialogVtbl + + GetResults uintptr // func (ppenum **IShellItemArray) HRESULT + GetSelectedItems uintptr +} + +func newIFileOpenDialog() (*iFileOpenDialog, error) { + if unknown, err := ole.CreateInstance(fileOpenDialogCLSID, fileOpenDialogIID); err == nil { + return (*iFileOpenDialog)(unsafe.Pointer(unknown)), nil + } else { + return nil, err + } +} + +func (fileOpenDialog *iFileOpenDialog) Show() error { + return fileOpenDialog.vtbl.show(unsafe.Pointer(fileOpenDialog), fileOpenDialog.parentWindowHandle) +} + +func (fileOpenDialog *iFileOpenDialog) SetParentWindowHandle(hwnd uintptr) { + fileOpenDialog.parentWindowHandle = hwnd +} + +func (fileOpenDialog *iFileOpenDialog) ShowAndGetResult() (string, error) { + isMultiselect, err := fileOpenDialog.isMultiselect() + if err != nil { + return "", err + } + if isMultiselect { + // We should panic as this error is caused by the developer using the library + panic("use ShowAndGetResults for open multiple files dialog") + } + if err := fileOpenDialog.Show(); err != nil { + return "", err + } + return fileOpenDialog.GetResult() +} + +func (fileOpenDialog *iFileOpenDialog) ShowAndGetResults() ([]string, error) { + isMultiselect, err := fileOpenDialog.isMultiselect() + if err != nil { + return nil, err + } + if !isMultiselect { + // We should panic as this error is caused by the developer using the library + panic("use ShowAndGetResult for open single file dialog") + } + if err := fileOpenDialog.Show(); err != nil { + return nil, err + } + return fileOpenDialog.GetResults() +} + +func (fileOpenDialog *iFileOpenDialog) SetTitle(title string) error { + return fileOpenDialog.vtbl.setTitle(unsafe.Pointer(fileOpenDialog), title) +} + +func (fileOpenDialog *iFileOpenDialog) GetResult() (string, error) { + isMultiselect, err := fileOpenDialog.isMultiselect() + if err != nil { + return "", err + } + if isMultiselect { + // We should panic as this error is caused by the developer using the library + panic("use GetResults for open multiple files dialog") + } + return fileOpenDialog.vtbl.getResultString(unsafe.Pointer(fileOpenDialog)) +} + +func (fileOpenDialog *iFileOpenDialog) Release() error { + return fileOpenDialog.vtbl.release(unsafe.Pointer(fileOpenDialog)) +} + +func (fileOpenDialog *iFileOpenDialog) SetDefaultFolder(defaultFolderPath string) error { + return fileOpenDialog.vtbl.setDefaultFolder(unsafe.Pointer(fileOpenDialog), defaultFolderPath) +} + +func (fileOpenDialog *iFileOpenDialog) SetFolder(defaultFolderPath string) error { + return fileOpenDialog.vtbl.setFolder(unsafe.Pointer(fileOpenDialog), defaultFolderPath) +} + +func (fileOpenDialog *iFileOpenDialog) SetFileFilters(filter []FileFilter) error { + return fileOpenDialog.vtbl.setFileTypes(unsafe.Pointer(fileOpenDialog), filter) +} + +func (fileOpenDialog *iFileOpenDialog) SetRole(role string) error { + return fileOpenDialog.vtbl.setClientGuid(unsafe.Pointer(fileOpenDialog), StringToUUID(role)) +} + +// This should only be callable when the user asks for a multi select because +// otherwise they will be given the Dialog interface which does not expose this function. +func (fileOpenDialog *iFileOpenDialog) GetResults() ([]string, error) { + isMultiselect, err := fileOpenDialog.isMultiselect() + if err != nil { + return nil, err + } + if !isMultiselect { + // We should panic as this error is caused by the developer using the library + panic("use GetResult for open single file dialog") + } + return fileOpenDialog.vtbl.getResultsStrings(unsafe.Pointer(fileOpenDialog)) +} + +func (fileOpenDialog *iFileOpenDialog) SetDefaultExtension(defaultExtension string) error { + return fileOpenDialog.vtbl.setDefaultExtension(unsafe.Pointer(fileOpenDialog), defaultExtension) +} + +func (fileOpenDialog *iFileOpenDialog) SetFileName(initialFileName string) error { + return fileOpenDialog.vtbl.setFileName(unsafe.Pointer(fileOpenDialog), initialFileName) +} + +func (fileOpenDialog *iFileOpenDialog) SetSelectedFileFilterIndex(index uint) error { + return fileOpenDialog.vtbl.setSelectedFileFilterIndex(unsafe.Pointer(fileOpenDialog), index) +} + +func (fileOpenDialog *iFileOpenDialog) setPickFolders(pickFolders bool) error { + const FosPickfolders = 0x20 + if pickFolders { + return fileOpenDialog.vtbl.addOption(unsafe.Pointer(fileOpenDialog), FosPickfolders) + } else { + return fileOpenDialog.vtbl.removeOption(unsafe.Pointer(fileOpenDialog), FosPickfolders) + } +} + +const FosAllowMultiselect = 0x200 + +func (fileOpenDialog *iFileOpenDialog) isMultiselect() (bool, error) { + options, err := fileOpenDialog.vtbl.getOptions(unsafe.Pointer(fileOpenDialog)) + if err != nil { + return false, err + } + return options&FosAllowMultiselect != 0, nil +} + +func (fileOpenDialog *iFileOpenDialog) setIsMultiselect(isMultiselect bool) error { + if isMultiselect { + return fileOpenDialog.vtbl.addOption(unsafe.Pointer(fileOpenDialog), FosAllowMultiselect) + } else { + return fileOpenDialog.vtbl.removeOption(unsafe.Pointer(fileOpenDialog), FosAllowMultiselect) + } +} + +func (vtbl *iFileOpenDialogVtbl) getResults(objPtr unsafe.Pointer) (*iShellItemArray, error) { + var shellItemArray *iShellItemArray + ret, _, _ := syscall.SyscallN(vtbl.GetResults, + uintptr(objPtr), + uintptr(unsafe.Pointer(&shellItemArray)), + 0) + return shellItemArray, hresultToError(ret) +} + +func (vtbl *iFileOpenDialogVtbl) getResultsStrings(objPtr unsafe.Pointer) ([]string, error) { + shellItemArray, err := vtbl.getResults(objPtr) + if err != nil { + return nil, err + } + if shellItemArray == nil { + return nil, ErrorCancelled + } + defer shellItemArray.vtbl.release(unsafe.Pointer(shellItemArray)) + count, err := shellItemArray.vtbl.getCount(unsafe.Pointer(shellItemArray)) + if err != nil { + return nil, err + } + var results []string + for i := uintptr(0); i < count; i++ { + newItem, err := shellItemArray.vtbl.getItemAt(unsafe.Pointer(shellItemArray), i) + if err != nil { + return nil, err + } + results = append(results, newItem) + } + return results, nil +} + +func StringToUUID(str string) *ole.GUID { + return ole.NewGUID(uuid.NewSHA1(uuid.Nil, []byte(str)).String()) +} diff --git a/v3/internal/go-common-file-dialog/cfd/iFileSaveDialog.go b/v3/internal/go-common-file-dialog/cfd/iFileSaveDialog.go new file mode 100644 index 000000000..ddee7b246 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/iFileSaveDialog.go @@ -0,0 +1,92 @@ +//go:build windows +// +build windows + +package cfd + +import ( + "github.com/go-ole/go-ole" + "unsafe" +) + +var ( + saveFileDialogCLSID = ole.NewGUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}") + saveFileDialogIID = ole.NewGUID("{84bccd23-5fde-4cdb-aea4-af64b83d78ab}") +) + +type iFileSaveDialog struct { + vtbl *iFileSaveDialogVtbl + parentWindowHandle uintptr +} + +type iFileSaveDialogVtbl struct { + iFileDialogVtbl + + SetSaveAsItem uintptr + SetProperties uintptr + SetCollectedProperties uintptr + GetProperties uintptr + ApplyProperties uintptr +} + +func newIFileSaveDialog() (*iFileSaveDialog, error) { + if unknown, err := ole.CreateInstance(saveFileDialogCLSID, saveFileDialogIID); err == nil { + return (*iFileSaveDialog)(unsafe.Pointer(unknown)), nil + } else { + return nil, err + } +} + +func (fileSaveDialog *iFileSaveDialog) Show() error { + return fileSaveDialog.vtbl.show(unsafe.Pointer(fileSaveDialog), fileSaveDialog.parentWindowHandle) +} + +func (fileSaveDialog *iFileSaveDialog) SetParentWindowHandle(hwnd uintptr) { + fileSaveDialog.parentWindowHandle = hwnd +} + +func (fileSaveDialog *iFileSaveDialog) ShowAndGetResult() (string, error) { + if err := fileSaveDialog.Show(); err != nil { + return "", err + } + return fileSaveDialog.GetResult() +} + +func (fileSaveDialog *iFileSaveDialog) SetTitle(title string) error { + return fileSaveDialog.vtbl.setTitle(unsafe.Pointer(fileSaveDialog), title) +} + +func (fileSaveDialog *iFileSaveDialog) GetResult() (string, error) { + return fileSaveDialog.vtbl.getResultString(unsafe.Pointer(fileSaveDialog)) +} + +func (fileSaveDialog *iFileSaveDialog) Release() error { + return fileSaveDialog.vtbl.release(unsafe.Pointer(fileSaveDialog)) +} + +func (fileSaveDialog *iFileSaveDialog) SetDefaultFolder(defaultFolderPath string) error { + return fileSaveDialog.vtbl.setDefaultFolder(unsafe.Pointer(fileSaveDialog), defaultFolderPath) +} + +func (fileSaveDialog *iFileSaveDialog) SetFolder(defaultFolderPath string) error { + return fileSaveDialog.vtbl.setFolder(unsafe.Pointer(fileSaveDialog), defaultFolderPath) +} + +func (fileSaveDialog *iFileSaveDialog) SetFileFilters(filter []FileFilter) error { + return fileSaveDialog.vtbl.setFileTypes(unsafe.Pointer(fileSaveDialog), filter) +} + +func (fileSaveDialog *iFileSaveDialog) SetRole(role string) error { + return fileSaveDialog.vtbl.setClientGuid(unsafe.Pointer(fileSaveDialog), StringToUUID(role)) +} + +func (fileSaveDialog *iFileSaveDialog) SetDefaultExtension(defaultExtension string) error { + return fileSaveDialog.vtbl.setDefaultExtension(unsafe.Pointer(fileSaveDialog), defaultExtension) +} + +func (fileSaveDialog *iFileSaveDialog) SetFileName(initialFileName string) error { + return fileSaveDialog.vtbl.setFileName(unsafe.Pointer(fileSaveDialog), initialFileName) +} + +func (fileSaveDialog *iFileSaveDialog) SetSelectedFileFilterIndex(index uint) error { + return fileSaveDialog.vtbl.setSelectedFileFilterIndex(unsafe.Pointer(fileSaveDialog), index) +} diff --git a/v3/internal/go-common-file-dialog/cfd/iShellItem.go b/v3/internal/go-common-file-dialog/cfd/iShellItem.go new file mode 100644 index 000000000..080115345 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/iShellItem.go @@ -0,0 +1,56 @@ +//go:build windows +// +build windows + +package cfd + +import ( + "github.com/go-ole/go-ole" + "syscall" + "unsafe" +) + +var ( + procSHCreateItemFromParsingName = syscall.NewLazyDLL("Shell32.dll").NewProc("SHCreateItemFromParsingName") + iidShellItem = ole.NewGUID("43826d1e-e718-42ee-bc55-a1e261c37bfe") +) + +type iShellItem struct { + vtbl *iShellItemVtbl +} + +type iShellItemVtbl struct { + iUnknownVtbl + BindToHandler uintptr + GetParent uintptr + GetDisplayName uintptr // func (sigdnName SIGDN, ppszName *LPWSTR) HRESULT + GetAttributes uintptr + Compare uintptr +} + +func newIShellItem(path string) (*iShellItem, error) { + var shellItem *iShellItem + pathPtr := ole.SysAllocString(path) + defer func(v *int16) { + _ = ole.SysFreeString(v) + }(pathPtr) + + ret, _, _ := procSHCreateItemFromParsingName.Call( + uintptr(unsafe.Pointer(pathPtr)), + 0, + uintptr(unsafe.Pointer(iidShellItem)), + uintptr(unsafe.Pointer(&shellItem))) + return shellItem, hresultToError(ret) +} + +func (vtbl *iShellItemVtbl) getDisplayName(objPtr unsafe.Pointer) (string, error) { + var ptr *uint16 + ret, _, _ := syscall.SyscallN(vtbl.GetDisplayName, + uintptr(objPtr), + 0x80058000, // SIGDN_FILESYSPATH, + uintptr(unsafe.Pointer(&ptr))) + if err := hresultToError(ret); err != nil { + return "", err + } + defer ole.CoTaskMemFree(uintptr(unsafe.Pointer(ptr))) + return ole.LpOleStrToString(ptr), nil +} diff --git a/v3/internal/go-common-file-dialog/cfd/iShellItemArray.go b/v3/internal/go-common-file-dialog/cfd/iShellItemArray.go new file mode 100644 index 000000000..bdd459402 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/iShellItemArray.go @@ -0,0 +1,65 @@ +//go:build windows +// +build windows + +package cfd + +import ( + "fmt" + "github.com/go-ole/go-ole" + "syscall" + "unsafe" +) + +const ( + iidShellItemArrayGUID = "{b63ea76d-1f85-456f-a19c-48159efa858b}" +) + +var ( + iidShellItemArray *ole.GUID +) + +func init() { + iidShellItemArray, _ = ole.IIDFromString(iidShellItemArrayGUID) +} + +type iShellItemArray struct { + vtbl *iShellItemArrayVtbl +} + +type iShellItemArrayVtbl struct { + iUnknownVtbl + BindToHandler uintptr + GetPropertyStore uintptr + GetPropertyDescriptionList uintptr + GetAttributes uintptr + GetCount uintptr // func (pdwNumItems *DWORD) HRESULT + GetItemAt uintptr // func (dwIndex DWORD, ppsi **IShellItem) HRESULT + EnumItems uintptr +} + +func (vtbl *iShellItemArrayVtbl) getCount(objPtr unsafe.Pointer) (uintptr, error) { + var count uintptr + ret, _, _ := syscall.SyscallN(vtbl.GetCount, + uintptr(objPtr), + uintptr(unsafe.Pointer(&count))) + if err := hresultToError(ret); err != nil { + return 0, err + } + return count, nil +} + +func (vtbl *iShellItemArrayVtbl) getItemAt(objPtr unsafe.Pointer, index uintptr) (string, error) { + var shellItem *iShellItem + ret, _, _ := syscall.SyscallN(vtbl.GetItemAt, + uintptr(objPtr), + index, + uintptr(unsafe.Pointer(&shellItem))) + if err := hresultToError(ret); err != nil { + return "", err + } + if shellItem == nil { + return "", fmt.Errorf("shellItem is nil") + } + defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) + return shellItem.vtbl.getDisplayName(unsafe.Pointer(shellItem)) +} diff --git a/v3/internal/go-common-file-dialog/cfd/vtblCommon.go b/v3/internal/go-common-file-dialog/cfd/vtblCommon.go new file mode 100644 index 000000000..21015c27c --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/vtblCommon.go @@ -0,0 +1,48 @@ +//go:build windows +// +build windows + +package cfd + +type comDlgFilterSpec struct { + pszName *int16 + pszSpec *int16 +} + +type iUnknownVtbl struct { + QueryInterface uintptr + AddRef uintptr + Release uintptr +} + +type iModalWindowVtbl struct { + iUnknownVtbl + Show uintptr // func (hwndOwner HWND) HRESULT +} + +type iFileDialogVtbl struct { + iModalWindowVtbl + SetFileTypes uintptr // func (cFileTypes UINT, rgFilterSpec *COMDLG_FILTERSPEC) HRESULT + SetFileTypeIndex uintptr // func(iFileType UINT) HRESULT + GetFileTypeIndex uintptr + Advise uintptr + Unadvise uintptr + SetOptions uintptr // func (fos FILEOPENDIALOGOPTIONS) HRESULT + GetOptions uintptr // func (pfos *FILEOPENDIALOGOPTIONS) HRESULT + SetDefaultFolder uintptr // func (psi *IShellItem) HRESULT + SetFolder uintptr // func (psi *IShellItem) HRESULT + GetFolder uintptr + GetCurrentSelection uintptr + SetFileName uintptr // func (pszName LPCWSTR) HRESULT + GetFileName uintptr + SetTitle uintptr // func(pszTitle LPCWSTR) HRESULT + SetOkButtonLabel uintptr + SetFileNameLabel uintptr + GetResult uintptr // func (ppsi **IShellItem) HRESULT + AddPlace uintptr + SetDefaultExtension uintptr // func (pszDefaultExtension LPCWSTR) HRESULT + // This can only be used from a callback. + Close uintptr + SetClientGuid uintptr // func (guid REFGUID) HRESULT + ClearClientData uintptr + SetFilter uintptr +} diff --git a/v3/internal/go-common-file-dialog/cfd/vtblCommonFunc.go b/v3/internal/go-common-file-dialog/cfd/vtblCommonFunc.go new file mode 100644 index 000000000..a59ff1ed1 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/vtblCommonFunc.go @@ -0,0 +1,226 @@ +//go:build windows + +package cfd + +import ( + "fmt" + "strings" + "syscall" + "unsafe" + + "github.com/go-ole/go-ole" +) + +func hresultToError(hr uintptr) error { + if hr < 0 { + return ole.NewError(hr) + } + return nil +} + +func (vtbl *iUnknownVtbl) release(objPtr unsafe.Pointer) error { + ret, _, _ := syscall.SyscallN(vtbl.Release, + uintptr(objPtr), + 0) + return hresultToError(ret) +} + +func (vtbl *iModalWindowVtbl) show(objPtr unsafe.Pointer, hwnd uintptr) error { + ret, _, _ := syscall.SyscallN(vtbl.Show, + uintptr(objPtr), + hwnd) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setFileTypes(objPtr unsafe.Pointer, filters []FileFilter) error { + cFileTypes := len(filters) + if cFileTypes < 0 { + return fmt.Errorf("must specify at least one filter") + } + comDlgFilterSpecs := make([]comDlgFilterSpec, cFileTypes) + for i := 0; i < cFileTypes; i++ { + filter := &filters[i] + comDlgFilterSpecs[i] = comDlgFilterSpec{ + pszName: ole.SysAllocString(filter.DisplayName), + pszSpec: ole.SysAllocString(filter.Pattern), + } + } + + // Ensure memory is freed after use + defer func() { + for _, spec := range comDlgFilterSpecs { + ole.SysFreeString(spec.pszName) + ole.SysFreeString(spec.pszSpec) + } + }() + + ret, _, _ := syscall.SyscallN(vtbl.SetFileTypes, + uintptr(objPtr), + uintptr(cFileTypes), + uintptr(unsafe.Pointer(&comDlgFilterSpecs[0]))) + return hresultToError(ret) +} + +// Options are: +// FOS_OVERWRITEPROMPT = 0x2, +// FOS_STRICTFILETYPES = 0x4, +// FOS_NOCHANGEDIR = 0x8, +// FOS_PICKFOLDERS = 0x20, +// FOS_FORCEFILESYSTEM = 0x40, +// FOS_ALLNONSTORAGEITEMS = 0x80, +// FOS_NOVALIDATE = 0x100, +// FOS_ALLOWMULTISELECT = 0x200, +// FOS_PATHMUSTEXIST = 0x800, +// FOS_FILEMUSTEXIST = 0x1000, +// FOS_CREATEPROMPT = 0x2000, +// FOS_SHAREAWARE = 0x4000, +// FOS_NOREADONLYRETURN = 0x8000, +// FOS_NOTESTFILECREATE = 0x10000, +// FOS_HIDEMRUPLACES = 0x20000, +// FOS_HIDEPINNEDPLACES = 0x40000, +// FOS_NODEREFERENCELINKS = 0x100000, +// FOS_OKBUTTONNEEDSINTERACTION = 0x200000, +// FOS_DONTADDTORECENT = 0x2000000, +// FOS_FORCESHOWHIDDEN = 0x10000000, +// FOS_DEFAULTNOMINIMODE = 0x20000000, +// FOS_FORCEPREVIEWPANEON = 0x40000000, +// FOS_SUPPORTSTREAMABLEITEMS = 0x80000000 +func (vtbl *iFileDialogVtbl) setOptions(objPtr unsafe.Pointer, options uint32) error { + ret, _, _ := syscall.SyscallN(vtbl.SetOptions, + uintptr(objPtr), + uintptr(options)) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) getOptions(objPtr unsafe.Pointer) (uint32, error) { + var options uint32 + ret, _, _ := syscall.SyscallN(vtbl.GetOptions, + uintptr(objPtr), + uintptr(unsafe.Pointer(&options))) + return options, hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) addOption(objPtr unsafe.Pointer, option uint32) error { + if options, err := vtbl.getOptions(objPtr); err == nil { + return vtbl.setOptions(objPtr, options|option) + } else { + return err + } +} + +func (vtbl *iFileDialogVtbl) removeOption(objPtr unsafe.Pointer, option uint32) error { + if options, err := vtbl.getOptions(objPtr); err == nil { + return vtbl.setOptions(objPtr, options&^option) + } else { + return err + } +} + +func (vtbl *iFileDialogVtbl) setDefaultFolder(objPtr unsafe.Pointer, path string) error { + shellItem, err := newIShellItem(path) + if err != nil { + return err + } + defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) + ret, _, _ := syscall.SyscallN(vtbl.SetDefaultFolder, + uintptr(objPtr), + uintptr(unsafe.Pointer(shellItem))) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setFolder(objPtr unsafe.Pointer, path string) error { + shellItem, err := newIShellItem(path) + if err != nil { + return err + } + defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) + ret, _, _ := syscall.SyscallN(vtbl.SetFolder, + uintptr(objPtr), + uintptr(unsafe.Pointer(shellItem))) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setTitle(objPtr unsafe.Pointer, title string) error { + titlePtr := ole.SysAllocString(title) + defer ole.SysFreeString(titlePtr) // Ensure the string is freed + ret, _, _ := syscall.SyscallN(vtbl.SetTitle, + uintptr(objPtr), + uintptr(unsafe.Pointer(titlePtr))) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) close(objPtr unsafe.Pointer) error { + ret, _, _ := syscall.SyscallN(vtbl.Close, + uintptr(objPtr)) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) getResult(objPtr unsafe.Pointer) (*iShellItem, error) { + var shellItem *iShellItem + ret, _, _ := syscall.SyscallN(vtbl.GetResult, + uintptr(objPtr), + uintptr(unsafe.Pointer(&shellItem))) + return shellItem, hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) getResultString(objPtr unsafe.Pointer) (string, error) { + shellItem, err := vtbl.getResult(objPtr) + if err != nil { + return "", err + } + if shellItem == nil { + return "", ErrorCancelled + } + defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) + return shellItem.vtbl.getDisplayName(unsafe.Pointer(shellItem)) +} + +func (vtbl *iFileDialogVtbl) setClientGuid(objPtr unsafe.Pointer, guid *ole.GUID) error { + // Ensure the GUID is not nil + if guid == nil { + return fmt.Errorf("guid cannot be nil") + } + + // Call the SetClientGuid method + ret, _, _ := syscall.SyscallN(vtbl.SetClientGuid, + uintptr(objPtr), + uintptr(unsafe.Pointer(guid))) + + // Convert the HRESULT to a Go error + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setDefaultExtension(objPtr unsafe.Pointer, defaultExtension string) error { + // Ensure the string is not empty before accessing the first character + if len(defaultExtension) > 0 && defaultExtension[0] == '.' { + defaultExtension = strings.TrimPrefix(defaultExtension, ".") + } + + // Allocate memory for the default extension string + defaultExtensionPtr := ole.SysAllocString(defaultExtension) + defer ole.SysFreeString(defaultExtensionPtr) // Ensure the string is freed + + // Call the SetDefaultExtension method + ret, _, _ := syscall.SyscallN(vtbl.SetDefaultExtension, + uintptr(objPtr), + uintptr(unsafe.Pointer(defaultExtensionPtr))) + + // Convert the HRESULT to a Go error + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setFileName(objPtr unsafe.Pointer, fileName string) error { + fileNamePtr := ole.SysAllocString(fileName) + defer ole.SysFreeString(fileNamePtr) // Ensure the string is freed + ret, _, _ := syscall.SyscallN(vtbl.SetFileName, + uintptr(objPtr), + uintptr(unsafe.Pointer(fileNamePtr))) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setSelectedFileFilterIndex(objPtr unsafe.Pointer, index uint) error { + ret, _, _ := syscall.SyscallN(vtbl.SetFileTypeIndex, + uintptr(objPtr), + uintptr(index+1)) // SetFileTypeIndex counts from 1 + return hresultToError(ret) +} diff --git a/v3/internal/go-common-file-dialog/cfdutil/CFDUtil.go b/v3/internal/go-common-file-dialog/cfdutil/CFDUtil.go new file mode 100644 index 000000000..aa3a783b2 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfdutil/CFDUtil.go @@ -0,0 +1,45 @@ +package cfdutil + +import ( + "github.com/wailsapp/wails/v3/internal/go-common-file-dialog/cfd" +) + +// TODO doc +func ShowOpenFileDialog(config cfd.DialogConfig) (string, error) { + dialog, err := cfd.NewOpenFileDialog(config) + if err != nil { + return "", err + } + defer dialog.Release() + return dialog.ShowAndGetResult() +} + +// TODO doc +func ShowOpenMultipleFilesDialog(config cfd.DialogConfig) ([]string, error) { + dialog, err := cfd.NewOpenMultipleFilesDialog(config) + if err != nil { + return nil, err + } + defer dialog.Release() + return dialog.ShowAndGetResults() +} + +// TODO doc +func ShowPickFolderDialog(config cfd.DialogConfig) (string, error) { + dialog, err := cfd.NewSelectFolderDialog(config) + if err != nil { + return "", err + } + defer dialog.Release() + return dialog.ShowAndGetResult() +} + +// TODO doc +func ShowSaveFileDialog(config cfd.DialogConfig) (string, error) { + dialog, err := cfd.NewSaveFileDialog(config) + if err != nil { + return "", err + } + defer dialog.Release() + return dialog.ShowAndGetResult() +} diff --git a/v3/internal/go-common-file-dialog/util/util.go b/v3/internal/go-common-file-dialog/util/util.go new file mode 100644 index 000000000..723fbedc0 --- /dev/null +++ b/v3/internal/go-common-file-dialog/util/util.go @@ -0,0 +1,10 @@ +package util + +import ( + "github.com/go-ole/go-ole" + "github.com/google/uuid" +) + +func StringToUUID(str string) *ole.GUID { + return ole.NewGUID(uuid.NewSHA1(uuid.Nil, []byte(str)).String()) +} diff --git a/v3/internal/go-common-file-dialog/util/util_test.go b/v3/internal/go-common-file-dialog/util/util_test.go new file mode 100644 index 000000000..2e8ffeb05 --- /dev/null +++ b/v3/internal/go-common-file-dialog/util/util_test.go @@ -0,0 +1,14 @@ +package util + +import ( + "github.com/go-ole/go-ole" + "testing" +) + +func TestStringToUUID(t *testing.T) { + generated := *StringToUUID("TestTestTest") + expected := *ole.NewGUID("7933985F-2C87-5A5B-A26E-5D0326829AC2") + if generated != expected { + t.Errorf("not equal. expected %s, found %s", expected.String(), generated.String()) + } +} diff --git a/v3/internal/hash/fnv.go b/v3/internal/hash/fnv.go new file mode 100644 index 000000000..bc18ee817 --- /dev/null +++ b/v3/internal/hash/fnv.go @@ -0,0 +1,9 @@ +package hash + +import "hash/fnv" + +func Fnv(s string) uint32 { + h := fnv.New32a() + _, _ = h.Write([]byte(s)) // Hash implementations never return errors (see https://pkg.go.dev/hash#Hash) + return h.Sum32() +} diff --git a/v3/internal/keychain/keychain.go b/v3/internal/keychain/keychain.go new file mode 100644 index 000000000..67e99e5ac --- /dev/null +++ b/v3/internal/keychain/keychain.go @@ -0,0 +1,89 @@ +// Package keychain provides secure credential storage using the system keychain. +// On macOS it uses Keychain, on Windows it uses Credential Manager, +// and on Linux it uses Secret Service (via D-Bus). +package keychain + +import ( + "fmt" + "os" + + "github.com/zalando/go-keyring" +) + +const ( + // ServiceName is the service identifier used for all Wails credentials + ServiceName = "wails" + + // Credential keys + KeyWindowsCertPassword = "windows-cert-password" + KeyPGPPassword = "pgp-password" +) + +// Set stores a credential in the system keychain. +// The credential is identified by a key and can be retrieved later with Get. +func Set(key, value string) error { + err := keyring.Set(ServiceName, key, value) + if err != nil { + return fmt.Errorf("failed to store credential in keychain: %w", err) + } + return nil +} + +// Get retrieves a credential from the system keychain. +// Returns the value and nil error if found, or empty string and error if not found. +// Also checks environment variables as a fallback (useful for CI). +func Get(key string) (string, error) { + // First check environment variable (for CI/automation) + envKey := "WAILS_" + toEnvName(key) + if val := os.Getenv(envKey); val != "" { + return val, nil + } + + // Try keychain + value, err := keyring.Get(ServiceName, key) + if err != nil { + if err == keyring.ErrNotFound { + return "", fmt.Errorf("credential %q not found in keychain (set with: wails3 setup signing, or set env var %s)", key, envKey) + } + return "", fmt.Errorf("failed to retrieve credential from keychain: %w", err) + } + return value, nil +} + +// Delete removes a credential from the system keychain. +func Delete(key string) error { + err := keyring.Delete(ServiceName, key) + if err != nil && err != keyring.ErrNotFound { + return fmt.Errorf("failed to delete credential from keychain: %w", err) + } + return nil +} + +// Exists checks if a credential exists in the keychain or environment. +func Exists(key string) bool { + // Check environment variable first + envKey := "WAILS_" + toEnvName(key) + if os.Getenv(envKey) != "" { + return true + } + + // Check keychain + _, err := keyring.Get(ServiceName, key) + return err == nil +} + +// toEnvName converts a key to an environment variable name. +// e.g., "windows-cert-password" -> "WINDOWS_CERT_PASSWORD" +func toEnvName(key string) string { + result := make([]byte, len(key)) + for i, c := range key { + if c == '-' { + result[i] = '_' + } else if c >= 'a' && c <= 'z' { + result[i] = byte(c - 'a' + 'A') + } else { + result[i] = byte(c) + } + } + return string(result) +} diff --git a/v3/internal/libpath/cache_linux.go b/v3/internal/libpath/cache_linux.go new file mode 100644 index 000000000..5591c6833 --- /dev/null +++ b/v3/internal/libpath/cache_linux.go @@ -0,0 +1,79 @@ +//go:build linux + +package libpath + +import "sync" + +// pathCache holds cached dynamic library paths to avoid repeated +// expensive filesystem and subprocess operations. +type pathCache struct { + mu sync.RWMutex + flatpak []string + snap []string + nix []string + initOnce sync.Once + inited bool +} + +var cache pathCache + +// init populates the cache with dynamic paths from package managers. +// This is called lazily on first access. +func (c *pathCache) init() { + c.initOnce.Do(func() { + // Discover paths without holding the lock + flatpak := discoverFlatpakLibPaths() + snap := discoverSnapLibPaths() + nix := discoverNixLibPaths() + + // Hold lock only while updating the cache + c.mu.Lock() + c.flatpak = flatpak + c.snap = snap + c.nix = nix + c.inited = true + c.mu.Unlock() + }) +} + +// getFlatpak returns cached Flatpak library paths. +func (c *pathCache) getFlatpak() []string { + c.init() + c.mu.RLock() + defer c.mu.RUnlock() + return c.flatpak +} + +// getSnap returns cached Snap library paths. +func (c *pathCache) getSnap() []string { + c.init() + c.mu.RLock() + defer c.mu.RUnlock() + return c.snap +} + +// getNix returns cached Nix library paths. +func (c *pathCache) getNix() []string { + c.init() + c.mu.RLock() + defer c.mu.RUnlock() + return c.nix +} + +// invalidate clears the cache and forces re-discovery on next access. +func (c *pathCache) invalidate() { + c.mu.Lock() + defer c.mu.Unlock() + c.flatpak = nil + c.snap = nil + c.nix = nil + c.initOnce = sync.Once{} // Reset so init() runs again + c.inited = false +} + +// InvalidateCache clears the cached dynamic library paths. +// Call this if packages are installed or removed during runtime +// and you need to re-discover library paths. +func InvalidateCache() { + cache.invalidate() +} diff --git a/v3/internal/libpath/flatpak_linux.go b/v3/internal/libpath/flatpak_linux.go new file mode 100644 index 000000000..ce83c08c6 --- /dev/null +++ b/v3/internal/libpath/flatpak_linux.go @@ -0,0 +1,53 @@ +//go:build linux + +package libpath + +import ( + "os" + "os/exec" + "path/filepath" + "strings" +) + +// getFlatpakLibPaths returns cached library paths from installed Flatpak runtimes. +func getFlatpakLibPaths() []string { + return cache.getFlatpak() +} + +// discoverFlatpakLibPaths scans for Flatpak runtime library directories. +// Uses `flatpak --installations` and scans for runtime lib directories. +func discoverFlatpakLibPaths() []string { + var paths []string + + // Get system and user installation directories + installDirs := []string{ + "/var/lib/flatpak", // System default + os.ExpandEnv("$HOME/.local/share/flatpak"), // User default + } + + // Try to get actual installation path from flatpak + if out, err := exec.Command("flatpak", "--installations").Output(); err == nil { + for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { + if line != "" { + installDirs = append(installDirs, line) + } + } + } + + // Scan for runtime lib directories + for _, installDir := range installDirs { + runtimeDir := filepath.Join(installDir, "runtime") + if _, err := os.Stat(runtimeDir); err != nil { + continue + } + + // Look for lib directories in runtimes + // Structure: runtime/////files/lib + matches, err := filepath.Glob(filepath.Join(runtimeDir, "*", "*", "*", "*", "files", "lib")) + if err == nil { + paths = append(paths, matches...) + } + } + + return paths +} diff --git a/v3/internal/libpath/libpath.go b/v3/internal/libpath/libpath.go new file mode 100644 index 000000000..4c32ce518 --- /dev/null +++ b/v3/internal/libpath/libpath.go @@ -0,0 +1,104 @@ +// Package libpath provides utilities for finding native library paths on Linux. +// +// # Overview +// +// This package helps locate shared libraries (.so files) on Linux systems, +// supporting multiple distributions and package managers. It's particularly +// useful for applications that need to link against libraries like GTK, +// WebKit2GTK, or other system libraries at runtime. +// +// # Search Strategy +// +// The package uses a multi-tier search strategy, trying each method in order +// until a library is found: +// +// 1. pkg-config: Queries the pkg-config database for library paths +// 2. ldconfig: Searches the dynamic linker cache +// 3. Filesystem: Scans common library directories +// +// # Supported Distributions +// +// The package includes default search paths for: +// +// - Debian/Ubuntu (multiarch paths like /usr/lib/x86_64-linux-gnu) +// - Fedora/RHEL/CentOS (/usr/lib64, /usr/lib64/gtk-*) +// - Arch Linux (/usr/lib/webkit2gtk-*, /usr/lib/gtk-*) +// - openSUSE (/usr/lib64/gcc/x86_64-suse-linux) +// - NixOS and Nix package manager +// +// # Package Manager Support +// +// Dynamic paths are discovered from: +// +// - Flatpak: Scans runtime directories via `flatpak --installations` +// - Snap: Globs /snap/*/current/usr/lib* directories +// - Nix: Checks ~/.nix-profile/lib and /run/current-system/sw/lib +// +// # Caching +// +// Dynamic path discovery (Flatpak, Snap, Nix) is cached for performance. +// The cache is populated on first access and persists for the process lifetime. +// Use [InvalidateCache] to force re-discovery if packages are installed/removed +// during runtime. +// +// # Security +// +// The current directory (".") is never included in search paths by default, +// as this is a security risk. Use [FindLibraryPathWithOptions] with +// IncludeCurrentDir if you explicitly need this behavior (not recommended +// for production). +// +// # Performance +// +// Typical lookup times (cached): +// +// - Found via pkg-config: ~2ms (spawns external process) +// - Found via ldconfig: ~1.3ms (spawns external process) +// - Found via filesystem: ~0.1ms (uses cached paths) +// - Not found (worst case): ~20ms (searches all paths) +// +// # Example Usage +// +// // Find a library by its pkg-config name +// path, err := libpath.FindLibraryPath("webkit2gtk-4.1") +// if err != nil { +// log.Fatal("WebKit2GTK not found:", err) +// } +// fmt.Println("Found at:", path) +// +// // Find a specific .so file +// soPath, err := libpath.FindLibraryFile("libgtk-3.so") +// if err != nil { +// log.Fatal("GTK3 library file not found:", err) +// } +// fmt.Println("Library file:", soPath) +// +// // Get all library search paths +// for _, p := range libpath.GetAllLibPaths() { +// fmt.Println(p) +// } +// +// # Multi-Library Search +// +// When you don't know which version of a library is installed, use the +// multi-library search functions: +// +// // Find any available WebKit2GTK version (first found wins) +// match, err := libpath.FindFirstLibrary("webkit2gtk-4.1", "webkit2gtk-4.0", "webkit2gtk-6.0") +// if err != nil { +// log.Fatal("No WebKit2GTK found") +// } +// fmt.Printf("Found %s at %s\n", match.Name, match.Path) +// +// // Prefer newer versions (ordered search) +// match, err := libpath.FindFirstLibraryOrdered("gtk4", "gtk+-3.0") +// +// // Discover all available versions +// matches := libpath.FindAllLibraries("gtk+-3.0", "gtk4", "webkit2gtk-4.0", "webkit2gtk-4.1") +// for _, m := range matches { +// fmt.Printf("Available: %s at %s\n", m.Name, m.Path) +// } +// +// On non-Linux platforms, stub implementations are provided that always +// return [LibraryNotFoundError]. +package libpath diff --git a/v3/internal/libpath/libpath_linux.go b/v3/internal/libpath/libpath_linux.go new file mode 100644 index 000000000..9b908d405 --- /dev/null +++ b/v3/internal/libpath/libpath_linux.go @@ -0,0 +1,551 @@ +//go:build linux + +package libpath + +import ( + "context" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" +) + +// Common library search paths on Linux systems +var defaultLibPaths = []string{ + // Standard paths + "/usr/lib", + "/usr/lib64", + "/lib", + "/lib64", + + // Debian/Ubuntu multiarch + "/usr/lib/x86_64-linux-gnu", + "/usr/lib/aarch64-linux-gnu", + "/usr/lib/i386-linux-gnu", + "/usr/lib/arm-linux-gnueabihf", + "/lib/x86_64-linux-gnu", + "/lib/aarch64-linux-gnu", + + // Fedora/RHEL/CentOS + "/usr/lib64/gtk-3.0", + "/usr/lib64/gtk-4.0", + "/usr/lib/gcc/x86_64-redhat-linux", + "/usr/lib/gcc/aarch64-redhat-linux", + + // Arch Linux + "/usr/lib/webkit2gtk-4.0", + "/usr/lib/webkit2gtk-4.1", + "/usr/lib/gtk-3.0", + "/usr/lib/gtk-4.0", + + // openSUSE + "/usr/lib64/gcc/x86_64-suse-linux", + + // Local installations + "/usr/local/lib", + "/usr/local/lib64", +} + +// searchResult holds the result from a parallel search goroutine. +type searchResult struct { + path string + source string // for debugging: "pkg-config", "ldconfig", "filesystem" +} + +// FindLibraryPath attempts to find the path to a library using multiple methods +// in parallel. It searches via pkg-config, ldconfig, and filesystem simultaneously, +// returning as soon as any method finds the library. +// +// The libName should be the pkg-config name (e.g., "gtk+-3.0", "webkit2gtk-4.1"). +// Returns the library directory path and any error encountered. +func FindLibraryPath(libName string) (string, error) { + return findLibraryPathCtx(context.Background(), libName) +} + +// FindLibraryPathSequential is the original sequential implementation. +// Use this if you need deterministic search order (pkg-config → ldconfig → filesystem). +func FindLibraryPathSequential(libName string) (string, error) { + // Try pkg-config first (most reliable when available) + if path, err := findWithPkgConfig(libName); err == nil { + return path, nil + } + + // Try ldconfig cache + if path, err := findWithLdconfig(libName); err == nil { + return path, nil + } + + // Fall back to searching common paths + return findInCommonPaths(libName) +} + +// FindLibraryFile finds the full path to a specific library file (e.g., "libgtk-3.so"). +func FindLibraryFile(fileName string) (string, error) { + // Try ldconfig first + if path, err := findFileWithLdconfig(fileName); err == nil { + return path, nil + } + + // Search all paths including dynamic ones + for _, dir := range GetAllLibPaths() { + // Check exact match + fullPath := filepath.Join(dir, fileName) + if _, err := os.Stat(fullPath); err == nil { + return fullPath, nil + } + + // Check with .so suffix variations + matches, err := filepath.Glob(filepath.Join(dir, fileName+"*")) + if err == nil && len(matches) > 0 { + return matches[0], nil + } + } + + return "", &LibraryNotFoundError{Name: fileName} +} + +// findWithPkgConfig uses pkg-config to find library paths. +func findWithPkgConfig(libName string) (string, error) { + return findWithPkgConfigCtx(context.Background(), libName) +} + +// findWithPkgConfigCtx uses pkg-config to find library paths with context support. +func findWithPkgConfigCtx(ctx context.Context, libName string) (string, error) { + // Check if already cancelled + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + cmd := exec.CommandContext(ctx, "pkg-config", "--libs-only-L", libName) + output, err := cmd.Output() + if err != nil { + return "", err + } + + // Parse -L flags from output + parts := strings.Fields(string(output)) + for _, part := range parts { + if strings.HasPrefix(part, "-L") { + path := strings.TrimPrefix(part, "-L") + if _, err := os.Stat(path); err == nil { + return path, nil + } + } + } + + // Check context before second command + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + // If no -L flag, try --variable=libdir + cmd = exec.CommandContext(ctx, "pkg-config", "--variable=libdir", libName) + output, err = cmd.Output() + if err != nil { + return "", err + } + + path := strings.TrimSpace(string(output)) + if path != "" { + if _, err := os.Stat(path); err == nil { + return path, nil + } + } + + return "", &LibraryNotFoundError{Name: libName} +} + +// findWithLdconfig searches the ldconfig cache for library paths. +func findWithLdconfig(libName string) (string, error) { + return findWithLdconfigCtx(context.Background(), libName) +} + +// findWithLdconfigCtx searches the ldconfig cache for library paths with context support. +func findWithLdconfigCtx(ctx context.Context, libName string) (string, error) { + // Check if already cancelled + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + // Convert pkg-config name to library name pattern + // e.g., "gtk+-3.0" -> "libgtk-3", "webkit2gtk-4.1" -> "libwebkit2gtk-4.1" + searchName := pkgConfigToLibName(libName) + + cmd := exec.CommandContext(ctx, "ldconfig", "-p") + output, err := cmd.Output() + if err != nil { + return "", err + } + + for _, line := range strings.Split(string(output), "\n") { + if strings.Contains(line, searchName) { + // Line format: " libname.so.X (libc6,x86-64) => /path/to/lib" + parts := strings.Split(line, "=>") + if len(parts) == 2 { + libPath := strings.TrimSpace(parts[1]) + return filepath.Dir(libPath), nil + } + } + } + + return "", &LibraryNotFoundError{Name: libName} +} + +// findFileWithLdconfig finds a specific library file using ldconfig. +func findFileWithLdconfig(fileName string) (string, error) { + cmd := exec.Command("ldconfig", "-p") + output, err := cmd.Output() + if err != nil { + return "", err + } + + baseName := strings.TrimSuffix(fileName, ".so") + for _, line := range strings.Split(string(output), "\n") { + if strings.Contains(line, baseName) { + parts := strings.Split(line, "=>") + if len(parts) == 2 { + return strings.TrimSpace(parts[1]), nil + } + } + } + + return "", &LibraryNotFoundError{Name: fileName} +} + +// findInCommonPaths searches common library directories including +// dynamically discovered Flatpak, Snap, and Nix paths. +func findInCommonPaths(libName string) (string, error) { + return findInCommonPathsCtx(context.Background(), libName) +} + +// findInCommonPathsCtx searches common library directories with context support. +func findInCommonPathsCtx(ctx context.Context, libName string) (string, error) { + searchName := pkgConfigToLibName(libName) + + // Search all paths including dynamic ones + allPaths := GetAllLibPaths() + + for _, dir := range allPaths { + // Check if cancelled periodically + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + if _, err := os.Stat(dir); err != nil { + continue + } + + // Look for the library file + pattern := filepath.Join(dir, searchName+"*.so*") + matches, err := filepath.Glob(pattern) + if err == nil && len(matches) > 0 { + return dir, nil + } + + // Also check pkgconfig subdirectory for .pc files + pcPath := filepath.Join(dir, "pkgconfig", libName+".pc") + if _, err := os.Stat(pcPath); err == nil { + return dir, nil + } + } + + return "", &LibraryNotFoundError{Name: libName} +} + +// pkgConfigToLibName converts a pkg-config package name to a library name pattern. +func pkgConfigToLibName(pkgName string) string { + // Common transformations + name := pkgName + + // Remove version suffix like "-3.0", "-4.1" + // but keep it for webkit2gtk-4.1 style names + if strings.HasPrefix(name, "gtk+-") { + // gtk+-3.0 -> libgtk-3 + name = "libgtk-" + strings.TrimPrefix(name, "gtk+-") + name = strings.Split(name, ".")[0] + } else if strings.HasPrefix(name, "webkit2gtk-") { + // webkit2gtk-4.1 -> libwebkit2gtk-4.1 + name = "lib" + name + } else if !strings.HasPrefix(name, "lib") { + name = "lib" + name + } + + return name +} + +// GetAllLibPaths returns all library paths from LD_LIBRARY_PATH, default paths, +// and dynamically discovered paths from Flatpak, Snap, and Nix. +// It does NOT include the current directory for security reasons. +func GetAllLibPaths() []string { + var paths []string + + // Add LD_LIBRARY_PATH entries first (highest priority) + if ldPath := os.Getenv("LD_LIBRARY_PATH"); ldPath != "" { + for _, p := range strings.Split(ldPath, ":") { + if p != "" { + paths = append(paths, p) + } + } + } + + // Add default system paths + paths = append(paths, defaultLibPaths...) + + // Add dynamically discovered paths from package managers + paths = append(paths, getFlatpakLibPaths()...) + paths = append(paths, getSnapLibPaths()...) + paths = append(paths, getNixLibPaths()...) + + return paths +} + +// FindOptions controls library search behavior. +type FindOptions struct { + // IncludeCurrentDir includes "." in the search path. + // WARNING: This is a security risk and should only be used for development. + IncludeCurrentDir bool + + // ExtraPaths are additional paths to search before the defaults. + ExtraPaths []string +} + +// FindLibraryPathWithOptions attempts to find the path to a library with custom options. +func FindLibraryPathWithOptions(libName string, opts FindOptions) (string, error) { + // Try pkg-config first (most reliable when available) + if path, err := findWithPkgConfig(libName); err == nil { + return path, nil + } + + // Try ldconfig cache + if path, err := findWithLdconfig(libName); err == nil { + return path, nil + } + + // Build search paths - include all dynamic paths too + allPaths := GetAllLibPaths() + searchPaths := make([]string, 0, len(opts.ExtraPaths)+len(allPaths)+1) + + if opts.IncludeCurrentDir { + if cwd, err := os.Getwd(); err == nil { + searchPaths = append(searchPaths, cwd) + } + } + + searchPaths = append(searchPaths, opts.ExtraPaths...) + searchPaths = append(searchPaths, allPaths...) + + // Search the paths + searchName := pkgConfigToLibName(libName) + for _, dir := range searchPaths { + if _, err := os.Stat(dir); err != nil { + continue + } + + pattern := filepath.Join(dir, searchName+"*.so*") + matches, err := filepath.Glob(pattern) + if err == nil && len(matches) > 0 { + return dir, nil + } + + pcPath := filepath.Join(dir, "pkgconfig", libName+".pc") + if _, err := os.Stat(pcPath); err == nil { + return dir, nil + } + } + + return "", &LibraryNotFoundError{Name: libName} +} + +// LibraryNotFoundError is returned when a library cannot be found. +type LibraryNotFoundError struct { + Name string +} + +func (e *LibraryNotFoundError) Error() string { + return "library not found: " + e.Name +} + +// LibraryMatch holds information about a found library. +type LibraryMatch struct { + // Name is the pkg-config name that was searched for. + Name string + // Path is the directory containing the library. + Path string +} + +// FindFirstLibrary searches for multiple libraries in parallel and returns +// the first one found. This is useful when you don't know the exact version +// of a library installed (e.g., gtk+-3.0 vs gtk+-4.0). +// +// The search order among candidates is non-deterministic - whichever is found +// first wins. If you need a specific preference order, list preferred libraries +// first and use FindFirstLibraryOrdered instead. +// +// Example: +// +// match, err := FindFirstLibrary("webkit2gtk-4.1", "webkit2gtk-4.0", "webkit2gtk-6.0") +// if err != nil { +// log.Fatal("No WebKit2GTK found") +// } +// fmt.Printf("Found %s at %s\n", match.Name, match.Path) +func FindFirstLibrary(libNames ...string) (*LibraryMatch, error) { + if len(libNames) == 0 { + return nil, &LibraryNotFoundError{Name: "no libraries specified"} + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + results := make(chan *LibraryMatch, len(libNames)) + var wg sync.WaitGroup + + for _, name := range libNames { + wg.Add(1) + go func(libName string) { + defer wg.Done() + if path, err := findLibraryPathCtx(ctx, libName); err == nil { + select { + case results <- &LibraryMatch{Name: libName, Path: path}: + case <-ctx.Done(): + } + } + }(name) + } + + // Close results when all goroutines complete + go func() { + wg.Wait() + close(results) + }() + + if result := <-results; result != nil { + return result, nil + } + + return nil, &LibraryNotFoundError{Name: strings.Join(libNames, ", ")} +} + +// FindFirstLibraryOrdered searches for libraries in order of preference, +// returning the first one found. Unlike FindFirstLibrary, this respects +// the order of candidates - earlier entries are preferred. +// +// This is useful when you want to prefer newer library versions: +// +// match, err := FindFirstLibraryOrdered("gtk+-4.0", "gtk+-3.0") +// // Will return gtk+-4.0 if available, otherwise gtk+-3.0 +func FindFirstLibraryOrdered(libNames ...string) (*LibraryMatch, error) { + if len(libNames) == 0 { + return nil, &LibraryNotFoundError{Name: "no libraries specified"} + } + + for _, name := range libNames { + if path, err := FindLibraryPath(name); err == nil { + return &LibraryMatch{Name: name, Path: path}, nil + } + } + + return nil, &LibraryNotFoundError{Name: strings.Join(libNames, ", ")} +} + +// FindAllLibraries searches for multiple libraries in parallel and returns +// all that are found. This is useful for discovering which library versions +// are available on the system. +// +// Example: +// +// matches := FindAllLibraries("gtk+-3.0", "gtk+-4.0", "webkit2gtk-4.0", "webkit2gtk-4.1") +// for _, m := range matches { +// fmt.Printf("Found %s at %s\n", m.Name, m.Path) +// } +func FindAllLibraries(libNames ...string) []LibraryMatch { + if len(libNames) == 0 { + return nil + } + + results := make(chan *LibraryMatch, len(libNames)) + var wg sync.WaitGroup + + for _, name := range libNames { + wg.Add(1) + go func(libName string) { + defer wg.Done() + if path, err := FindLibraryPath(libName); err == nil { + results <- &LibraryMatch{Name: libName, Path: path} + } + }(name) + } + + // Close results when all goroutines complete + go func() { + wg.Wait() + close(results) + }() + + var matches []LibraryMatch + for result := range results { + matches = append(matches, *result) + } + + return matches +} + +// findLibraryPathCtx is FindLibraryPath with context support. +func findLibraryPathCtx(ctx context.Context, libName string) (string, error) { + // Create a child context for this search + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + results := make(chan searchResult, 3) + var wg sync.WaitGroup + wg.Add(3) + + go func() { + defer wg.Done() + if path, err := findWithPkgConfigCtx(ctx, libName); err == nil { + select { + case results <- searchResult{path: path, source: "pkg-config"}: + case <-ctx.Done(): + } + } + }() + + go func() { + defer wg.Done() + if path, err := findWithLdconfigCtx(ctx, libName); err == nil { + select { + case results <- searchResult{path: path, source: "ldconfig"}: + case <-ctx.Done(): + } + } + }() + + go func() { + defer wg.Done() + if path, err := findInCommonPathsCtx(ctx, libName); err == nil { + select { + case results <- searchResult{path: path, source: "filesystem"}: + case <-ctx.Done(): + } + } + }() + + go func() { + wg.Wait() + close(results) + }() + + if result, ok := <-results; ok { + return result.path, nil + } + + return "", &LibraryNotFoundError{Name: libName} +} diff --git a/v3/internal/libpath/libpath_linux_test.go b/v3/internal/libpath/libpath_linux_test.go new file mode 100644 index 000000000..0f2e91bef --- /dev/null +++ b/v3/internal/libpath/libpath_linux_test.go @@ -0,0 +1,769 @@ +//go:build linux + +package libpath + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +func TestPkgConfigToLibName(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"gtk+-3.0", "libgtk-3"}, + {"gtk+-4.0", "libgtk-4"}, + {"webkit2gtk-4.1", "libwebkit2gtk-4.1"}, + {"webkit2gtk-4.0", "libwebkit2gtk-4.0"}, + {"glib-2.0", "libglib-2.0"}, + {"libsoup-3.0", "libsoup-3.0"}, + {"cairo", "libcairo"}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result := pkgConfigToLibName(tt.input) + if result != tt.expected { + t.Errorf("pkgConfigToLibName(%q) = %q, want %q", tt.input, result, tt.expected) + } + }) + } +} + +func TestGetAllLibPaths(t *testing.T) { + paths := GetAllLibPaths() + + if len(paths) == 0 { + t.Error("GetAllLibPaths() returned empty slice") + } + + // Check that default paths are included + hasUsrLib := false + for _, p := range paths { + if p == "/usr/lib" || p == "/usr/lib64" { + hasUsrLib = true + break + } + } + if !hasUsrLib { + t.Error("GetAllLibPaths() should include /usr/lib or /usr/lib64") + } +} + +func TestGetAllLibPaths_WithLDPath(t *testing.T) { + // Save and restore LD_LIBRARY_PATH + original := os.Getenv("LD_LIBRARY_PATH") + defer os.Setenv("LD_LIBRARY_PATH", original) + + testPath := "/test/custom/lib:/another/path" + os.Setenv("LD_LIBRARY_PATH", testPath) + + paths := GetAllLibPaths() + + // First paths should be from LD_LIBRARY_PATH + if len(paths) < 2 { + t.Fatal("Expected at least 2 paths") + } + if paths[0] != "/test/custom/lib" { + t.Errorf("First path should be /test/custom/lib, got %s", paths[0]) + } + if paths[1] != "/another/path" { + t.Errorf("Second path should be /another/path, got %s", paths[1]) + } +} + +func TestLibraryNotFoundError(t *testing.T) { + err := &LibraryNotFoundError{Name: "testlib"} + expected := "library not found: testlib" + if err.Error() != expected { + t.Errorf("Error() = %q, want %q", err.Error(), expected) + } +} + +func TestFindLibraryPath_NotFound(t *testing.T) { + _, err := FindLibraryPath("nonexistent-library-xyz-123") + if err == nil { + t.Error("Expected error for nonexistent library") + } + + var notFoundErr *LibraryNotFoundError + if _, ok := err.(*LibraryNotFoundError); !ok { + t.Errorf("Expected LibraryNotFoundError, got %T", err) + } else { + notFoundErr = err.(*LibraryNotFoundError) + if notFoundErr.Name != "nonexistent-library-xyz-123" { + t.Errorf("Error name = %q, want %q", notFoundErr.Name, "nonexistent-library-xyz-123") + } + } +} + +func TestFindLibraryFile_NotFound(t *testing.T) { + _, err := FindLibraryFile("libnonexistent-xyz-123.so") + if err == nil { + t.Error("Expected error for nonexistent library file") + } +} + +// Integration tests - these depend on system state +// They're skipped if the required tools/libraries aren't available + +func TestFindLibraryPath_WithPkgConfig(t *testing.T) { + // Skip if pkg-config is not available + if _, err := exec.LookPath("pkg-config"); err != nil { + t.Skip("pkg-config not available") + } + + // Try to find a common library that's likely installed + commonLibs := []string{"glib-2.0", "zlib"} + + for _, lib := range commonLibs { + // Check if pkg-config knows about this library + cmd := exec.Command("pkg-config", "--exists", lib) + if cmd.Run() != nil { + continue + } + + t.Run(lib, func(t *testing.T) { + path, err := FindLibraryPath(lib) + if err != nil { + t.Errorf("FindLibraryPath(%q) failed: %v", lib, err) + return + } + + // Verify the path exists + if _, err := os.Stat(path); err != nil { + t.Errorf("Returned path %q does not exist", path) + } + }) + return // Only need to test one + } + + t.Skip("No common libraries found via pkg-config") +} + +func TestFindLibraryFile_Integration(t *testing.T) { + // Try to find libc which should exist on any Linux system + libcNames := []string{"libc.so.6", "libc.so"} + + for _, name := range libcNames { + path, err := FindLibraryFile(name) + if err == nil { + // Verify the path exists + if _, err := os.Stat(path); err != nil { + t.Errorf("Returned path %q does not exist", path) + } + return + } + } + + t.Skip("Could not find libc.so - unusual system configuration") +} + +func TestFindInCommonPaths(t *testing.T) { + // Create a temporary directory structure for testing + tmpDir := t.TempDir() + + // Create a fake library directory with a fake .so file + libDir := filepath.Join(tmpDir, "lib") + if err := os.MkdirAll(libDir, 0755); err != nil { + t.Fatal(err) + } + + // Create a fake library file + fakeLib := filepath.Join(libDir, "libfaketest.so.1") + if err := os.WriteFile(fakeLib, []byte{}, 0644); err != nil { + t.Fatal(err) + } + + // Temporarily add our test dir to defaultLibPaths + originalPaths := defaultLibPaths + defaultLibPaths = append([]string{libDir}, defaultLibPaths...) + defer func() { defaultLibPaths = originalPaths }() + + // Now test finding it + path, err := findInCommonPaths("faketest") + if err != nil { + t.Errorf("findInCommonPaths(\"faketest\") failed: %v", err) + return + } + + if path != libDir { + t.Errorf("findInCommonPaths(\"faketest\") = %q, want %q", path, libDir) + } +} + +func TestFindWithLdconfig(t *testing.T) { + // Skip if ldconfig is not available + if _, err := exec.LookPath("ldconfig"); err != nil { + t.Skip("ldconfig not available") + } + + // Check if we can run ldconfig -p + cmd := exec.Command("ldconfig", "-p") + output, err := cmd.Output() + if err != nil { + t.Skip("ldconfig -p failed") + } + + // Find any library from the output to test with + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.Contains(line, "=>") && strings.Contains(line, "libc.so") { + // We found libc, try to find it + path, err := findWithLdconfig("glib-2.0") // Common library + if err == nil { + if _, statErr := os.Stat(path); statErr != nil { + t.Errorf("Returned path %q does not exist", path) + } + return + } + // If glib not found, that's okay - just means it's not installed + break + } + } +} + +func TestFindLibraryPathWithOptions_IncludeCurrentDir(t *testing.T) { + // Create a temporary directory and change to it + tmpDir := t.TempDir() + + // Create a fake library file in the temp dir + fakeLib := filepath.Join(tmpDir, "libcwdtest.so.1") + if err := os.WriteFile(fakeLib, []byte{}, 0644); err != nil { + t.Fatal(err) + } + + // Save current directory + origDir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + defer os.Chdir(origDir) + + // Change to temp directory + if err := os.Chdir(tmpDir); err != nil { + t.Fatal(err) + } + + // Without IncludeCurrentDir, should not find it + _, err = FindLibraryPathWithOptions("cwdtest", FindOptions{IncludeCurrentDir: false}) + if err == nil { + t.Error("Expected error without IncludeCurrentDir") + } + + // With IncludeCurrentDir, should find it + path, err := FindLibraryPathWithOptions("cwdtest", FindOptions{IncludeCurrentDir: true}) + if err != nil { + t.Errorf("FindLibraryPathWithOptions with IncludeCurrentDir failed: %v", err) + return + } + + if path != tmpDir { + t.Errorf("Expected path %q, got %q", tmpDir, path) + } +} + +func TestFindLibraryPathWithOptions_ExtraPaths(t *testing.T) { + // Create a temporary directory with a fake library + tmpDir := t.TempDir() + + fakeLib := filepath.Join(tmpDir, "libextratest.so.1") + if err := os.WriteFile(fakeLib, []byte{}, 0644); err != nil { + t.Fatal(err) + } + + // Should find it with ExtraPaths + path, err := FindLibraryPathWithOptions("extratest", FindOptions{ + ExtraPaths: []string{tmpDir}, + }) + if err != nil { + t.Errorf("FindLibraryPathWithOptions with ExtraPaths failed: %v", err) + return + } + + if path != tmpDir { + t.Errorf("Expected path %q, got %q", tmpDir, path) + } +} + +func TestDefaultLibPaths_ContainsDistros(t *testing.T) { + // Verify that paths for various distros are included + expectedPaths := map[string][]string{ + "Debian/Ubuntu": {"/usr/lib/x86_64-linux-gnu", "/usr/lib/aarch64-linux-gnu"}, + "Fedora/RHEL": {"/usr/lib64/gtk-3.0", "/usr/lib64/gtk-4.0"}, + "Arch": {"/usr/lib/webkit2gtk-4.0", "/usr/lib/webkit2gtk-4.1"}, + "openSUSE": {"/usr/lib64/gcc/x86_64-suse-linux"}, + "Local": {"/usr/local/lib", "/usr/local/lib64"}, + } + + for distro, paths := range expectedPaths { + for _, path := range paths { + found := false + for _, defaultPath := range defaultLibPaths { + if defaultPath == path { + found = true + break + } + } + if !found { + t.Errorf("Missing %s path: %s", distro, path) + } + } + } +} + +func TestGetFlatpakLibPaths(t *testing.T) { + // This test just ensures the function doesn't panic + // Actual paths depend on system state + paths := getFlatpakLibPaths() + t.Logf("Found %d Flatpak lib paths", len(paths)) + for _, p := range paths { + t.Logf(" %s", p) + } +} + +func TestGetSnapLibPaths(t *testing.T) { + // This test just ensures the function doesn't panic + // Actual paths depend on system state + paths := getSnapLibPaths() + t.Logf("Found %d Snap lib paths", len(paths)) + for _, p := range paths { + t.Logf(" %s", p) + } +} + +func TestGetNixLibPaths(t *testing.T) { + // This test just ensures the function doesn't panic + paths := getNixLibPaths() + t.Logf("Found %d Nix lib paths", len(paths)) + for _, p := range paths { + t.Logf(" %s", p) + } +} + +func TestGetAllLibPaths_IncludesDynamicPaths(t *testing.T) { + paths := GetAllLibPaths() + + // Should have at least the default paths + if len(paths) < len(defaultLibPaths) { + t.Errorf("GetAllLibPaths returned fewer paths (%d) than defaultLibPaths (%d)", + len(paths), len(defaultLibPaths)) + } + + // Log all paths for debugging + t.Logf("Total paths: %d", len(paths)) +} + +func TestGetAllLibPaths_DoesNotIncludeCurrentDir(t *testing.T) { + paths := GetAllLibPaths() + + for _, p := range paths { + if p == "." { + t.Error("GetAllLibPaths should not include '.' for security reasons") + } + } +} + +func TestInvalidateCache(t *testing.T) { + // First call populates cache + paths1 := GetAllLibPaths() + + // Invalidate and call again + InvalidateCache() + paths2 := GetAllLibPaths() + + // Should get same results (assuming no system changes) + if len(paths1) != len(paths2) { + t.Logf("Path counts differ after invalidation: %d vs %d", len(paths1), len(paths2)) + // This is not necessarily an error, just informational + } + + // Verify cache is working by checking getFlatpakLibPaths is fast + // (would be slow if cache wasn't working) + for i := 0; i < 100; i++ { + _ = getFlatpakLibPaths() + } +} + +func TestFindLibraryPath_ParallelConsistency(t *testing.T) { + // Skip if pkg-config is not available + if _, err := exec.LookPath("pkg-config"); err != nil { + t.Skip("pkg-config not available") + } + + // Check if glib-2.0 is available + cmd := exec.Command("pkg-config", "--exists", "glib-2.0") + if cmd.Run() != nil { + t.Skip("glib-2.0 not installed") + } + + // Run parallel and sequential versions multiple times + // to ensure they return consistent results + for i := 0; i < 10; i++ { + parallelPath, parallelErr := FindLibraryPath("glib-2.0") + seqPath, seqErr := FindLibraryPathSequential("glib-2.0") + + if parallelErr != nil && seqErr == nil { + t.Errorf("Parallel failed but sequential succeeded: %v", parallelErr) + } + if parallelErr == nil && seqErr != nil { + t.Errorf("Sequential failed but parallel succeeded: %v", seqErr) + } + + // Both should find the library (path might differ if found by different methods) + if parallelErr != nil { + t.Errorf("Iteration %d: parallel search failed: %v", i, parallelErr) + } + if seqErr != nil { + t.Errorf("Iteration %d: sequential search failed: %v", i, seqErr) + } + + // Log paths for debugging + t.Logf("Iteration %d: parallel=%s, sequential=%s", i, parallelPath, seqPath) + } +} + +func TestFindLibraryPath_ParallelNotFound(t *testing.T) { + // Both parallel and sequential should return the same error for non-existent libs + _, parallelErr := FindLibraryPath("nonexistent-library-xyz-123") + _, seqErr := FindLibraryPathSequential("nonexistent-library-xyz-123") + + if parallelErr == nil { + t.Error("Parallel search should fail for nonexistent library") + } + if seqErr == nil { + t.Error("Sequential search should fail for nonexistent library") + } + + // Both should return LibraryNotFoundError + if _, ok := parallelErr.(*LibraryNotFoundError); !ok { + t.Errorf("Parallel: expected LibraryNotFoundError, got %T", parallelErr) + } + if _, ok := seqErr.(*LibraryNotFoundError); !ok { + t.Errorf("Sequential: expected LibraryNotFoundError, got %T", seqErr) + } +} + +// Benchmarks + +// BenchmarkFindLibraryPath benchmarks finding a library via the full search chain. +func BenchmarkFindLibraryPath(b *testing.B) { + // Test with glib-2.0 which is commonly installed + if _, err := exec.LookPath("pkg-config"); err != nil { + b.Skip("pkg-config not available") + } + cmd := exec.Command("pkg-config", "--exists", "glib-2.0") + if cmd.Run() != nil { + b.Skip("glib-2.0 not installed") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindLibraryPath("glib-2.0") + } +} + +// BenchmarkFindLibraryPath_NotFound benchmarks the worst case (library not found). +func BenchmarkFindLibraryPath_NotFound(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindLibraryPath("nonexistent-library-xyz-123") + } +} + +// BenchmarkFindLibraryFile benchmarks finding a specific library file. +func BenchmarkFindLibraryFile(b *testing.B) { + // libc.so.6 should exist on any Linux system + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindLibraryFile("libc.so.6") + } +} + +// BenchmarkGetAllLibPaths benchmarks collecting all library paths. +func BenchmarkGetAllLibPaths(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = GetAllLibPaths() + } +} + +// BenchmarkFindWithPkgConfig benchmarks pkg-config lookup directly. +func BenchmarkFindWithPkgConfig(b *testing.B) { + if _, err := exec.LookPath("pkg-config"); err != nil { + b.Skip("pkg-config not available") + } + cmd := exec.Command("pkg-config", "--exists", "glib-2.0") + if cmd.Run() != nil { + b.Skip("glib-2.0 not installed") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = findWithPkgConfig("glib-2.0") + } +} + +// BenchmarkFindWithLdconfig benchmarks ldconfig lookup directly. +func BenchmarkFindWithLdconfig(b *testing.B) { + if _, err := exec.LookPath("ldconfig"); err != nil { + b.Skip("ldconfig not available") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = findWithLdconfig("glib-2.0") + } +} + +// BenchmarkFindInCommonPaths benchmarks filesystem scanning. +func BenchmarkFindInCommonPaths(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = findInCommonPaths("glib-2.0") + } +} + +// BenchmarkGetFlatpakLibPaths benchmarks Flatpak path discovery. +func BenchmarkGetFlatpakLibPaths(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = getFlatpakLibPaths() + } +} + +// BenchmarkGetSnapLibPaths benchmarks Snap path discovery. +func BenchmarkGetSnapLibPaths(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = getSnapLibPaths() + } +} + +// BenchmarkGetNixLibPaths benchmarks Nix path discovery. +func BenchmarkGetNixLibPaths(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = getNixLibPaths() + } +} + +// BenchmarkPkgConfigToLibName benchmarks the name conversion function. +func BenchmarkPkgConfigToLibName(b *testing.B) { + names := []string{"gtk+-3.0", "webkit2gtk-4.1", "glib-2.0", "cairo", "libsoup-3.0"} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, name := range names { + _ = pkgConfigToLibName(name) + } + } +} + +// BenchmarkFindLibraryPathSequential benchmarks the sequential search. +func BenchmarkFindLibraryPathSequential(b *testing.B) { + if _, err := exec.LookPath("pkg-config"); err != nil { + b.Skip("pkg-config not available") + } + cmd := exec.Command("pkg-config", "--exists", "glib-2.0") + if cmd.Run() != nil { + b.Skip("glib-2.0 not installed") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindLibraryPathSequential("glib-2.0") + } +} + +// BenchmarkFindLibraryPathSequential_NotFound benchmarks the sequential worst case. +func BenchmarkFindLibraryPathSequential_NotFound(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindLibraryPathSequential("nonexistent-library-xyz-123") + } +} + +// BenchmarkFindLibraryPathParallel explicitly tests parallel performance. +func BenchmarkFindLibraryPathParallel(b *testing.B) { + if _, err := exec.LookPath("pkg-config"); err != nil { + b.Skip("pkg-config not available") + } + cmd := exec.Command("pkg-config", "--exists", "glib-2.0") + if cmd.Run() != nil { + b.Skip("glib-2.0 not installed") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindLibraryPath("glib-2.0") + } +} + +// Tests for multi-library search functions + +func TestFindFirstLibrary(t *testing.T) { + if _, err := exec.LookPath("pkg-config"); err != nil { + t.Skip("pkg-config not available") + } + + // Test with a mix of existing and non-existing libraries + match, err := FindFirstLibrary("nonexistent-xyz", "glib-2.0", "also-nonexistent") + if err != nil { + t.Skipf("glib-2.0 not installed: %v", err) + } + + if match.Name != "glib-2.0" { + t.Errorf("Expected glib-2.0, got %s", match.Name) + } + if match.Path == "" { + t.Error("Expected non-empty path") + } +} + +func TestFindFirstLibrary_AllNotFound(t *testing.T) { + _, err := FindFirstLibrary("nonexistent-1", "nonexistent-2", "nonexistent-3") + if err == nil { + t.Error("Expected error for all non-existent libraries") + } +} + +func TestFindFirstLibrary_Empty(t *testing.T) { + _, err := FindFirstLibrary() + if err == nil { + t.Error("Expected error for empty library list") + } +} + +func TestFindFirstLibraryOrdered(t *testing.T) { + if _, err := exec.LookPath("pkg-config"); err != nil { + t.Skip("pkg-config not available") + } + + // glib-2.0 should be found, and since it's first, it should be returned + match, err := FindFirstLibraryOrdered("glib-2.0", "nonexistent-xyz") + if err != nil { + t.Skipf("glib-2.0 not installed: %v", err) + } + + if match.Name != "glib-2.0" { + t.Errorf("Expected glib-2.0, got %s", match.Name) + } +} + +func TestFindFirstLibraryOrdered_PreferFirst(t *testing.T) { + if _, err := exec.LookPath("pkg-config"); err != nil { + t.Skip("pkg-config not available") + } + + // Check what GTK versions are available + gtk4Available := exec.Command("pkg-config", "--exists", "gtk4").Run() == nil + gtk3Available := exec.Command("pkg-config", "--exists", "gtk+-3.0").Run() == nil + + if !gtk4Available && !gtk3Available { + t.Skip("Neither GTK3 nor GTK4 installed") + } + + // If both available, test that order is respected + if gtk4Available && gtk3Available { + match, err := FindFirstLibraryOrdered("gtk4", "gtk+-3.0") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if match.Name != "gtk4" { + t.Errorf("Expected gtk4 (first in order), got %s", match.Name) + } + + // Reverse order + match, err = FindFirstLibraryOrdered("gtk+-3.0", "gtk4") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if match.Name != "gtk+-3.0" { + t.Errorf("Expected gtk+-3.0 (first in order), got %s", match.Name) + } + } +} + +func TestFindAllLibraries(t *testing.T) { + if _, err := exec.LookPath("pkg-config"); err != nil { + t.Skip("pkg-config not available") + } + + matches := FindAllLibraries("glib-2.0", "nonexistent-xyz", "zlib") + + // Should find at least glib-2.0 on most systems + if len(matches) == 0 { + t.Skip("No common libraries found") + } + + t.Logf("Found %d libraries:", len(matches)) + for _, m := range matches { + t.Logf(" %s at %s", m.Name, m.Path) + } + + // Verify no duplicates and no nonexistent library + seen := make(map[string]bool) + for _, m := range matches { + if m.Name == "nonexistent-xyz" { + t.Error("Should not have found nonexistent library") + } + if seen[m.Name] { + t.Errorf("Duplicate match for %s", m.Name) + } + seen[m.Name] = true + } +} + +func TestFindAllLibraries_Empty(t *testing.T) { + matches := FindAllLibraries() + if len(matches) != 0 { + t.Error("Expected empty result for empty input") + } +} + +func TestFindAllLibraries_AllNotFound(t *testing.T) { + matches := FindAllLibraries("nonexistent-1", "nonexistent-2") + if len(matches) != 0 { + t.Errorf("Expected empty result, got %d matches", len(matches)) + } +} + +// Benchmarks for multi-library search + +func BenchmarkFindFirstLibrary(b *testing.B) { + if _, err := exec.LookPath("pkg-config"); err != nil { + b.Skip("pkg-config not available") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindFirstLibrary("nonexistent-1", "glib-2.0", "nonexistent-2") + } +} + +func BenchmarkFindFirstLibraryOrdered(b *testing.B) { + if _, err := exec.LookPath("pkg-config"); err != nil { + b.Skip("pkg-config not available") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindFirstLibraryOrdered("nonexistent-1", "glib-2.0", "nonexistent-2") + } +} + +func BenchmarkFindAllLibraries(b *testing.B) { + if _, err := exec.LookPath("pkg-config"); err != nil { + b.Skip("pkg-config not available") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = FindAllLibraries("glib-2.0", "zlib", "nonexistent-xyz") + } +} diff --git a/v3/internal/libpath/libpath_other.go b/v3/internal/libpath/libpath_other.go new file mode 100644 index 000000000..d1c7bc77d --- /dev/null +++ b/v3/internal/libpath/libpath_other.go @@ -0,0 +1,73 @@ +//go:build !linux + +package libpath + +// FindLibraryPath is a stub for non-Linux platforms. +func FindLibraryPath(libName string) (string, error) { + return "", &LibraryNotFoundError{Name: libName} +} + +// FindLibraryFile is a stub for non-Linux platforms. +func FindLibraryFile(fileName string) (string, error) { + return "", &LibraryNotFoundError{Name: fileName} +} + +// GetAllLibPaths returns an empty slice on non-Linux platforms. +func GetAllLibPaths() []string { + return nil +} + +// InvalidateCache is a no-op on non-Linux platforms. +func InvalidateCache() {} + +// FindOptions controls library search behavior. +type FindOptions struct { + IncludeCurrentDir bool + ExtraPaths []string +} + +// FindLibraryPathWithOptions is a stub for non-Linux platforms. +func FindLibraryPathWithOptions(libName string, opts FindOptions) (string, error) { + return "", &LibraryNotFoundError{Name: libName} +} + +// LibraryNotFoundError is returned when a library cannot be found. +type LibraryNotFoundError struct { + Name string +} + +func (e *LibraryNotFoundError) Error() string { + return "library not found: " + e.Name +} + +// LibraryMatch holds information about a found library. +type LibraryMatch struct { + Name string + Path string +} + +// FindFirstLibrary is a stub for non-Linux platforms. +func FindFirstLibrary(libNames ...string) (*LibraryMatch, error) { + if len(libNames) == 0 { + return nil, &LibraryNotFoundError{Name: "no libraries specified"} + } + return nil, &LibraryNotFoundError{Name: libNames[0]} +} + +// FindFirstLibraryOrdered is a stub for non-Linux platforms. +func FindFirstLibraryOrdered(libNames ...string) (*LibraryMatch, error) { + if len(libNames) == 0 { + return nil, &LibraryNotFoundError{Name: "no libraries specified"} + } + return nil, &LibraryNotFoundError{Name: libNames[0]} +} + +// FindAllLibraries is a stub for non-Linux platforms. +func FindAllLibraries(libNames ...string) []LibraryMatch { + return nil +} + +// FindLibraryPathSequential is a stub for non-Linux platforms. +func FindLibraryPathSequential(libName string) (string, error) { + return "", &LibraryNotFoundError{Name: libName} +} diff --git a/v3/internal/libpath/nix_linux.go b/v3/internal/libpath/nix_linux.go new file mode 100644 index 000000000..74d8487a9 --- /dev/null +++ b/v3/internal/libpath/nix_linux.go @@ -0,0 +1,28 @@ +//go:build linux + +package libpath + +import "os" + +// getNixLibPaths returns cached library paths for Nix/NixOS installations. +func getNixLibPaths() []string { + return cache.getNix() +} + +// discoverNixLibPaths scans for Nix library paths. +func discoverNixLibPaths() []string { + var paths []string + + nixProfileLib := os.ExpandEnv("$HOME/.nix-profile/lib") + if _, err := os.Stat(nixProfileLib); err == nil { + paths = append(paths, nixProfileLib) + } + + // System Nix store - packages expose libs through profiles + nixStoreLib := "/run/current-system/sw/lib" + if _, err := os.Stat(nixStoreLib); err == nil { + paths = append(paths, nixStoreLib) + } + + return paths +} diff --git a/v3/internal/libpath/snap_linux.go b/v3/internal/libpath/snap_linux.go new file mode 100644 index 000000000..99def76ac --- /dev/null +++ b/v3/internal/libpath/snap_linux.go @@ -0,0 +1,42 @@ +//go:build linux + +package libpath + +import ( + "os" + "path/filepath" +) + +// getSnapLibPaths returns cached library paths from installed Snap packages. +func getSnapLibPaths() []string { + return cache.getSnap() +} + +// discoverSnapLibPaths scans for Snap package library directories. +// Scans /snap/*/current/usr/lib* directories. +func discoverSnapLibPaths() []string { + var paths []string + + snapDir := "/snap" + if _, err := os.Stat(snapDir); err != nil { + return paths + } + + // Find all snap packages with lib directories + patterns := []string{ + filepath.Join(snapDir, "*", "current", "usr", "lib"), + filepath.Join(snapDir, "*", "current", "usr", "lib64"), + filepath.Join(snapDir, "*", "current", "usr", "lib", "*-linux-gnu"), + filepath.Join(snapDir, "*", "current", "lib"), + filepath.Join(snapDir, "*", "current", "lib", "*-linux-gnu"), + } + + for _, pattern := range patterns { + matches, err := filepath.Glob(pattern) + if err == nil { + paths = append(paths, matches...) + } + } + + return paths +} diff --git a/v3/internal/operatingsystem/os.go b/v3/internal/operatingsystem/os.go new file mode 100644 index 000000000..2d5656281 --- /dev/null +++ b/v3/internal/operatingsystem/os.go @@ -0,0 +1,23 @@ +package operatingsystem + +// OS contains information about the operating system +type OS struct { + ID string + Name string + Version string + Branding string +} + +func (o *OS) AsLogSlice() []any { + return []any{ + "ID", o.ID, + "Name", o.Name, + "Version", o.Version, + "Branding", o.Branding, + } +} + +// Info retrieves information about the current platform +func Info() (*OS, error) { + return platformInfo() +} diff --git a/v3/internal/operatingsystem/os_android.go b/v3/internal/operatingsystem/os_android.go new file mode 100644 index 000000000..494b84d78 --- /dev/null +++ b/v3/internal/operatingsystem/os_android.go @@ -0,0 +1,17 @@ +//go:build android + +package operatingsystem + +import ( + "fmt" + "runtime" +) + +func platformInfo() (*OS, error) { + return &OS{ + ID: "android", + Name: "Android", + Version: fmt.Sprintf("Go %s", runtime.Version()), + Branding: "Android", + }, nil +} diff --git a/v3/internal/operatingsystem/os_darwin.go b/v3/internal/operatingsystem/os_darwin.go new file mode 100644 index 000000000..2975c76f1 --- /dev/null +++ b/v3/internal/operatingsystem/os_darwin.go @@ -0,0 +1,72 @@ +//go:build darwin + +package operatingsystem + +import ( + "os/exec" + "strings" +) + +var macOSNames = map[string]string{ + "10.10": "Yosemite", + "10.11": "El Capitan", + "10.12": "Sierra", + "10.13": "High Sierra", + "10.14": "Mojave", + "10.15": "Catalina", + "11": "Big Sur", + "12": "Monterey", + "13": "Ventura", + "14": "Sonoma", + "15": "Sequoia", + // Add newer versions as they are released... +} + +func getOSName(version string) string { + trimmedVersion := version + if !strings.HasPrefix(version, "10.") { + trimmedVersion = strings.SplitN(version, ".", 2)[0] + } + name, ok := macOSNames[trimmedVersion] + if ok { + return name + } + return "MacOS " + version +} + +func getSysctlValue(key string) (string, error) { + // Run "sysctl" command + command := exec.Command("sysctl", key) + // Capture stdout + var stdout strings.Builder + command.Stdout = &stdout + // Run command + err := command.Run() + if err != nil { + return "", err + } + version := strings.TrimPrefix(stdout.String(), key+": ") + return strings.TrimSpace(version), nil +} + +func platformInfo() (*OS, error) { + // Default value + var result OS + result.ID = "Unknown" + result.Name = "MacOS" + result.Version = "Unknown" + + version, err := getSysctlValue("kern.osproductversion") + if err != nil { + return nil, err + } + result.Version = version + ID, err := getSysctlValue("kern.osversion") + if err != nil { + return nil, err + } + result.ID = ID + result.Branding = getOSName(result.Version) + + return &result, nil +} diff --git a/v3/internal/operatingsystem/os_linux.go b/v3/internal/operatingsystem/os_linux.go new file mode 100644 index 000000000..04cd39b9a --- /dev/null +++ b/v3/internal/operatingsystem/os_linux.go @@ -0,0 +1,52 @@ +//go:build linux && !android + +package operatingsystem + +import ( + "fmt" + "os" + "strings" +) + +// platformInfo is the platform specific method to get system information +func platformInfo() (*OS, error) { + _, err := os.Stat("/etc/os-release") + if os.IsNotExist(err) { + return nil, fmt.Errorf("unable to read system information") + } + + osRelease, _ := os.ReadFile("/etc/os-release") + return parseOsRelease(string(osRelease)), nil +} + +func parseOsRelease(osRelease string) *OS { + + // Default value + var result OS + result.ID = "Unknown" + result.Name = "Unknown" + result.Version = "Unknown" + + // Split into lines + lines := strings.Split(osRelease, "\n") + // Iterate lines + for _, line := range lines { + // Split each line by the equals char + splitLine := strings.SplitN(line, "=", 2) + // Check we have + if len(splitLine) != 2 { + continue + } + switch splitLine[0] { + case "ID": + result.ID = strings.ToLower(strings.Trim(splitLine[1], `"`)) + case "NAME": + result.Name = strings.Trim(splitLine[1], `"`) + case "VERSION_ID": + result.Version = strings.Trim(splitLine[1], `"`) + case "VERSION": + result.Branding = strings.Trim(splitLine[1], `"`) + } + } + return &result +} diff --git a/v3/internal/operatingsystem/os_windows.go b/v3/internal/operatingsystem/os_windows.go new file mode 100644 index 000000000..2e8c0775b --- /dev/null +++ b/v3/internal/operatingsystem/os_windows.go @@ -0,0 +1,34 @@ +//go:build windows + +package operatingsystem + +import ( + "fmt" + "github.com/wailsapp/wails/v3/pkg/w32" + + "golang.org/x/sys/windows/registry" +) + +func platformInfo() (*OS, error) { + // Default value + var result OS + result.ID = "Unknown" + result.Name = "Windows" + result.Version = "Unknown" + + // Credit: https://stackoverflow.com/a/33288328 + // Ignore errors as it isn't a showstopper + key, _ := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + + productName, _, _ := key.GetStringValue("ProductName") + currentBuild, _, _ := key.GetStringValue("CurrentBuildNumber") + displayVersion, _, _ := key.GetStringValue("DisplayVersion") + releaseId, _, _ := key.GetStringValue("ReleaseId") + + result.Name = productName + result.Version = fmt.Sprintf("%s (Build: %s)", releaseId, currentBuild) + result.ID = displayVersion + result.Branding = w32.GetBranding() + + return &result, key.Close() +} diff --git a/v3/internal/operatingsystem/version_windows.go b/v3/internal/operatingsystem/version_windows.go new file mode 100644 index 000000000..a8f53d134 --- /dev/null +++ b/v3/internal/operatingsystem/version_windows.go @@ -0,0 +1,62 @@ +//go:build windows + +package operatingsystem + +import ( + "strconv" + + "golang.org/x/sys/windows/registry" +) + +type WindowsVersionInfo struct { + Major int + Minor int + Build int + DisplayVersion string +} + +func (w *WindowsVersionInfo) IsWindowsVersionAtLeast(major, minor, buildNumber int) bool { + return w.Major >= major && w.Minor >= minor && w.Build >= buildNumber +} + +func GetWindowsVersionInfo() (*WindowsVersionInfo, error) { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + if err != nil { + return nil, err + } + + return &WindowsVersionInfo{ + Major: regDWORDKeyAsInt(key, "CurrentMajorVersionNumber"), + Minor: regDWORDKeyAsInt(key, "CurrentMinorVersionNumber"), + Build: regStringKeyAsInt(key, "CurrentBuildNumber"), + DisplayVersion: regKeyAsString(key, "DisplayVersion"), + }, nil +} + +func regDWORDKeyAsInt(key registry.Key, name string) int { + result, _, err := key.GetIntegerValue(name) + if err != nil { + return -1 + } + return int(result) +} + +func regStringKeyAsInt(key registry.Key, name string) int { + resultStr, _, err := key.GetStringValue(name) + if err != nil { + return -1 + } + result, err := strconv.Atoi(resultStr) + if err != nil { + return -1 + } + return result +} + +func regKeyAsString(key registry.Key, name string) string { + resultStr, _, err := key.GetStringValue(name) + if err != nil { + return "" + } + return resultStr +} diff --git a/v3/internal/operatingsystem/webkit_linux.go b/v3/internal/operatingsystem/webkit_linux.go new file mode 100644 index 000000000..8424f298c --- /dev/null +++ b/v3/internal/operatingsystem/webkit_linux.go @@ -0,0 +1,42 @@ +//go:build linux && !android + +package operatingsystem + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 +#include +*/ +import "C" +import "fmt" + +type WebkitVersion struct { + Major uint + Minor uint + Micro uint +} + +func GetWebkitVersion() WebkitVersion { + var major, minor, micro C.uint + major = C.webkit_get_major_version() + minor = C.webkit_get_minor_version() + micro = C.webkit_get_micro_version() + return WebkitVersion{ + Major: uint(major), + Minor: uint(minor), + Micro: uint(micro), + } +} + +func (v WebkitVersion) String() string { + return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Micro) +} + +func (v WebkitVersion) IsAtLeast(major int, minor int, micro int) bool { + if v.Major != uint(major) { + return v.Major > uint(major) + } + if v.Minor != uint(minor) { + return v.Minor > uint(minor) + } + return v.Micro >= uint(micro) +} diff --git a/v3/internal/packager/packager.go b/v3/internal/packager/packager.go new file mode 100644 index 000000000..5cf235d72 --- /dev/null +++ b/v3/internal/packager/packager.go @@ -0,0 +1,94 @@ +// Package packager provides a simplified interface for creating Linux packages using nfpm +package packager + +import ( + "fmt" + "io" + "os" + + "github.com/goreleaser/nfpm/v2" + _ "github.com/goreleaser/nfpm/v2/apk" // Register APK packager + _ "github.com/goreleaser/nfpm/v2/arch" // Register Arch Linux packager + _ "github.com/goreleaser/nfpm/v2/deb" // Register DEB packager + _ "github.com/goreleaser/nfpm/v2/ipk" // Register IPK packager + _ "github.com/goreleaser/nfpm/v2/rpm" // Register RPM packager +) + +// PackageType represents supported package formats +type PackageType string + +const ( + // DEB is for Debian/Ubuntu packages + DEB PackageType = "deb" + // RPM is for RedHat/CentOS packages + RPM PackageType = "rpm" + // APK is for Alpine Linux packages + APK PackageType = "apk" + // IPK is for OpenWrt packages + IPK PackageType = "ipk" + // ARCH is for Arch Linux packages + ARCH PackageType = "archlinux" +) + +// CreatePackageFromConfig loads a configuration file and creates a package +func CreatePackageFromConfig(pkgType PackageType, configPath string, output string) error { + // Parse nfpm config + config, err := nfpm.ParseFile(configPath) + if err != nil { + return fmt.Errorf("error parsing config file: %w", err) + } + + // Get info for the specified packager + info, err := config.Get(string(pkgType)) + if err != nil { + return fmt.Errorf("error getting packager info: %w", err) + } + + // Get the packager + packager, err := nfpm.Get(string(pkgType)) + if err != nil { + return fmt.Errorf("error getting packager: %w", err) + } + + // Create output file + out, err := os.Create(output) + if err != nil { + return fmt.Errorf("error creating output file: %w", err) + } + defer out.Close() + + // Create the package + if err := packager.Package(info, out); err != nil { + return fmt.Errorf("error creating package: %w", err) + } + + return nil +} + +// CreatePackageFromConfigWriter loads a configuration file and writes the package to the provided writer +func CreatePackageFromConfigWriter(pkgType PackageType, configPath string, output io.Writer) error { + // Parse nfpm config + config, err := nfpm.ParseFile(configPath) + if err != nil { + return fmt.Errorf("error parsing config file: %w", err) + } + + // Get info for the specified packager + info, err := config.Get(string(pkgType)) + if err != nil { + return fmt.Errorf("error getting packager info: %w", err) + } + + // Get the packager + packager, err := nfpm.Get(string(pkgType)) + if err != nil { + return fmt.Errorf("error getting packager: %w", err) + } + + // Create the package + if err := packager.Package(info, output); err != nil { + return fmt.Errorf("error creating package: %w", err) + } + + return nil +} diff --git a/v3/internal/packager/packager_test.go b/v3/internal/packager/packager_test.go new file mode 100644 index 000000000..3ed4a4f48 --- /dev/null +++ b/v3/internal/packager/packager_test.go @@ -0,0 +1,100 @@ +package packager + +import ( + "bytes" + "os" + "path/filepath" + "testing" +) + +func TestCreatePackageFromConfig(t *testing.T) { + // Create a temporary file for testing + content := []byte("test content") + tmpfile, err := os.CreateTemp("", "example") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpfile.Name()) + + if _, err := tmpfile.Write(content); err != nil { + t.Fatal(err) + } + if err := tmpfile.Close(); err != nil { + t.Fatal(err) + } + + // Create a temporary config file + configContent := []byte(` +name: test-package +version: v1.0.0 +arch: amd64 +description: Test package +maintainer: Test User +license: MIT +contents: +- src: ` + tmpfile.Name() + ` + dst: /usr/local/bin/test-file +`) + + configFile, err := os.CreateTemp("", "config*.yaml") + if err != nil { + t.Fatal(err) + } + defer os.Remove(configFile.Name()) + + if _, err := configFile.Write(configContent); err != nil { + t.Fatal(err) + } + if err := configFile.Close(); err != nil { + t.Fatal(err) + } + + // Test creating packages for each format + formats := []struct { + pkgType PackageType + ext string + }{ + {DEB, "deb"}, + {RPM, "rpm"}, + {APK, "apk"}, + {IPK, "ipk"}, + {ARCH, "pkg.tar.zst"}, + } + + for _, format := range formats { + t.Run(string(format.pkgType), func(t *testing.T) { + // Test file-based package creation + outputPath := filepath.Join(os.TempDir(), "test-package."+format.ext) + err := CreatePackageFromConfig(format.pkgType, configFile.Name(), outputPath) + if err != nil { + t.Errorf("CreatePackageFromConfig failed for %s: %v", format.pkgType, err) + } + defer os.Remove(outputPath) + + // Verify the file was created + if _, err := os.Stat(outputPath); os.IsNotExist(err) { + t.Errorf("Package file was not created for %s", format.pkgType) + } + + // Test writer-based package creation + var buf bytes.Buffer + err = CreatePackageFromConfigWriter(format.pkgType, configFile.Name(), &buf) + if err != nil { + t.Errorf("CreatePackageFromConfigWriter failed for %s: %v", format.pkgType, err) + } + + // Verify some content was written + if buf.Len() == 0 { + t.Errorf("No content was written for %s", format.pkgType) + } + }) + } + + // Test with invalid config file + t.Run("InvalidConfig", func(t *testing.T) { + err := CreatePackageFromConfig(DEB, "nonexistent.yaml", "output.deb") + if err == nil { + t.Error("Expected error for invalid config, got nil") + } + }) +} diff --git a/v3/internal/runtime/.gitignore b/v3/internal/runtime/.gitignore new file mode 100644 index 000000000..059d2134d --- /dev/null +++ b/v3/internal/runtime/.gitignore @@ -0,0 +1,5 @@ +node_modules +.task +*.tsbuildinfo +desktop/@wailsio/runtime/dist/ +desktop/@wailsio/runtime/types/ diff --git a/v3/internal/runtime/README.md b/v3/internal/runtime/README.md new file mode 100644 index 000000000..c4b43d430 --- /dev/null +++ b/v3/internal/runtime/README.md @@ -0,0 +1,3 @@ +# Runtime + +To rebuild the runtime run `task build` or if you have Wails v3 CLI, you can use `wails3 task build`. diff --git a/v3/internal/runtime/Taskfile.yaml b/v3/internal/runtime/Taskfile.yaml new file mode 100644 index 000000000..4a2a2ccf3 --- /dev/null +++ b/v3/internal/runtime/Taskfile.yaml @@ -0,0 +1,112 @@ +# https://taskfile.dev + +version: "3" + +tasks: + install-deps: + internal: true + dir: desktop/@wailsio/runtime + sources: + - package.json + cmds: + - npm install + + check: + dir: desktop/@wailsio/runtime + deps: + - install-deps + cmds: + - npm run check + + test: + dir: desktop/@wailsio/runtime + deps: + - install-deps + cmds: + - npm test + + build:debug: + internal: true + cmds: + - npx esbuild@latest desktop/@wailsio/runtime/src/index.ts --inject:desktop/compiled/main.js --format=esm --target=safari11 --bundle --ignore-annotations --tree-shaking=true --sourcemap=inline --outfile=../assetserver/bundledassets/runtime.debug.js --define:DEBUG=true + + build:production: + internal: true + cmds: + - npx esbuild@latest desktop/@wailsio/runtime/src/index.ts --inject:desktop/compiled/main.js --format=esm --target=safari11 --bundle --ignore-annotations --tree-shaking=true --minify --outfile=../assetserver/bundledassets/runtime.js --define:DEBUG=false --drop:console + + build:docs: + internal: true + dir: desktop/@wailsio/runtime + deps: + - install-deps + cmds: + - npm run build:docs + + build:docs:md: + internal: true + dir: desktop/@wailsio/runtime + deps: + - install-deps + cmds: + - npm run build:docs:md + + build:runtime: + internal: true + deps: + - build:debug + - build:production + + cmds: + - cmd: echo "Runtime build complete." + + build:all: + internal: true + cmds: + - task: generate:events + - task: build:docs + - task: build:runtime + - echo "Build Complete." + + build: + deps: + - install-deps + cmds: + - task: build:all + + docs: + summary: Generate TypeDoc documentation for the runtime + dir: desktop/@wailsio/runtime + deps: + - install-deps + cmds: + - npm run build:docs + - echo "Documentation generated at desktop/@wailsio/runtime/docs/" + + docs:md: + summary: Generate markdown documentation for the runtime + dir: desktop/@wailsio/runtime + deps: + - install-deps + cmds: + - npm run build:docs:md + - echo "Markdown documentation generated" + + generate:events: + dir: ../../tasks/events + cmds: + - go run generate.go + - go fmt ../../pkg/events/events.go + + clean: + summary: Clean built artifacts and documentation + dir: desktop/@wailsio/runtime + cmds: + - npm run clean + - echo "Cleaned runtime artifacts" + + generate: + summary: Generate events only (use runtime:build to rebuild everything) + cmds: + - task: generate:events + - echo "Events generated. Run 'wails3 task runtime:build' to rebuild runtime with updated documentation" diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/.nojekyll b/v3/internal/runtime/desktop/@wailsio/runtime/docs/.nojekyll new file mode 100644 index 000000000..e2ac6616a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/hierarchy.js b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/hierarchy.js new file mode 100644 index 000000000..b9338595d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/hierarchy.js @@ -0,0 +1 @@ +window.hierarchyData = "eJylj8EKgzAQRP9lzqlFxaC59tpD70Uk1RWDa4QkPYn/XrRUerCl0NMuO8zOmwluHIOHuso0F0WSlQKOWqY6mNF6qAkyzZdh9UBQOGlbE7O+MV3cOBhPZ9MTBHpjG6gkkwJ3x1AwNpBrdU3+uG+KujAwBGrW3kMh+OawfDlszkXsDDeO7EIo43IWkDL+yrOxxEn+YlkjdkE+QjwPs0CRZG95v5eu1t1qrqJ/S8/zAy5wjmw=" \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/highlight.css b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/highlight.css new file mode 100644 index 000000000..5ee358c5e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/highlight.css @@ -0,0 +1,78 @@ +:root { + --light-hl-0: #0000FF; + --dark-hl-0: #569CD6; + --light-hl-1: #000000; + --dark-hl-1: #D4D4D4; + --light-hl-2: #001080; + --dark-hl-2: #9CDCFE; + --light-hl-3: #795E26; + --dark-hl-3: #DCDCAA; + --light-hl-4: #008000; + --dark-hl-4: #6A9955; + --light-hl-5: #AF00DB; + --dark-hl-5: #C586C0; + --light-hl-6: #A31515; + --dark-hl-6: #CE9178; + --light-hl-7: #0070C1; + --dark-hl-7: #4FC1FF; + --light-code-background: #FFFFFF; + --dark-code-background: #1E1E1E; +} + +@media (prefers-color-scheme: light) { :root { + --hl-0: var(--light-hl-0); + --hl-1: var(--light-hl-1); + --hl-2: var(--light-hl-2); + --hl-3: var(--light-hl-3); + --hl-4: var(--light-hl-4); + --hl-5: var(--light-hl-5); + --hl-6: var(--light-hl-6); + --hl-7: var(--light-hl-7); + --code-background: var(--light-code-background); +} } + +@media (prefers-color-scheme: dark) { :root { + --hl-0: var(--dark-hl-0); + --hl-1: var(--dark-hl-1); + --hl-2: var(--dark-hl-2); + --hl-3: var(--dark-hl-3); + --hl-4: var(--dark-hl-4); + --hl-5: var(--dark-hl-5); + --hl-6: var(--dark-hl-6); + --hl-7: var(--dark-hl-7); + --code-background: var(--dark-code-background); +} } + +:root[data-theme='light'] { + --hl-0: var(--light-hl-0); + --hl-1: var(--light-hl-1); + --hl-2: var(--light-hl-2); + --hl-3: var(--light-hl-3); + --hl-4: var(--light-hl-4); + --hl-5: var(--light-hl-5); + --hl-6: var(--light-hl-6); + --hl-7: var(--light-hl-7); + --code-background: var(--light-code-background); +} + +:root[data-theme='dark'] { + --hl-0: var(--dark-hl-0); + --hl-1: var(--dark-hl-1); + --hl-2: var(--dark-hl-2); + --hl-3: var(--dark-hl-3); + --hl-4: var(--dark-hl-4); + --hl-5: var(--dark-hl-5); + --hl-6: var(--dark-hl-6); + --hl-7: var(--dark-hl-7); + --code-background: var(--dark-code-background); +} + +.hl-0 { color: var(--hl-0); } +.hl-1 { color: var(--hl-1); } +.hl-2 { color: var(--hl-2); } +.hl-3 { color: var(--hl-3); } +.hl-4 { color: var(--hl-4); } +.hl-5 { color: var(--hl-5); } +.hl-6 { color: var(--hl-6); } +.hl-7 { color: var(--hl-7); } +pre, code { background: var(--code-background); } diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/icons.js b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/icons.js new file mode 100644 index 000000000..58882d76d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/icons.js @@ -0,0 +1,18 @@ +(function() { + addIcons(); + function addIcons() { + if (document.readyState === "loading") return document.addEventListener("DOMContentLoaded", addIcons); + const svg = document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); + svg.innerHTML = `MMNEPVFCICPMFPCPTTAAATR`; + svg.style.display = "none"; + if (location.protocol === "file:") updateUseElements(); + } + + function updateUseElements() { + document.querySelectorAll("use").forEach(el => { + if (el.getAttribute("href").includes("#icon-")) { + el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); + } + }); + } +})() \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/icons.svg b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/icons.svg new file mode 100644 index 000000000..50ad5799d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/icons.svg @@ -0,0 +1 @@ +MMNEPVFCICPMFPCPTTAAATR \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/main.js b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/main.js new file mode 100644 index 000000000..2363f64c2 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/main.js @@ -0,0 +1,60 @@ +"use strict"; +window.translations={"copy":"Copy","copied":"Copied!","normally_hidden":"This member is normally hidden due to your filter settings.","hierarchy_expand":"Expand","hierarchy_collapse":"Collapse","folder":"Folder","kind_1":"Project","kind_2":"Module","kind_4":"Namespace","kind_8":"Enumeration","kind_16":"Enumeration Member","kind_32":"Variable","kind_64":"Function","kind_128":"Class","kind_256":"Interface","kind_512":"Constructor","kind_1024":"Property","kind_2048":"Method","kind_4096":"Call Signature","kind_8192":"Index Signature","kind_16384":"Constructor Signature","kind_32768":"Parameter","kind_65536":"Type Literal","kind_131072":"Type Parameter","kind_262144":"Accessor","kind_524288":"Get Signature","kind_1048576":"Set Signature","kind_2097152":"Type Alias","kind_4194304":"Reference","kind_8388608":"Document"}; +"use strict";(()=>{var De=Object.create;var le=Object.defineProperty;var Fe=Object.getOwnPropertyDescriptor;var Ne=Object.getOwnPropertyNames;var Ve=Object.getPrototypeOf,Be=Object.prototype.hasOwnProperty;var qe=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var je=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Ne(e))!Be.call(t,i)&&i!==n&&le(t,i,{get:()=>e[i],enumerable:!(r=Fe(e,i))||r.enumerable});return t};var $e=(t,e,n)=>(n=t!=null?De(Ve(t)):{},je(e||!t||!t.__esModule?le(n,"default",{value:t,enumerable:!0}):n,t));var pe=qe((de,he)=>{(function(){var t=function(e){var n=new t.Builder;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),n.searchPipeline.add(t.stemmer),e.call(n,n),n.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(n){e.console&&console.warn&&console.warn(n)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var n=Object.create(null),r=Object.keys(e),i=0;i0){var d=t.utils.clone(n)||{};d.position=[a,c],d.index=s.length,s.push(new t.Token(r.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. +`,e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(r){var i=t.Pipeline.registeredFunctions[r];if(i)n.add(i);else throw new Error("Cannot load unregistered function: "+r)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(n){t.Pipeline.warnIfFunctionNotRegistered(n),this._stack.push(n)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");r=r+1,this._stack.splice(r,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");this._stack.splice(r,0,n)},t.Pipeline.prototype.remove=function(e){var n=this._stack.indexOf(e);n!=-1&&this._stack.splice(n,1)},t.Pipeline.prototype.run=function(e){for(var n=this._stack.length,r=0;r1&&(oe&&(r=s),o!=e);)i=r-n,s=n+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ol?d+=2:a==l&&(n+=r[c+1]*i[d+1],c+=2,d+=2);return n},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),n=1,r=0;n0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}if(s.str.length==0&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}s.str.length==1&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),m=s.str.charAt(1),p;m in s.node.edges?p=s.node.edges[m]:(p=new t.TokenSet,s.node.edges[m]=p),s.str.length==1&&(p.final=!0),i.push({node:p,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return r},t.TokenSet.fromString=function(e){for(var n=new t.TokenSet,r=n,i=0,s=e.length;i=e;n--){var r=this.uncheckedNodes[n],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(n){var r=new t.QueryParser(e,n);r.parse()})},t.Index.prototype.query=function(e){for(var n=new t.Query(this.fields),r=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),l=0;l1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,n){var r=e[this._ref],i=Object.keys(this._fields);this._documents[r]=n||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,n;do e=this.next(),n=e.charCodeAt(0);while(n>47&&n<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var n=e.next();if(n==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(n.charCodeAt(0)==92){e.escapeCharacter();continue}if(n==":")return t.QueryLexer.lexField;if(n=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(n=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(n=="+"&&e.width()===1||n=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(n.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,n){this.lexer=new t.QueryLexer(e),this.query=n,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var n=e.peekLexeme();if(n!=null)switch(n.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+n.type;throw n.str.length>=1&&(r+=" with value '"+n.str+"'"),new t.QueryParseError(r,n.start,n.end)}},t.QueryParser.parsePresence=function(e){var n=e.consumeLexeme();if(n!=null){switch(n.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator'"+n.str+"'";throw new t.QueryParseError(r,n.start,n.end)}var i=e.peekLexeme();if(i==null){var r="expecting term or field, found nothing";throw new t.QueryParseError(r,n.start,n.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(r,i.start,i.end)}}},t.QueryParser.parseField=function(e){var n=e.consumeLexeme();if(n!=null){if(e.query.allFields.indexOf(n.str)==-1){var r=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+n.str+"', possible fields: "+r;throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.fields=[n.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,n.start,n.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var n=e.consumeLexeme();if(n!=null){e.currentClause.term=n.str.toLowerCase(),n.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(r==null){e.nextClause();return}switch(r.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+r.type+"'";throw new t.QueryParseError(i,r.start,r.end)}}},t.QueryParser.parseEditDistance=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="edit distance must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.editDistance=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="boost must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.boost=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,n){typeof define=="function"&&define.amd?define(n):typeof de=="object"?he.exports=n():e.lunr=n()}(this,function(){return t})})()});window.translations||={copy:"Copy",copied:"Copied!",normally_hidden:"This member is normally hidden due to your filter settings.",hierarchy_expand:"Expand",hierarchy_collapse:"Collapse",folder:"Folder",kind_1:"Project",kind_2:"Module",kind_4:"Namespace",kind_8:"Enumeration",kind_16:"Enumeration Member",kind_32:"Variable",kind_64:"Function",kind_128:"Class",kind_256:"Interface",kind_512:"Constructor",kind_1024:"Property",kind_2048:"Method",kind_4096:"Call Signature",kind_8192:"Index Signature",kind_16384:"Constructor Signature",kind_32768:"Parameter",kind_65536:"Type Literal",kind_131072:"Type Parameter",kind_262144:"Accessor",kind_524288:"Get Signature",kind_1048576:"Set Signature",kind_2097152:"Type Alias",kind_4194304:"Reference",kind_8388608:"Document"};var ce=[];function G(t,e){ce.push({selector:e,constructor:t})}var J=class{alwaysVisibleMember=null;constructor(){this.createComponents(document.body),this.ensureFocusedElementVisible(),this.listenForCodeCopies(),window.addEventListener("hashchange",()=>this.ensureFocusedElementVisible()),document.body.style.display||(this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}createComponents(e){ce.forEach(n=>{e.querySelectorAll(n.selector).forEach(r=>{r.dataset.hasInstance||(new n.constructor({el:r,app:this}),r.dataset.hasInstance=String(!0))})})}filterChanged(){this.ensureFocusedElementVisible()}showPage(){document.body.style.display&&(document.body.style.removeProperty("display"),this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}scrollToHash(){if(location.hash){let e=document.getElementById(location.hash.substring(1));if(!e)return;e.scrollIntoView({behavior:"instant",block:"start"})}}ensureActivePageVisible(){let e=document.querySelector(".tsd-navigation .current"),n=e?.parentElement;for(;n&&!n.classList.contains(".tsd-navigation");)n instanceof HTMLDetailsElement&&(n.open=!0),n=n.parentElement;if(e&&!ze(e)){let r=e.getBoundingClientRect().top-document.documentElement.clientHeight/4;document.querySelector(".site-menu").scrollTop=r,document.querySelector(".col-sidebar").scrollTop=r}}updateIndexVisibility(){let e=document.querySelector(".tsd-index-content"),n=e?.open;e&&(e.open=!0),document.querySelectorAll(".tsd-index-section").forEach(r=>{r.style.display="block";let i=Array.from(r.querySelectorAll(".tsd-index-link")).every(s=>s.offsetParent==null);r.style.display=i?"none":"block"}),e&&(e.open=n)}ensureFocusedElementVisible(){if(this.alwaysVisibleMember&&(this.alwaysVisibleMember.classList.remove("always-visible"),this.alwaysVisibleMember.firstElementChild.remove(),this.alwaysVisibleMember=null),!location.hash)return;let e=document.getElementById(location.hash.substring(1));if(!e)return;let n=e.parentElement;for(;n&&n.tagName!=="SECTION";)n=n.parentElement;if(!n)return;let r=n.offsetParent==null,i=n;for(;i!==document.body;)i instanceof HTMLDetailsElement&&(i.open=!0),i=i.parentElement;if(n.offsetParent==null){this.alwaysVisibleMember=n,n.classList.add("always-visible");let s=document.createElement("p");s.classList.add("warning"),s.textContent=window.translations.normally_hidden,n.prepend(s)}r&&e.scrollIntoView()}listenForCodeCopies(){document.querySelectorAll("pre > button").forEach(e=>{let n;e.addEventListener("click",()=>{e.previousElementSibling instanceof HTMLElement&&navigator.clipboard.writeText(e.previousElementSibling.innerText.trim()),e.textContent=window.translations.copied,e.classList.add("visible"),clearTimeout(n),n=setTimeout(()=>{e.classList.remove("visible"),n=setTimeout(()=>{e.textContent=window.translations.copy},100)},1e3)})})}};function ze(t){let e=t.getBoundingClientRect(),n=Math.max(document.documentElement.clientHeight,window.innerHeight);return!(e.bottom<0||e.top-n>=0)}var ue=(t,e=100)=>{let n;return()=>{clearTimeout(n),n=setTimeout(()=>t(),e)}};var ge=$e(pe(),1);async function A(t){let e=Uint8Array.from(atob(t),s=>s.charCodeAt(0)),r=new Blob([e]).stream().pipeThrough(new DecompressionStream("deflate")),i=await new Response(r).text();return JSON.parse(i)}async function fe(t,e){if(!window.searchData)return;let n=await A(window.searchData);t.data=n,t.index=ge.Index.load(n.index),e.classList.remove("loading"),e.classList.add("ready")}function ve(){let t=document.getElementById("tsd-search");if(!t)return;let e={base:document.documentElement.dataset.base+"/"},n=document.getElementById("tsd-search-script");t.classList.add("loading"),n&&(n.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),n.addEventListener("load",()=>{fe(e,t)}),fe(e,t));let r=document.querySelector("#tsd-search input"),i=document.querySelector("#tsd-search .results");if(!r||!i)throw new Error("The input field or the result list wrapper was not found");i.addEventListener("mouseup",()=>{re(t)}),r.addEventListener("focus",()=>t.classList.add("has-focus")),We(t,i,r,e)}function We(t,e,n,r){n.addEventListener("input",ue(()=>{Ue(t,e,n,r)},200)),n.addEventListener("keydown",i=>{i.key=="Enter"?Je(e,t):i.key=="ArrowUp"?(me(e,n,-1),i.preventDefault()):i.key==="ArrowDown"&&(me(e,n,1),i.preventDefault())}),document.body.addEventListener("keypress",i=>{i.altKey||i.ctrlKey||i.metaKey||!n.matches(":focus")&&i.key==="/"&&(i.preventDefault(),n.focus())}),document.body.addEventListener("keyup",i=>{t.classList.contains("has-focus")&&(i.key==="Escape"||!e.matches(":focus-within")&&!n.matches(":focus"))&&(n.blur(),re(t))})}function re(t){t.classList.remove("has-focus")}function Ue(t,e,n,r){if(!r.index||!r.data)return;e.textContent="";let i=n.value.trim(),s;if(i){let o=i.split(" ").map(a=>a.length?`*${a}*`:"").join(" ");s=r.index.search(o)}else s=[];for(let o=0;oa.score-o.score);for(let o=0,a=Math.min(10,s.length);o`,d=ye(l.name,i);globalThis.DEBUG_SEARCH_WEIGHTS&&(d+=` (score: ${s[o].score.toFixed(2)})`),l.parent&&(d=` + ${ye(l.parent,i)}.${d}`);let m=document.createElement("li");m.classList.value=l.classes??"";let p=document.createElement("a");p.href=r.base+l.url,p.innerHTML=c+d,m.append(p),p.addEventListener("focus",()=>{e.querySelector(".current")?.classList.remove("current"),m.classList.add("current")}),e.appendChild(m)}}function me(t,e,n){let r=t.querySelector(".current");if(!r)r=t.querySelector(n==1?"li:first-child":"li:last-child"),r&&r.classList.add("current");else{let i=r;if(n===1)do i=i.nextElementSibling??void 0;while(i instanceof HTMLElement&&i.offsetParent==null);else do i=i.previousElementSibling??void 0;while(i instanceof HTMLElement&&i.offsetParent==null);i?(r.classList.remove("current"),i.classList.add("current")):n===-1&&(r.classList.remove("current"),e.focus())}}function Je(t,e){let n=t.querySelector(".current");if(n||(n=t.querySelector("li:first-child")),n){let r=n.querySelector("a");r&&(window.location.href=r.href),re(e)}}function ye(t,e){if(e==="")return t;let n=t.toLocaleLowerCase(),r=e.toLocaleLowerCase(),i=[],s=0,o=n.indexOf(r);for(;o!=-1;)i.push(ne(t.substring(s,o)),`${ne(t.substring(o,o+r.length))}`),s=o+r.length,o=n.indexOf(r,s);return i.push(ne(t.substring(s))),i.join("")}var Ge={"&":"&","<":"<",">":">","'":"'",'"':"""};function ne(t){return t.replace(/[&<>"'"]/g,e=>Ge[e])}var I=class{el;app;constructor(e){this.el=e.el,this.app=e.app}};var H="mousedown",Ee="mousemove",B="mouseup",X={x:0,y:0},xe=!1,ie=!1,Xe=!1,D=!1,be=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(be?"is-mobile":"not-mobile");be&&"ontouchstart"in document.documentElement&&(Xe=!0,H="touchstart",Ee="touchmove",B="touchend");document.addEventListener(H,t=>{ie=!0,D=!1;let e=H=="touchstart"?t.targetTouches[0]:t;X.y=e.pageY||0,X.x=e.pageX||0});document.addEventListener(Ee,t=>{if(ie&&!D){let e=H=="touchstart"?t.targetTouches[0]:t,n=X.x-(e.pageX||0),r=X.y-(e.pageY||0);D=Math.sqrt(n*n+r*r)>10}});document.addEventListener(B,()=>{ie=!1});document.addEventListener("click",t=>{xe&&(t.preventDefault(),t.stopImmediatePropagation(),xe=!1)});var Y=class extends I{active;className;constructor(e){super(e),this.className=this.el.dataset.toggle||"",this.el.addEventListener(B,n=>this.onPointerUp(n)),this.el.addEventListener("click",n=>n.preventDefault()),document.addEventListener(H,n=>this.onDocumentPointerDown(n)),document.addEventListener(B,n=>this.onDocumentPointerUp(n))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let n=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(n),setTimeout(()=>document.documentElement.classList.remove(n),500)}onPointerUp(e){D||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-sidebar, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!D&&this.active&&e.target.closest(".col-sidebar")){let n=e.target.closest("a");if(n){let r=window.location.href;r.indexOf("#")!=-1&&(r=r.substring(0,r.indexOf("#"))),n.href.substring(0,r.length)==r&&setTimeout(()=>this.setActive(!1),250)}}}};var se;try{se=localStorage}catch{se={getItem(){return null},setItem(){}}}var C=se;var Le=document.head.appendChild(document.createElement("style"));Le.dataset.for="filters";var Z=class extends I{key;value;constructor(e){super(e),this.key=`filter-${this.el.name}`,this.value=this.el.checked,this.el.addEventListener("change",()=>{this.setLocalStorage(this.el.checked)}),this.setLocalStorage(this.fromLocalStorage()),Le.innerHTML+=`html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } +`,this.app.updateIndexVisibility()}fromLocalStorage(){let e=C.getItem(this.key);return e?e==="true":this.el.checked}setLocalStorage(e){C.setItem(this.key,e.toString()),this.value=e,this.handleValueChange()}handleValueChange(){this.el.checked=this.value,document.documentElement.classList.toggle(this.key,this.value),this.app.filterChanged(),this.app.updateIndexVisibility()}};var oe=new Map,ae=class{open;accordions=[];key;constructor(e,n){this.key=e,this.open=n}add(e){this.accordions.push(e),e.open=this.open,e.addEventListener("toggle",()=>{this.toggle(e.open)})}toggle(e){for(let n of this.accordions)n.open=e;C.setItem(this.key,e.toString())}},K=class extends I{constructor(e){super(e);let n=this.el.querySelector("summary"),r=n.querySelector("a");r&&r.addEventListener("click",()=>{location.assign(r.href)});let i=`tsd-accordion-${n.dataset.key??n.textContent.trim().replace(/\s+/g,"-").toLowerCase()}`,s;if(oe.has(i))s=oe.get(i);else{let o=C.getItem(i),a=o?o==="true":this.el.open;s=new ae(i,a),oe.set(i,s)}s.add(this.el)}};function Se(t){let e=C.getItem("tsd-theme")||"os";t.value=e,we(e),t.addEventListener("change",()=>{C.setItem("tsd-theme",t.value),we(t.value)})}function we(t){document.documentElement.dataset.theme=t}var ee;function Ce(){let t=document.getElementById("tsd-nav-script");t&&(t.addEventListener("load",Te),Te())}async function Te(){let t=document.getElementById("tsd-nav-container");if(!t||!window.navigationData)return;let e=await A(window.navigationData);ee=document.documentElement.dataset.base,ee.endsWith("/")||(ee+="/"),t.innerHTML="";for(let n of e)Ie(n,t,[]);window.app.createComponents(t),window.app.showPage(),window.app.ensureActivePageVisible()}function Ie(t,e,n){let r=e.appendChild(document.createElement("li"));if(t.children){let i=[...n,t.text],s=r.appendChild(document.createElement("details"));s.className=t.class?`${t.class} tsd-accordion`:"tsd-accordion";let o=s.appendChild(document.createElement("summary"));o.className="tsd-accordion-summary",o.dataset.key=i.join("$"),o.innerHTML='',ke(t,o);let a=s.appendChild(document.createElement("div"));a.className="tsd-accordion-details";let l=a.appendChild(document.createElement("ul"));l.className="tsd-nested-navigation";for(let c of t.children)Ie(c,l,i)}else ke(t,r,t.class)}function ke(t,e,n){if(t.path){let r=e.appendChild(document.createElement("a"));if(r.href=ee+t.path,n&&(r.className=n),location.pathname===r.pathname&&!r.href.includes("#")&&r.classList.add("current"),t.kind){let i=window.translations[`kind_${t.kind}`].replaceAll('"',""");r.innerHTML=``}r.appendChild(document.createElement("span")).textContent=t.text}else{let r=e.appendChild(document.createElement("span")),i=window.translations.folder.replaceAll('"',""");r.innerHTML=``,r.appendChild(document.createElement("span")).textContent=t.text}}var te=document.documentElement.dataset.base;te.endsWith("/")||(te+="/");function Pe(){document.querySelector(".tsd-full-hierarchy")?Ye():document.querySelector(".tsd-hierarchy")&&Ze()}function Ye(){document.addEventListener("click",r=>{let i=r.target;for(;i.parentElement&&i.parentElement.tagName!="LI";)i=i.parentElement;i.dataset.dropdown&&(i.dataset.dropdown=String(i.dataset.dropdown!=="true"))});let t=new Map,e=new Set;for(let r of document.querySelectorAll(".tsd-full-hierarchy [data-refl]")){let i=r.querySelector("ul");t.has(r.dataset.refl)?e.add(r.dataset.refl):i&&t.set(r.dataset.refl,i)}for(let r of e)n(r);function n(r){let i=t.get(r).cloneNode(!0);i.querySelectorAll("[id]").forEach(s=>{s.removeAttribute("id")}),i.querySelectorAll("[data-dropdown]").forEach(s=>{s.dataset.dropdown="false"});for(let s of document.querySelectorAll(`[data-refl="${r}"]`)){let o=tt(),a=s.querySelector("ul");s.insertBefore(o,a),o.dataset.dropdown=String(!!a),a||s.appendChild(i.cloneNode(!0))}}}function Ze(){let t=document.getElementById("tsd-hierarchy-script");t&&(t.addEventListener("load",Qe),Qe())}async function Qe(){let t=document.querySelector(".tsd-panel.tsd-hierarchy:has(h4 a)");if(!t||!window.hierarchyData)return;let e=+t.dataset.refl,n=await A(window.hierarchyData),r=t.querySelector("ul"),i=document.createElement("ul");if(i.classList.add("tsd-hierarchy"),Ke(i,n,e),r.querySelectorAll("li").length==i.querySelectorAll("li").length)return;let s=document.createElement("span");s.classList.add("tsd-hierarchy-toggle"),s.textContent=window.translations.hierarchy_expand,t.querySelector("h4 a")?.insertAdjacentElement("afterend",s),s.insertAdjacentText("beforebegin",", "),s.addEventListener("click",()=>{s.textContent===window.translations.hierarchy_expand?(r.insertAdjacentElement("afterend",i),r.remove(),s.textContent=window.translations.hierarchy_collapse):(i.insertAdjacentElement("afterend",r),i.remove(),s.textContent=window.translations.hierarchy_expand)})}function Ke(t,e,n){let r=e.roots.filter(i=>et(e,i,n));for(let i of r)t.appendChild(_e(e,i,n))}function _e(t,e,n,r=new Set){if(r.has(e))return;r.add(e);let i=t.reflections[e],s=document.createElement("li");if(s.classList.add("tsd-hierarchy-item"),e===n){let o=s.appendChild(document.createElement("span"));o.textContent=i.name,o.classList.add("tsd-hierarchy-target")}else{for(let a of i.uniqueNameParents||[]){let l=t.reflections[a],c=s.appendChild(document.createElement("a"));c.textContent=l.name,c.href=te+l.url,c.className=l.class+" tsd-signature-type",s.append(document.createTextNode("."))}let o=s.appendChild(document.createElement("a"));o.textContent=t.reflections[e].name,o.href=te+i.url,o.className=i.class+" tsd-signature-type"}if(i.children){let o=s.appendChild(document.createElement("ul"));o.classList.add("tsd-hierarchy");for(let a of i.children){let l=_e(t,a,n,r);l&&o.appendChild(l)}}return r.delete(e),s}function et(t,e,n){if(e===n)return!0;let r=new Set,i=[t.reflections[e]];for(;i.length;){let s=i.pop();if(!r.has(s)){r.add(s);for(let o of s.children||[]){if(o===n)return!0;i.push(t.reflections[o])}}}return!1}function tt(){let t=document.createElementNS("http://www.w3.org/2000/svg","svg");return t.setAttribute("width","20"),t.setAttribute("height","20"),t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("fill","none"),t.innerHTML='',t}G(Y,"a[data-toggle]");G(K,".tsd-accordion");G(Z,".tsd-filter-item input[type=checkbox]");var Oe=document.getElementById("tsd-theme");Oe&&Se(Oe);var nt=new J;Object.defineProperty(window,"app",{value:nt});ve();Ce();Pe();})(); +/*! Bundled license information: + +lunr/lunr.js: + (** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 + * Copyright (C) 2020 Oliver Nightingale + * @license MIT + *) + (*! + * lunr.utils + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Set + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.tokenizer + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Pipeline + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Vector + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.stemmer + * Copyright (C) 2020 Oliver Nightingale + * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt + *) + (*! + * lunr.stopWordFilter + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.trimmer + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.TokenSet + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Index + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Builder + * Copyright (C) 2020 Oliver Nightingale + *) +*/ diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/navigation.js b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/navigation.js new file mode 100644 index 000000000..0759eaf20 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/navigation.js @@ -0,0 +1 @@ +window.navigationData = "eJylmVFv2zYQx7+LnpNtydJsC4oBSeykAeI5s1r4oSgKWqZjLhQpkJQTd9h3HyiSEiVRR9l5SuD73++OFI9HUV//TRR+U8lV8pEwhQVD9M/kJCmQ2iZXSc7XJcXy5+/O9v2nrcppcpK8ELZOrs5PkmxL6Fpgllx9rVFLwtb8tcFkFEnZxhhJm3Z2/vt/JzVkKgQX80IRzmSDqggblLVpvraT4YfLExM/uUqUXJ8SeYrfjF/iRXtQWKAVxbFITndclCcuiU4yFsXpelF8luA5kfiupBtCKV4vsCypipKDXkeOxrAeyUt02jzpu2It8D84UwcOtu30rvhLorYLLDndYRFdlSGf46Kn5Ed0irUGWi/Xr4govG4wal+0CVbRgfzyx29nH85HpXmLWIYp1fVhx25/wQIIC3gFUwEDTt9wVip+WDzndHg4s7QODOecjglnltGB4YxTLNwTEoogCrCt4vgFYjNKsVKBHasfLyA/PvgCozVndA8EdJL3BMm4gIrMCI4PkO6lwvl0h5n6C+UYiNRRhh//N3+HKApKMtRuUO4I4BnbpIvwGeATWXvJbUqWVd25xdGaNuzywsvn75KoGEJrAES69c8hYYTW9BD+vNwI/ir9onNzYg2j5mNeYPZl8RhKxmGsBMzlFlHaT0T/OiqLRckUyXF1XuqfzyqML4HOZ1rcO56ZRVhxPHts57nZP0xCE1NxtBF4wjf7dhn0vPtrv+XfntCOd39eu4+DkmLFkV/x9TNxplEPJsXqs/4vlEkNsiJgNHFGEOAPaUIQ5c+yPyBrGDWcm1KpgWOuwxgJdGK5IxTfEar80guAGhkEm2Ep0TM2TtBrhcOGHKAAunp1KuMjBD2gECna4cNCBD2gEJ2doVlBjhjYF1or8IFtOOSv7YC7mxMI4TRg68Cy3cf6GKeB2oedPgjjNABmiQQj7BmiWAlYmVUrDxSm+X1UXS4RobLS93d/i2kk4O5fSsXzbkbeCrQ0Xwetuyaq3nNXKHvpNpVeek4Y6y6NxwQpFMVq0Xhk6AzWQw4fv7ydW7s2pB0SRJ/ha1plb0N+9f2nefiwZN21GSq7zQZwnm82sO91uIc27teBPuoRgnXqvKECnbMsWJy1bwbV5ZzNSqpIES7wmuFEYHHeURRqmtXPo0rzHistDmViIFYAZvEwT/s5PMzTURlM8I740+n7G9soTLsDeFuCx+l3gfZ2MNRFOojTM3A2PqFCkSzwVDTGGscNKS9QplK1p71i91GeLFbuRjo0xDYwMEZ/lGkmMGaBUVrDuBcDnIWv0xxEC8CzSaUDEeYvCBm656oRkUuue6wG9iJHMApgU7jH6rYUotUigxirglFPguRI7CMoqwIXs3mdDzzl6vdRD3nKdkRwlmOmBovU4jpS8OCbxmBGATFuUYFWhBJF/CbozZUB+TJg3r3kAZinAliE7fhLsD9YjBFAJ2J5PZtcXgAIq4AZixlMWMxi/rEctAJkTJB4mfHwvU6NcSKYhFdlsNU1GK0AGY+ElW8go1KAjBnKQMIMZaC/+YwFLdlaAxb3cvbYr+zl7HFkWbe/XDU5aIKxAqNYYMrResjdWCN3UvrKefBOqTbCt0nda+shlK8ZQdT3xvq6nXAGZtgTHpbt4EewsBTeC7se8a9OER8onL3v+ywQkwUX4VNAVwQBM0p01/BWVPMy42zAiwxf6aegX5mCr0OeGYB0v0A3/qEPzy3XZ6wCc9EUhW8HqkpXjblvQTTNBCmCsL4KQMpIZhLK7Nv/fHHiaA==" \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/search.js b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/search.js new file mode 100644 index 000000000..29cc82e52 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/search.js @@ -0,0 +1 @@ +window.searchData = "eJy1XW2T2zaS/i/y11mvGnx3XV2VYye7roovWU+yvjtXaosjYWYYU6SOpGbsTeW/XwEgpQbQJJuS/MmuEdDdAB40Gv0AxB+rpn5uV68+/bH6XFTb1as4vFlV+U6uXq3KOt/+tO+KusrL201T7LvVzerQlKtXq/tDtVE/tH/1C7187Hbl6ma1KfO2le3q1Wr1580g/iT99X5fFptc1TyK3dXbQynbv6LfJqUhY/9ebCVhHpakihDiblb7vJFV59hEarl9rJ9ntKgiF2r5x6GguhprUUXO0XJS8p0aeNl4fd//ndvvP+1l9euHHwlzB0F9iWlrB2soS9/kZemZqf7ItdEScDJQixiRg0zTtUm53339L/WfEcnm1/Nlv3s7Lvnd20VyxTpLIBJWh5hJ2x51dF/3EvVJ//MiNSDSo4oPh6ordvL7pqlPGOulGC24xCI1EZxasqmrtmsOm46t5YVdZVyjVZUGZlns7+q82froHH7hQvRWdr/IL9SsP4nqy8z01dEmUs+skvM0nBS8LfKyfmi9Hun/zu2Pd9V9Tdg5SFE/T1s52EFK/5g3VVE9TCjoS1ygw0a+r4GB+0n5/zjI1lo8fRVDkQu0KOf9Q1FSTm7QMhS5QMtt/iRntAxFlmoRUew1xpRyHWBRdbK5zzfSb5pVYakFsBZ4KarePNZ1K98WjVQuqJBnWvBik1cbLWpriZo0i5Y3a6yqcbGZ972QKxvYyLy7Um9qUd+sN1Vg+PdiuzVVzrWzfayfH7WUb9CfH2Rbl0+yfV0WeXu2iU0vJT9KuaKJr8uyfm7fH8qu2JfyVpZyY3nBZabmWtqul9YiaVc0We07vv/Syao939DHYislknHdSWS60YDzUks3eWX60aD0Gxn9i5qrrSr+c775nD/I9nV7uRPotFQ1sfa91Lz9Zv7AIPmn7lE2qo6Ovy+Cca1E3SNRVzT2h6LsZHOufffH2teEQNGhiGHhQPd1r2jOe9m2+cO5Bu2Ota9o0neHrqsrK+RfZtWdFtAZAVc0bJipX8+0a4vqX9Ms2eWbR7k916pT9cuMwjHrEPeyY1aywkUxq5JW4UTHIq1q8g/Vp7uFbuqVAukJA88KpM8zdjby45jJC/wWG7gkkJ4xc3EgvcjYBYH0hJ1LA+lFJi4IpCdMXBpILzKRHZVOGLgsKl2KyMVR6TQqz4tKFxl9QVQ6YfulUemiJiyNSifsPisqXWQsIyqdXphYUekyCMxFpVMDzYpKF5nDiEonDOJGpYtM4kWlE1YtiEoXGcaKSifs4kely8xiRKVTVnGj0jmjcFTaw4odlFLlL4pJZ6fZqEbmLCNbeP4kGzeHO8eWGGSm2Hkj0k+vebe4xCAOhsctYkN4xiSMYNNFU/aYEheh9Mf8TpZMHS/KvvB0E3u7RxS+a9/k1Yavs2g3Q/nL1L6V9/mhnPTott7tscJCxXgQlbsyMcCU4lOpiwbzbdHuy9ym/Oe1vdiaaqwdMGrPiBE/510nm0ncugbsj1XOUI74zCdZdT6va/7MPi1SDZl7gurrRZ3KTA9Xb9CInin5l8jdTFq+ucTm+/sJ0ff3F0l+TZ6AOQl/PXcKZkr+9zvyvFIvXf26UDae428ObVfvHPQh2PdacLGl2pzTMR/zomx1KWuumwMyvQa7zNUUvs27fE6hKnM1heqgy12++TyndCi3UDE+EXQS5p3U8dQtVMM9EUTrYZ0I8qqOeWgrUzqjeHJRYGvcYtDMaOzLXqixldVWsrv3WHq51uA0rr9YmYinvCnyO7QA6Z8XwgYlEsqcOLOk/8pd2v4mO1We8INGTP/7tInGDsrC200jZeXb2P99gZX0UjDIMQWmrRxsGdPwc1PscrSNJrX0hS7T9ObQNNihkJr6Qks1WSxM8W8y6Bu0qN+Xyrdm1Mdi2z2yNLx47otO6tHlR1O/snh4JGN1X9vjUHaROtx3H+RmUpf6/aK++2+W9BdfZhuhLR1R8j88JeO5H46SWRScFPFQMKVsHgUnbUwUOOqsGaSLTGJO/3sREtCR6WkFL4rxZIJdfEzV2DaQUja5zPPU3W7yUv6QW1HNjNZWVbnPJ6ManvLJCYZVzk+xaUWTkwwrmp9mM905589xP5qyl6j7rj5UW3L3Qim8G0pfovLnx69tscnLZar3fa2rmPCxbj6/bmTOVf5cN59zU/4aLV+qfmj7lcx417ph0JxLavfHCpco/lB39q2qGb3NqfxCtWg+fW07ufODU/1nbmxaVE/1Zyq90osxv88sEMYQ+k5B+zZvPr+vyYtivY5TmfP1vMn3+V1RFh2mfD1NuNT5ur6vnoqmrnYjQbBRhQpd0nsfi2qrLgtOdF5f5BItPxbV4cuUDl3gEg3v882U/Pf55hLpr9+/jcMp+brARRo+vJ+U/+H9hdJn7FcFLpqF8u5A7ZNPU1AVWKjBuudxa10fwv7PqDAFFmqwV/Qmr7b4CtGkkhd3p+JT2vo6C8NbX91UdMtRNBrc+qqmY1uOsn/KZuxgD6Hv6Vh6mUqMD+QPZ4DilLwEMa+bDb21G9f0Ijd1JlvqNmaUgcWTjql/21e6hgE/3S7VXk8EoAtVnzHML+q2MLWuYcLPZd7d183uHEP2fd3LzEFuBA3FEKm9++l2MkxDaYN83xUbPxGpJPS/TU8TpZ5eGXb7fEOFMVi0KTSrYSg/QYIYSbfd19LleHx9utQ5SvEEfCoQeYi7zfx0bq/RN2WRXFXiLzBvfW8h7TPHgOvoOUPLHHkzo4zF3jgVx2bprt7SZydIxUPpi1S2et6OLbikXlNlcuFdpHxiAZ7QP7cQM00o2ttidyjzJSNetC2qs1Q94pTMdoUglcwPTI/48f2P3rT++J764MXIFk7pJKawkmF+nJ5VSj8p+YNUX2cZkWx+PFNyK7tfmrxq93VDOWz8M7cbHqZFPnBFWtSD+YKDLxchzC0zLXwdngjtDf6syJzEF31puqM9Q0m41ne/y40+cEARoejXyTYggZuyUIHClpA2/DQpCtP75uDZ2Pc+jr9NyuN/2cOWx/ymx8nAqSaUcvtB/m4uAk81xyt3xabRshc002/EiP/dN/WuaP0TC5N2nCqda4N1wseUVrj72Qj+sfhMLod0Sf6M7R5pCmpC7ou+zmRDXdNHHcbY2cwpA2ZOaM6bMNnVH4vusb+tRF+kmKky7R+mkLZQBxd0o20bMau/ZHW2Waf6Vzbr9xHenGlVX/2qRtXVZpjZZ1tmy7jYPN99o1pjXg0V4TsPvNrPyHsxtdYTZo5rvJVdh3ubobg9VrlYf/WVr7ga5634Gpt8wx60F33hS3U6XnlOqyn+F3EFzSrkqw/+ccgx1afyl2puSyn3bL1D6YtH1/Zps+O71IeN67Vd/LzipS59TPMzubrO6Ve1GlTrDCsWhps+yvmR5reaYlfTiy4D8DRPZDUWTG4ca87O7GVB5nh7O0QyzDe2m6AX+FrviyovS/5ScSp/juaj2v/QkUeVl//pZV/+Nfz0r5nl3c4Imxz62Ml/JNQpOJ1EQXaOHIy3s1BD9yF9o+moOTXWOP1N+r6XVvLiQY773ZOivsqYvp/rtrAOoswo3Z/KX6j5jaw64nz6iN7NUPpSrWVNhJxjSvvCF+p8W7RqnqjzbG+0y84LfE9mxoitqa1OuG2s2hdaZbKWZxolq29i0w/15sC24b4vfLHOZiOdLOys5mYjm6HKpfoPZdnaB2/n1OMaF2r/m+xuFyl/kN0Vdf9vXe8WaP63KX6hXudQ9YzamWPVfK344+dzOk3ZCzW+a/V8InamI2qL9v5Y4XLdi3FdtNdEtjok9qVQkcmC9u9Qlcv1F9Vi/ajKhfqH1nOV707lL9XcN4Kt+VT+Qs1WRDijdZKbZWtUn4l7K59+qeuSvXDVe3XZ7qnr61xowQdZ5l3xJJdGck1f73oR3bJF9Frr5wcVhljs7KziU4XLdXd1s0RzX/xCvbeyWzrcreyuN9K36gLic/61/an6pfZzVOMm5LpWXXX1eK5qiRXf5ZvPD426CPGmLusDezvRyu7uWHUzVL3cnh+afCdL2bJ9QSu7e1TnChYcl1DzVQ4T5rNnpbLnKMF8ZUYeJVxu3fv8i3WtZ96aXf5l8nLPIu1FtVR7UV1N+7mOupXd9X21tmeh49SGXM133spu4WBcbSTsD1PNK57+GtUSzUv2O+219jvWg0hzSk3ZSzUuGdirjOov9cNDKZfvOTpd75r7DmPJ0ujb2HG9GLzvj6XLUd8d11uRfq2Wj8mhuuZ4/FotHYtDdb1x+LVauhs6VNfbD9l31WcUT99WZ+tc4uKu49+UxndsdCmdxRVwpbT+RBDDE2qneOFFej/Ils8TKM1NX+HSHFZebUs53JPQH6Js+PH/o6493JRQn5vdNlfZB1T5/nXbFi27S9oq3+dDjUsZlKYgvpY0Rp/0hc/R6T4P5xJwA4c9TomN1zmLtpo36HdpEdosg0ydb2LQcORxkUXHSt/EpO+/yM1hYR8Ndc4xCJ0z9PYh6JQY0j8UO4dAxefSvizRM/GVCjRJji0YUUle6x9VOU52T6vkfIMI89KznyGa78vnsW/QUHr4C/vU94geR79EQ+rksyZG6W83q6Layi+rV3+shlsrr1biZfAyW92s7gtZbtULuKsha1vv+uv023pz0P/9rS/2T+0/VGFT+q/r1c2n9U0EL4Ms+e23m09DZf2D/sMg4/QXXRFWN5+AqgheRbAqitXNJ3ETxi9FmlkVhVdRWBWD1c2ngKoYeBUDq2K4uvkUUqaGXsXQqhitbj5FVMXIqxhZFePVzaeYqhh7FWOrYrK6+ZTchOJlJkKrYuJVTKyK6VjF1KuYWhWz1c2nlDI18ypmNgAUHjISAT52wAGPRg+JOyDwYwMIFCyAxp6PIbBBBAoaIG6C+GUS2D0FPo7ABhIoeEBAavaxBDaYQEEESByCjyewAQUKJkBiEXxMgQ0qUFABEo/g4wpsYIGCC5DQAh9bYIMLFGSAhBf4+AIbYEJhBkiECR9hwkaYUJgRJMKEjzDhuCjto0iECcJL2QgTCjNCkJV9hAkbYUJhRpAIEz7ChI0woTAjQtK3+ggTNsKEwoyIyMo+woSNMKEwI2Kyso8wYSNMKMyIhKzsI0zYCBMKMyIlK/sIEzbCAoUZQSIs8BEW2AgLFGaCNbkg+QgLbIQFCjMBkJV9hAXOQqhXQnINDYi10EZYoDAT0Ouoj7DARligMBOQCAt8hAU2wgKFmSCiPEngIyywERYozAQxWdlHWGAjLFCYCUiEBT7CAhthgcJMQCIs8BEW2AgLFWaCjDI79BEW2ggLFWZC0oeFPsJCG2GhwkxI+rDQR1hoIywMRj1J6CMsdMKtcNSThETEZSMsjEY9SegjLLQRFsajniT0ERbaCAuTUU8S+ggLbYSF6agzCH2EhTbCwmzUGYQ+wkIbYdF61BlEPsIiG2ERjDqDyEdYZCMsEqPOIPIRFtkIi4JRZxD5CItshEXhqDOIfIRFTlAfjTqDiIjrbYRF8agziHyERTbComTUGUQ+wiIbYZHCTEiGFZGPsMhGWJSN97aPsMhGWLwe7e3YR1hsIyzWPowMaGIfYbGNsFiMdljsIyy2ERYrzIRkvB37CItthMV6z0jG27GPsNhGWKwwE9L7Px9hsbN1VJgJE7IysXu0ERYrzIRkvB37CItthMUaYWQ0FPsIi22ExQozEblWxT7CYhthicJMRK5ViY+wxEZYojATkRMj8RGW2AhLFGYiEp6Jj7DERliiMBORCEt8hCU2whKFmYhEWOIjLLERlujMBImwxEdYYiMsUZiJSIQlPsISJ0GhMBORCEuIHIWNsERhJiIRlvgIS2yEJQozMYmwxEdYYiMsVZiJSYSlPsJSG2GpwkxMIiz1EZbaCEvFaMIh9RGW2ghLFWbi4CZcvwwB7Mo+wlIbYanCTEzCM/URltoISxVmYhKeqY+w1EZYqvNfJDxTH2GpjbBUYSYm4Zn6CEudNJjCTEzCMyUyYTbCUo0wEp6pj7DURlimMJOQ8Mx8hGU2wjKFmYSEZ+YjLLMRlinMJCQ8Mx9hmY2wTGEmIRGW+QjLbIRlCjNJSFb2EZbZCMsUZpKIrOwjLLMRlinMJCTCMh9hmY2wTGdZyZRW5iMssxGW6UxrSlb2EZY5ydZsvMOIfKubcF2P9pj5za6O/tbX1ygbSdkSade1k3ddK+ik5CbH/ObWd1Kv63FvZn5z6zvZ17UCUEonftdE/nXtJGDX0ei4m9/c+k4Odh2PDr35za3vpGHXyehcM7+59Z1M7FqBKSUnuvnNre8kY9c610+nr9dEOnbt4E8n8VM6g03l/L2kv8JTSiexybS/gz+dyk/pPDaV+XdT/zqbn5IrC1DJfzf7rxP6KZ3NpvL/LgGgc/opPf8oCsDlAHRaPxthTQj8uTSAzuxn9PyhiACXCdDJ/YzGH8UFuGSAzu9nNP4oOsDhA0Cn+DMafwQjAA4lADrLn9H4I0gBcFgB0In+jMYfwQuAQwyAzvVnNP4IagAcbgB0uj+j8UewA+DQA6Az/hmNP4IgAIchAJ30hzW9ABAkATgsAejEP6xHqDsCgQ5TADr5P7ICEVwBOGQB6Pz/yApE0AXg8AWgKQBY01OAoAzA4QwgMMQnPQcI2gAc3gA0FQBrehIQ1AE43AFoOgDW9Cwg6ANw+APQlACs6WlAUAjgcAigaYExGBE0Ajg8AgQGh/REIqgEcLgECAwO6ZlE0Ang8AmgKQJY01OJoBTA4RRA0wQwwoATtAI4vAJoqgBGWHCCWgCHWwBNFwDQUCboBXD4BdCUwQgxTDAM4FAMoFmDsfoEDh2WATRxMMLkEzwDOEQDaO4Axth8AoYO2QCaP4ARRp/gG8AhHEBzCDDC6hOcAzikA2geAUaYfYJ3AId4gNDAkEyGA8E9gEM+QGRgSM8kgn8Ah4CAyMCQnkkEBwEOCQGRya/Q3oTgIcAhIiAyJ0LomURwEeCQERAZINIzieAjwCEkQHMMQLP+QHAS4JASoHkGeiYRrAQ4tARopgHEyNEUAocONQGabQBBA5lgJ8ChJ0AzDiBoIBMMBTgUBWjWYcQZECQFOCwFaOIBBL2kEEQFOEwFxONpPiC4CnDICogNCklmCQi+AhzCAmKDQnoiEZwFOKQFaB4CaA4TCN4CHOICNBcBisckcEhwF+CQF6D5CAjoiUTwF+AQGBCnU51I4NAhMUDzEkATokDwGOAQGaC5CaBJUSC4DHDIDND8BAT0VCL4DHAIDdAcBQQjp70IJDqkBmieAgJ6KhC8BjjEBiTmlBy9JhDcBjjkBiQGiTSUCX4DHIIDNGcB9MkJIDgOcEgOSJIJKBM8BzhEB2juAujjF0BwHeCQHaD5C6D5XiD4DnAID0inkEhwHuCQHpDCxFwgeA9wiA8wzAfN/QLBfYBDfkA6cVaToD/A4T8gDSdGkaBAwOFAIDVHNumFkaBBwOFBII0nfCpBhYDDhYCmN4AmooGgQ8DhQyA1QKTdAUGJgMOJQGqASLsDghYBhxcBTXUATUoDQY2Aw41ABhNBKkGPgMOPQGaASPsTgiIBhyMBTXtARA8jQZOAw5OApj6AZriBoErA4UpA0x9As9xA0CXg8CWQmQPE9GQkKBNwOBPQNAjQbDcQtAk4vAloKgRoxhsI6gQc7gQ0HQI06w0EfQIOfyI0HwI08y0IAkU4BIrQhAjQ7LcgGBThMChCMyJAM+CCoFCEQ6EITYnQFJAgKBThUChCUyL07QuCQREOgyI0IwI0CS8ICkU4FIrQlAjQRLwgOBThcChibc6y08erCRJFOCSK0KQIxPQRa4JFEQ6LIjQrAjSvLggaRTg0itC0CNDcuiB4FOHwKELzInQWUxA8inB4FGEuUND8vCCIFOEQKcJcoqA5ekEwKcJhUoS5SEFv2gVBpQiHShHmMgVN9AuCSxEOlyLMhQqa7BcEmSIcMkWYSxU04S8INkU4bIroL1bQU4GgU4RDpwhzuYIm/gXBpwj3foW5YJHQU4G6YuHesdAECST0VKCuWXj3LEwOkZ4K5FULB4nCJBFpKFPXLdz7FpojgYSGMnXlwr1zoUkSSGgkUtcu3HsXhlWhaXlBXb1w714YViWlkUhdv3DvX2iWBGhiXVBXMNw7GJomITcKgrqE4bAqQpMkNC8uCFJFOKSKMKQKTYwLglQRDqkiDKlCM+OCIFWEQ6oIQ6rQ1LggSBXhkCrCkCo0Ny4IUkU4pIowpApNjguCVBEOqSIMqUKz44IgVYRDqgjNkdBnWwTBqQiHUxGGU6HpdUFwKsLhVIThVGh+XRCcinA4FWE4FZpgFwSnIhxORRhOhWbYBcGpCIdTEYZToSl2QZAqwiFVhCFVaI5dEKyKcFgVYVgVmmQXBK0iHFpFGFqFZtkFQasIh1YRhlahaXZB0CrCoVWEoVVonl0QtIpwaBVhaBWaaBcErSIcWkUYWoXeLwqCVhEOrSI0S0KfVBIEqyIcVkVokoQ+qSQIUkU4pIrQHMmIRyY4FeFwKkJTJCO+gKBUhEOpCM2QjGxzCEZl+Ju+J/8km05u35n78p8+mVdv/lj9q79BrzKbWou6TK9yl6/++PPP0535V3/8ia7Nq9+Uprws6+d2dyi7Yl/KVpbmTTAsV2QnscF6gdS6e5SN+t6L/rgFFhkESGRohETAFX18RQe3PcVtD3mi1Ls4WEaGZUQ8Gft9WWxyt88ASeLJ0c9JIwHrEIkIBEvInfqspdXRqUBS1ilPyvFBdGwObpFImILq51Z9deUkJzpJ4fWv+RYlFhGijol5IDdCrI4JEQJj3hAZKZ380llQRpMuSHsoM9v2tbBAjOHHm2d3X3UFPOAnGRlLxMbxIqg5ianSN0qRq1yB9V6/DGojCLWO1+HDWzN4imIYxnFvWdJbqtLcfMHSPCCJpIsYSY+4/Xf8Ts/xFTxsMBaZ8KaxL3Jz+oIRkp2hsVJL/Xmy5fFTRFh0ikXz/IYvutRvReIOxhCPlwwWFtscPzCFLY6xxVyo+qKHj2lh0REWzZvbvmjn5SiMEeyk40XjKLfN8IwnAWccDzCd3OmVJSQIu1zFBHEFPdZ1K7dFo0ersEMAgWSKyAgJee78KFrFFrZQPFK9dwj5MGtk3o3ai/HV+xs+Fkxg9Vhst7KSXzpZtU7AgB2bWe1vViHXA+3zu6IsOsfiDPtK4MKqc+KQMMJjz5TSv9qDxETYmIw5JPqdY3uNBDzE6rQNU9D+rs4bJ2REgrgG1Y57j/DUzZhwwO+34ZbhgK9fenskQNgHyep4T/+f4aeUOWkObVfvpHoXy0JJhDx9whvebd7lWESMpjJzBd7Ku4MTYOJxDXht2sou3zzaOwEcxId9Z0X9fI156+9Wv01vWWcNTshzpdsiL+sH29Vb0QBTinFH1lYF73aCrG8kb80be1cKgxrPjIwtdl/mXjgaotGIeZNV7gorwI5wRMozxny63h5AHHkwt5ojr13hnsILY8YbUFk9FU1d6b9hb40RBsyuOokqqvvank94EANmg70QAvWa4G1JCA+DXH/CE6IW9vuidBaREKGeGSYpQR4mkT0hzyMYW+wUBhr6oI9fIq60/pFFvMxiz5Dyhv++zG33EqOpwlyq+9fUMKaxI86YluCX0bAs3KqMF87gj4BjUXizs+bBSD+ViDsZO4GMN1gP6vUSe4eMdrIZ25DNoWmcKZ+gKc+3Ro26NehoVjCB8yC7fVPscntRSdC8yHgOA70Kh4cKbx7XbEldk1ftvm7sMRPYL0a86OL4ZBw2ynJlvN4e+3I1dpDIPMH0Jo/5vis2TnYEQzPgDePwsVU0hGjuZr1TgnUfBakjM+Y/zG40z9GhLRBq6iCSL4ne9qC5ZBapmxUz3WDvC1KcXloPsbFgitrt840NOytdFfA8lxHTdl/d2APvfUKex/DWc7yXHzJy4XF7wIuXh6/v2k7RksxsafVU26mdFOMXeBuhonbmAM7hB7z1q2jz3VYd+EeRFF50BG9KFm3eWP4C59bUdVW2EMcW3CTBmyxF6yc+cXgQM3u33ebN511tz+HMSsPywumi9bZrGR5uwTZI3ueH0ppoGH0x15zjc5V4Z7XG/p1r0UiwEQMWxjWrLKrDF6uX8PaAGT+rxyg3lhAcjwneamq9aInbhV34mjvD0OuUWFaAZTEdbUvEHSmer8Cdr22xO5S5m0OxNtjchaR91q8u2MkzvH0Cnlcs8ztn1qKhY2a7VQht2JNcAbPYW7MFYZyHptOLNnjkcOp1zWvbTrZt/mDJwdRG0GdYoj5PFTPNM1JNpoQgjUKEV2au7/SKDm4xzgszuTXlOe2V0s4Y8lyMu/nEsyYNhght+I/oczmCyZDVdyr5r+TZKylOrkPEm+f1/b21a0ddxuTT6/t7Z6uE939MQt2OD3Eqisme1dWRG7HTvdiPJsz+rew8IM73MA9Y1NVwuMIShIDNtcV6PhaDGzMTTP+phKn9jBXe40VvmZjRCSzwIsFE0V5W6rEJPGmQG+XJcAJLvCoETDNaP6WGY6eBXoKAh+t93qng2wIBkhfz3PD+8WtbbPKSOOmBnRPTqQ/Snuvmc95IK6mf4unCdEfDTtnvORxNB7wxPD16iXfxeAiyYT/LJMr6V5jw7h2vhEyikGL5BQ7z48GbMxPe/3eQrXfmCkezvM7/v4OdN0dN4zWsyR3SI8SdzUy9K7LCykigqcekEgyjbftuvJ4lAwWW8hZy/xVV7DxxWhF4Lp1IdOLxgmjIjjBpRfS6KrYMZ9GYO+r+fIHdd9gxxNnQdzxQ9QLbvCzy1iHEsX29WOYhlOOD2Li5OD/H3Jg2deedvEuxq+EO6KHqip306Q/r0NsSUSO5TDwUzENibf4kvZUab+N4TRzEjIfaCMFMgrPd5KW8z10iO8W9xkwQ+nvwxDoNxzVISbEpESSGmdxqVZxltQhvH5jHX9xHz3EmD8dZTKaPfLgcy8SIYG5X7TfIsTActDHTFlMPiGPReMFlLmz4NXAsCu/pmPEqftobi8IRNHdCyY5cUBJr5eb5Q/KpbywTL1LMMNp+tRsLw36bmbo7vsKN5WCHLXgOW53cds7PgrVVZUsxT3MjMZgAEMwDyu0o6YRdWMQGhEc6pdaWijnV9VPcKM2Csix9XCGYE/yxfjZHzvyDchhRPZ3APDTpAgF7i+yY0ejzQSIYyCcmN96WUtruEueA1LdAWFLQQ6843Ld2o8xh/dp20hrVFAf7zBDPSNFnI9yMEOBjDcAklI08T5Q1mbjjqUUNjy9a0vAWkBnaedMby+AZ1D062XhMEkI80JopU1qxk/oBZtzjeHonvGnpeRycTwr6ZEDU4595dtx7dh27DrxSMs/D+a/aY4FW0Mhbet3n6bE4PJeY91M6dcC2Vc5on28+5w+yzduR47b4THQw9Cpvbni3jXCCjIka+y163GwcdTBTSfhheSwKRx3MLBJ+JR6Lwu6cmV+hZvzaOoPK6+7nvChb7dmsPkeSmHvNkyB1hUSFu5ZANGeZG/+TQO/EKvIozCXlJMz1uvhkCdOZPOdN5d6wslYBnhTzwC9aipHPyHomJhsohYGiEcx4xpBi9oKA99bhkEpgnhUav/+A72JBynMlzzuHm8Hdx3QUZNoTr6BMpsjifHGsng33ktbR0Pc8ifa5LHynazj6sY4Hibze90NTvA1gHjhSQgrHH+JdAPOcjBLjLsn4XIJgnpFRchrZOof88LUHwclk/Haz2hd7WRaVXL369Nuff/4/xjIYCQ=="; \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/style.css b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/style.css new file mode 100644 index 000000000..2ab8b836e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/style.css @@ -0,0 +1,1611 @@ +@layer typedoc { + :root { + /* Light */ + --light-color-background: #f2f4f8; + --light-color-background-secondary: #eff0f1; + --light-color-warning-text: #222; + --light-color-background-warning: #e6e600; + --light-color-accent: #c5c7c9; + --light-color-active-menu-item: var(--light-color-accent); + --light-color-text: #222; + --light-color-text-aside: #6e6e6e; + + --light-color-icon-background: var(--light-color-background); + --light-color-icon-text: var(--light-color-text); + + --light-color-comment-tag-text: var(--light-color-text); + --light-color-comment-tag: var(--light-color-background); + + --light-color-link: #1f70c2; + --light-color-focus-outline: #3584e4; + + --light-color-ts-keyword: #056bd6; + --light-color-ts-project: #b111c9; + --light-color-ts-module: var(--light-color-ts-project); + --light-color-ts-namespace: var(--light-color-ts-project); + --light-color-ts-enum: #7e6f15; + --light-color-ts-enum-member: var(--light-color-ts-enum); + --light-color-ts-variable: #4760ec; + --light-color-ts-function: #572be7; + --light-color-ts-class: #1f70c2; + --light-color-ts-interface: #108024; + --light-color-ts-constructor: var(--light-color-ts-class); + --light-color-ts-property: #9f5f30; + --light-color-ts-method: #be3989; + --light-color-ts-reference: #ff4d82; + --light-color-ts-call-signature: var(--light-color-ts-method); + --light-color-ts-index-signature: var(--light-color-ts-property); + --light-color-ts-constructor-signature: var( + --light-color-ts-constructor + ); + --light-color-ts-parameter: var(--light-color-ts-variable); + /* type literal not included as links will never be generated to it */ + --light-color-ts-type-parameter: #a55c0e; + --light-color-ts-accessor: #c73c3c; + --light-color-ts-get-signature: var(--light-color-ts-accessor); + --light-color-ts-set-signature: var(--light-color-ts-accessor); + --light-color-ts-type-alias: #d51270; + /* reference not included as links will be colored with the kind that it points to */ + --light-color-document: #000000; + + --light-color-alert-note: #0969d9; + --light-color-alert-tip: #1a7f37; + --light-color-alert-important: #8250df; + --light-color-alert-warning: #9a6700; + --light-color-alert-caution: #cf222e; + + --light-external-icon: url("data:image/svg+xml;utf8,"); + --light-color-scheme: light; + + /* Dark */ + --dark-color-background: #2b2e33; + --dark-color-background-secondary: #1e2024; + --dark-color-background-warning: #bebe00; + --dark-color-warning-text: #222; + --dark-color-accent: #9096a2; + --dark-color-active-menu-item: #5d5d6a; + --dark-color-text: #f5f5f5; + --dark-color-text-aside: #dddddd; + + --dark-color-icon-background: var(--dark-color-background-secondary); + --dark-color-icon-text: var(--dark-color-text); + + --dark-color-comment-tag-text: var(--dark-color-text); + --dark-color-comment-tag: var(--dark-color-background); + + --dark-color-link: #00aff4; + --dark-color-focus-outline: #4c97f2; + + --dark-color-ts-keyword: #3399ff; + --dark-color-ts-project: #e358ff; + --dark-color-ts-module: var(--dark-color-ts-project); + --dark-color-ts-namespace: var(--dark-color-ts-project); + --dark-color-ts-enum: #f4d93e; + --dark-color-ts-enum-member: var(--dark-color-ts-enum); + --dark-color-ts-variable: #798dff; + --dark-color-ts-function: #a280ff; + --dark-color-ts-class: #8ac4ff; + --dark-color-ts-interface: #6cff87; + --dark-color-ts-constructor: var(--dark-color-ts-class); + --dark-color-ts-property: #ff984d; + --dark-color-ts-method: #ff4db8; + --dark-color-ts-reference: #ff4d82; + --dark-color-ts-call-signature: var(--dark-color-ts-method); + --dark-color-ts-index-signature: var(--dark-color-ts-property); + --dark-color-ts-constructor-signature: var(--dark-color-ts-constructor); + --dark-color-ts-parameter: var(--dark-color-ts-variable); + /* type literal not included as links will never be generated to it */ + --dark-color-ts-type-parameter: #e07d13; + --dark-color-ts-accessor: #ff6060; + --dark-color-ts-get-signature: var(--dark-color-ts-accessor); + --dark-color-ts-set-signature: var(--dark-color-ts-accessor); + --dark-color-ts-type-alias: #ff6492; + /* reference not included as links will be colored with the kind that it points to */ + --dark-color-document: #ffffff; + + --dark-color-alert-note: #0969d9; + --dark-color-alert-tip: #1a7f37; + --dark-color-alert-important: #8250df; + --dark-color-alert-warning: #9a6700; + --dark-color-alert-caution: #cf222e; + + --dark-external-icon: url("data:image/svg+xml;utf8,"); + --dark-color-scheme: dark; + } + + @media (prefers-color-scheme: light) { + :root { + --color-background: var(--light-color-background); + --color-background-secondary: var( + --light-color-background-secondary + ); + --color-background-warning: var(--light-color-background-warning); + --color-warning-text: var(--light-color-warning-text); + --color-accent: var(--light-color-accent); + --color-active-menu-item: var(--light-color-active-menu-item); + --color-text: var(--light-color-text); + --color-text-aside: var(--light-color-text-aside); + + --color-icon-background: var(--light-color-icon-background); + --color-icon-text: var(--light-color-icon-text); + + --color-comment-tag-text: var(--light-color-text); + --color-comment-tag: var(--light-color-background); + + --color-link: var(--light-color-link); + --color-focus-outline: var(--light-color-focus-outline); + + --color-ts-keyword: var(--light-color-ts-keyword); + --color-ts-project: var(--light-color-ts-project); + --color-ts-module: var(--light-color-ts-module); + --color-ts-namespace: var(--light-color-ts-namespace); + --color-ts-enum: var(--light-color-ts-enum); + --color-ts-enum-member: var(--light-color-ts-enum-member); + --color-ts-variable: var(--light-color-ts-variable); + --color-ts-function: var(--light-color-ts-function); + --color-ts-class: var(--light-color-ts-class); + --color-ts-interface: var(--light-color-ts-interface); + --color-ts-constructor: var(--light-color-ts-constructor); + --color-ts-property: var(--light-color-ts-property); + --color-ts-method: var(--light-color-ts-method); + --color-ts-reference: var(--light-color-ts-reference); + --color-ts-call-signature: var(--light-color-ts-call-signature); + --color-ts-index-signature: var(--light-color-ts-index-signature); + --color-ts-constructor-signature: var( + --light-color-ts-constructor-signature + ); + --color-ts-parameter: var(--light-color-ts-parameter); + --color-ts-type-parameter: var(--light-color-ts-type-parameter); + --color-ts-accessor: var(--light-color-ts-accessor); + --color-ts-get-signature: var(--light-color-ts-get-signature); + --color-ts-set-signature: var(--light-color-ts-set-signature); + --color-ts-type-alias: var(--light-color-ts-type-alias); + --color-document: var(--light-color-document); + + --color-alert-note: var(--light-color-alert-note); + --color-alert-tip: var(--light-color-alert-tip); + --color-alert-important: var(--light-color-alert-important); + --color-alert-warning: var(--light-color-alert-warning); + --color-alert-caution: var(--light-color-alert-caution); + + --external-icon: var(--light-external-icon); + --color-scheme: var(--light-color-scheme); + } + } + + @media (prefers-color-scheme: dark) { + :root { + --color-background: var(--dark-color-background); + --color-background-secondary: var( + --dark-color-background-secondary + ); + --color-background-warning: var(--dark-color-background-warning); + --color-warning-text: var(--dark-color-warning-text); + --color-accent: var(--dark-color-accent); + --color-active-menu-item: var(--dark-color-active-menu-item); + --color-text: var(--dark-color-text); + --color-text-aside: var(--dark-color-text-aside); + + --color-icon-background: var(--dark-color-icon-background); + --color-icon-text: var(--dark-color-icon-text); + + --color-comment-tag-text: var(--dark-color-text); + --color-comment-tag: var(--dark-color-background); + + --color-link: var(--dark-color-link); + --color-focus-outline: var(--dark-color-focus-outline); + + --color-ts-keyword: var(--dark-color-ts-keyword); + --color-ts-project: var(--dark-color-ts-project); + --color-ts-module: var(--dark-color-ts-module); + --color-ts-namespace: var(--dark-color-ts-namespace); + --color-ts-enum: var(--dark-color-ts-enum); + --color-ts-enum-member: var(--dark-color-ts-enum-member); + --color-ts-variable: var(--dark-color-ts-variable); + --color-ts-function: var(--dark-color-ts-function); + --color-ts-class: var(--dark-color-ts-class); + --color-ts-interface: var(--dark-color-ts-interface); + --color-ts-constructor: var(--dark-color-ts-constructor); + --color-ts-property: var(--dark-color-ts-property); + --color-ts-method: var(--dark-color-ts-method); + --color-ts-reference: var(--dark-color-ts-reference); + --color-ts-call-signature: var(--dark-color-ts-call-signature); + --color-ts-index-signature: var(--dark-color-ts-index-signature); + --color-ts-constructor-signature: var( + --dark-color-ts-constructor-signature + ); + --color-ts-parameter: var(--dark-color-ts-parameter); + --color-ts-type-parameter: var(--dark-color-ts-type-parameter); + --color-ts-accessor: var(--dark-color-ts-accessor); + --color-ts-get-signature: var(--dark-color-ts-get-signature); + --color-ts-set-signature: var(--dark-color-ts-set-signature); + --color-ts-type-alias: var(--dark-color-ts-type-alias); + --color-document: var(--dark-color-document); + + --color-alert-note: var(--dark-color-alert-note); + --color-alert-tip: var(--dark-color-alert-tip); + --color-alert-important: var(--dark-color-alert-important); + --color-alert-warning: var(--dark-color-alert-warning); + --color-alert-caution: var(--dark-color-alert-caution); + + --external-icon: var(--dark-external-icon); + --color-scheme: var(--dark-color-scheme); + } + } + + html { + color-scheme: var(--color-scheme); + } + + body { + margin: 0; + } + + :root[data-theme="light"] { + --color-background: var(--light-color-background); + --color-background-secondary: var(--light-color-background-secondary); + --color-background-warning: var(--light-color-background-warning); + --color-warning-text: var(--light-color-warning-text); + --color-icon-background: var(--light-color-icon-background); + --color-accent: var(--light-color-accent); + --color-active-menu-item: var(--light-color-active-menu-item); + --color-text: var(--light-color-text); + --color-text-aside: var(--light-color-text-aside); + --color-icon-text: var(--light-color-icon-text); + + --color-comment-tag-text: var(--light-color-text); + --color-comment-tag: var(--light-color-background); + + --color-link: var(--light-color-link); + --color-focus-outline: var(--light-color-focus-outline); + + --color-ts-keyword: var(--light-color-ts-keyword); + --color-ts-project: var(--light-color-ts-project); + --color-ts-module: var(--light-color-ts-module); + --color-ts-namespace: var(--light-color-ts-namespace); + --color-ts-enum: var(--light-color-ts-enum); + --color-ts-enum-member: var(--light-color-ts-enum-member); + --color-ts-variable: var(--light-color-ts-variable); + --color-ts-function: var(--light-color-ts-function); + --color-ts-class: var(--light-color-ts-class); + --color-ts-interface: var(--light-color-ts-interface); + --color-ts-constructor: var(--light-color-ts-constructor); + --color-ts-property: var(--light-color-ts-property); + --color-ts-method: var(--light-color-ts-method); + --color-ts-reference: var(--light-color-ts-reference); + --color-ts-call-signature: var(--light-color-ts-call-signature); + --color-ts-index-signature: var(--light-color-ts-index-signature); + --color-ts-constructor-signature: var( + --light-color-ts-constructor-signature + ); + --color-ts-parameter: var(--light-color-ts-parameter); + --color-ts-type-parameter: var(--light-color-ts-type-parameter); + --color-ts-accessor: var(--light-color-ts-accessor); + --color-ts-get-signature: var(--light-color-ts-get-signature); + --color-ts-set-signature: var(--light-color-ts-set-signature); + --color-ts-type-alias: var(--light-color-ts-type-alias); + --color-document: var(--light-color-document); + + --color-note: var(--light-color-note); + --color-tip: var(--light-color-tip); + --color-important: var(--light-color-important); + --color-warning: var(--light-color-warning); + --color-caution: var(--light-color-caution); + + --external-icon: var(--light-external-icon); + --color-scheme: var(--light-color-scheme); + } + + :root[data-theme="dark"] { + --color-background: var(--dark-color-background); + --color-background-secondary: var(--dark-color-background-secondary); + --color-background-warning: var(--dark-color-background-warning); + --color-warning-text: var(--dark-color-warning-text); + --color-icon-background: var(--dark-color-icon-background); + --color-accent: var(--dark-color-accent); + --color-active-menu-item: var(--dark-color-active-menu-item); + --color-text: var(--dark-color-text); + --color-text-aside: var(--dark-color-text-aside); + --color-icon-text: var(--dark-color-icon-text); + + --color-comment-tag-text: var(--dark-color-text); + --color-comment-tag: var(--dark-color-background); + + --color-link: var(--dark-color-link); + --color-focus-outline: var(--dark-color-focus-outline); + + --color-ts-keyword: var(--dark-color-ts-keyword); + --color-ts-project: var(--dark-color-ts-project); + --color-ts-module: var(--dark-color-ts-module); + --color-ts-namespace: var(--dark-color-ts-namespace); + --color-ts-enum: var(--dark-color-ts-enum); + --color-ts-enum-member: var(--dark-color-ts-enum-member); + --color-ts-variable: var(--dark-color-ts-variable); + --color-ts-function: var(--dark-color-ts-function); + --color-ts-class: var(--dark-color-ts-class); + --color-ts-interface: var(--dark-color-ts-interface); + --color-ts-constructor: var(--dark-color-ts-constructor); + --color-ts-property: var(--dark-color-ts-property); + --color-ts-method: var(--dark-color-ts-method); + --color-ts-reference: var(--dark-color-ts-reference); + --color-ts-call-signature: var(--dark-color-ts-call-signature); + --color-ts-index-signature: var(--dark-color-ts-index-signature); + --color-ts-constructor-signature: var( + --dark-color-ts-constructor-signature + ); + --color-ts-parameter: var(--dark-color-ts-parameter); + --color-ts-type-parameter: var(--dark-color-ts-type-parameter); + --color-ts-accessor: var(--dark-color-ts-accessor); + --color-ts-get-signature: var(--dark-color-ts-get-signature); + --color-ts-set-signature: var(--dark-color-ts-set-signature); + --color-ts-type-alias: var(--dark-color-ts-type-alias); + --color-document: var(--dark-color-document); + + --color-note: var(--dark-color-note); + --color-tip: var(--dark-color-tip); + --color-important: var(--dark-color-important); + --color-warning: var(--dark-color-warning); + --color-caution: var(--dark-color-caution); + + --external-icon: var(--dark-external-icon); + --color-scheme: var(--dark-color-scheme); + } + + *:focus-visible, + .tsd-accordion-summary:focus-visible svg { + outline: 2px solid var(--color-focus-outline); + } + + .always-visible, + .always-visible .tsd-signatures { + display: inherit !important; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + line-height: 1.2; + } + + h1 { + font-size: 1.875rem; + margin: 0.67rem 0; + } + + h2 { + font-size: 1.5rem; + margin: 0.83rem 0; + } + + h3 { + font-size: 1.25rem; + margin: 1rem 0; + } + + h4 { + font-size: 1.05rem; + margin: 1.33rem 0; + } + + h5 { + font-size: 1rem; + margin: 1.5rem 0; + } + + h6 { + font-size: 0.875rem; + margin: 2.33rem 0; + } + + dl, + menu, + ol, + ul { + margin: 1em 0; + } + + dd { + margin: 0 0 0 34px; + } + + .container { + max-width: 1700px; + padding: 0 2rem; + } + + /* Footer */ + footer { + border-top: 1px solid var(--color-accent); + padding-top: 1rem; + padding-bottom: 1rem; + max-height: 3.5rem; + } + footer > p { + margin: 0 1em; + } + + .container-main { + margin: 0 auto; + /* toolbar, footer, margin */ + min-height: calc(100vh - 41px - 56px - 4rem); + } + + @keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + @keyframes fade-out { + from { + opacity: 1; + visibility: visible; + } + to { + opacity: 0; + } + } + @keyframes fade-in-delayed { + 0% { + opacity: 0; + } + 33% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + @keyframes fade-out-delayed { + 0% { + opacity: 1; + visibility: visible; + } + 66% { + opacity: 0; + } + 100% { + opacity: 0; + } + } + @keyframes pop-in-from-right { + from { + transform: translate(100%, 0); + } + to { + transform: translate(0, 0); + } + } + @keyframes pop-out-to-right { + from { + transform: translate(0, 0); + visibility: visible; + } + to { + transform: translate(100%, 0); + } + } + body { + background: var(--color-background); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", + Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 16px; + color: var(--color-text); + } + + a { + color: var(--color-link); + text-decoration: none; + } + a:hover { + text-decoration: underline; + } + a.external[target="_blank"] { + background-image: var(--external-icon); + background-position: top 3px right; + background-repeat: no-repeat; + padding-right: 13px; + } + a.tsd-anchor-link { + color: var(--color-text); + } + + code, + pre { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + padding: 0.2em; + margin: 0; + font-size: 0.875rem; + border-radius: 0.8em; + } + + pre { + position: relative; + white-space: pre-wrap; + word-wrap: break-word; + padding: 10px; + border: 1px solid var(--color-accent); + margin-bottom: 8px; + } + pre code { + padding: 0; + font-size: 100%; + } + pre > button { + position: absolute; + top: 10px; + right: 10px; + opacity: 0; + transition: opacity 0.1s; + box-sizing: border-box; + } + pre:hover > button, + pre > button.visible { + opacity: 1; + } + + blockquote { + margin: 1em 0; + padding-left: 1em; + border-left: 4px solid gray; + } + + .tsd-typography { + line-height: 1.333em; + } + .tsd-typography ul { + list-style: square; + padding: 0 0 0 20px; + margin: 0; + } + .tsd-typography .tsd-index-panel h3, + .tsd-index-panel .tsd-typography h3, + .tsd-typography h4, + .tsd-typography h5, + .tsd-typography h6 { + font-size: 1em; + } + .tsd-typography h5, + .tsd-typography h6 { + font-weight: normal; + } + .tsd-typography p, + .tsd-typography ul, + .tsd-typography ol { + margin: 1em 0; + } + .tsd-typography table { + border-collapse: collapse; + border: none; + } + .tsd-typography td, + .tsd-typography th { + padding: 6px 13px; + border: 1px solid var(--color-accent); + } + .tsd-typography thead, + .tsd-typography tr:nth-child(even) { + background-color: var(--color-background-secondary); + } + + .tsd-alert { + padding: 8px 16px; + margin-bottom: 16px; + border-left: 0.25em solid var(--alert-color); + } + .tsd-alert blockquote > :last-child, + .tsd-alert > :last-child { + margin-bottom: 0; + } + .tsd-alert-title { + color: var(--alert-color); + display: inline-flex; + align-items: center; + } + .tsd-alert-title span { + margin-left: 4px; + } + + .tsd-alert-note { + --alert-color: var(--color-alert-note); + } + .tsd-alert-tip { + --alert-color: var(--color-alert-tip); + } + .tsd-alert-important { + --alert-color: var(--color-alert-important); + } + .tsd-alert-warning { + --alert-color: var(--color-alert-warning); + } + .tsd-alert-caution { + --alert-color: var(--color-alert-caution); + } + + .tsd-breadcrumb { + margin: 0; + padding: 0; + color: var(--color-text-aside); + } + .tsd-breadcrumb a { + color: var(--color-text-aside); + text-decoration: none; + } + .tsd-breadcrumb a:hover { + text-decoration: underline; + } + .tsd-breadcrumb li { + display: inline; + } + .tsd-breadcrumb li:after { + content: " / "; + } + + .tsd-comment-tags { + display: flex; + flex-direction: column; + } + dl.tsd-comment-tag-group { + display: flex; + align-items: center; + overflow: hidden; + margin: 0.5em 0; + } + dl.tsd-comment-tag-group dt { + display: flex; + margin-right: 0.5em; + font-size: 0.875em; + font-weight: normal; + } + dl.tsd-comment-tag-group dd { + margin: 0; + } + code.tsd-tag { + padding: 0.25em 0.4em; + border: 0.1em solid var(--color-accent); + margin-right: 0.25em; + font-size: 70%; + } + h1 code.tsd-tag:first-of-type { + margin-left: 0.25em; + } + + dl.tsd-comment-tag-group dd:before, + dl.tsd-comment-tag-group dd:after { + content: " "; + } + dl.tsd-comment-tag-group dd pre, + dl.tsd-comment-tag-group dd:after { + clear: both; + } + dl.tsd-comment-tag-group p { + margin: 0; + } + + .tsd-panel.tsd-comment .lead { + font-size: 1.1em; + line-height: 1.333em; + margin-bottom: 2em; + } + .tsd-panel.tsd-comment .lead:last-child { + margin-bottom: 0; + } + + .tsd-filter-visibility h4 { + font-size: 1rem; + padding-top: 0.75rem; + padding-bottom: 0.5rem; + margin: 0; + } + .tsd-filter-item:not(:last-child) { + margin-bottom: 0.5rem; + } + .tsd-filter-input { + display: flex; + width: -moz-fit-content; + width: fit-content; + align-items: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + } + .tsd-filter-input input[type="checkbox"] { + cursor: pointer; + position: absolute; + width: 1.5em; + height: 1.5em; + opacity: 0; + } + .tsd-filter-input input[type="checkbox"]:disabled { + pointer-events: none; + } + .tsd-filter-input svg { + cursor: pointer; + width: 1.5em; + height: 1.5em; + margin-right: 0.5em; + border-radius: 0.33em; + /* Leaving this at full opacity breaks event listeners on Firefox. + Don't remove unless you know what you're doing. */ + opacity: 0.99; + } + .tsd-filter-input input[type="checkbox"]:focus-visible + svg { + outline: 2px solid var(--color-focus-outline); + } + .tsd-checkbox-background { + fill: var(--color-accent); + } + input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark { + stroke: var(--color-text); + } + .tsd-filter-input input:disabled ~ svg > .tsd-checkbox-background { + fill: var(--color-background); + stroke: var(--color-accent); + stroke-width: 0.25rem; + } + .tsd-filter-input input:disabled ~ svg > .tsd-checkbox-checkmark { + stroke: var(--color-accent); + } + + .settings-label { + font-weight: bold; + text-transform: uppercase; + display: inline-block; + } + + .tsd-filter-visibility .settings-label { + margin: 0.75rem 0 0.5rem 0; + } + + .tsd-theme-toggle .settings-label { + margin: 0.75rem 0.75rem 0 0; + } + + .tsd-hierarchy h4 label:hover span { + text-decoration: underline; + } + + .tsd-hierarchy { + list-style: square; + margin: 0; + } + .tsd-hierarchy-target { + font-weight: bold; + } + .tsd-hierarchy-toggle { + color: var(--color-link); + cursor: pointer; + } + + .tsd-full-hierarchy:not(:last-child) { + margin-bottom: 1em; + padding-bottom: 1em; + border-bottom: 1px solid var(--color-accent); + } + .tsd-full-hierarchy, + .tsd-full-hierarchy ul { + list-style: none; + margin: 0; + padding: 0; + } + .tsd-full-hierarchy ul { + padding-left: 1.5rem; + } + .tsd-full-hierarchy a { + padding: 0.25rem 0 !important; + font-size: 1rem; + display: inline-flex; + align-items: center; + color: var(--color-text); + } + .tsd-full-hierarchy svg[data-dropdown] { + cursor: pointer; + } + .tsd-full-hierarchy svg[data-dropdown="false"] { + transform: rotate(-90deg); + } + .tsd-full-hierarchy svg[data-dropdown="false"] ~ ul { + display: none; + } + + .tsd-panel-group.tsd-index-group { + margin-bottom: 0; + } + .tsd-index-panel .tsd-index-list { + list-style: none; + line-height: 1.333em; + margin: 0; + padding: 0.25rem 0 0 0; + overflow: hidden; + display: grid; + grid-template-columns: repeat(3, 1fr); + column-gap: 1rem; + grid-template-rows: auto; + } + @media (max-width: 1024px) { + .tsd-index-panel .tsd-index-list { + grid-template-columns: repeat(2, 1fr); + } + } + @media (max-width: 768px) { + .tsd-index-panel .tsd-index-list { + grid-template-columns: repeat(1, 1fr); + } + } + .tsd-index-panel .tsd-index-list li { + -webkit-page-break-inside: avoid; + -moz-page-break-inside: avoid; + -ms-page-break-inside: avoid; + -o-page-break-inside: avoid; + page-break-inside: avoid; + } + + .tsd-flag { + display: inline-block; + padding: 0.25em 0.4em; + border-radius: 4px; + color: var(--color-comment-tag-text); + background-color: var(--color-comment-tag); + text-indent: 0; + font-size: 75%; + line-height: 1; + font-weight: normal; + } + + .tsd-anchor { + position: relative; + top: -100px; + } + + .tsd-member { + position: relative; + } + .tsd-member .tsd-anchor + h3 { + display: flex; + align-items: center; + margin-top: 0; + margin-bottom: 0; + border-bottom: none; + } + + .tsd-navigation.settings { + margin: 1rem 0; + } + .tsd-navigation > a, + .tsd-navigation .tsd-accordion-summary { + width: calc(100% - 0.25rem); + display: flex; + align-items: center; + } + .tsd-navigation a, + .tsd-navigation summary > span, + .tsd-page-navigation a { + display: flex; + width: calc(100% - 0.25rem); + align-items: center; + padding: 0.25rem; + color: var(--color-text); + text-decoration: none; + box-sizing: border-box; + } + .tsd-navigation a.current, + .tsd-page-navigation a.current { + background: var(--color-active-menu-item); + } + .tsd-navigation a:hover, + .tsd-page-navigation a:hover { + text-decoration: underline; + } + .tsd-navigation ul, + .tsd-page-navigation ul { + margin-top: 0; + margin-bottom: 0; + padding: 0; + list-style: none; + } + .tsd-navigation li, + .tsd-page-navigation li { + padding: 0; + max-width: 100%; + } + .tsd-navigation .tsd-nav-link { + display: none; + } + .tsd-nested-navigation { + margin-left: 3rem; + } + .tsd-nested-navigation > li > details { + margin-left: -1.5rem; + } + .tsd-small-nested-navigation { + margin-left: 1.5rem; + } + .tsd-small-nested-navigation > li > details { + margin-left: -1.5rem; + } + + .tsd-page-navigation-section { + margin-left: 10px; + } + .tsd-page-navigation-section > summary { + padding: 0.25rem; + } + .tsd-page-navigation-section > div { + margin-left: 20px; + } + .tsd-page-navigation ul { + padding-left: 1.75rem; + } + + #tsd-sidebar-links a { + margin-top: 0; + margin-bottom: 0.5rem; + line-height: 1.25rem; + } + #tsd-sidebar-links a:last-of-type { + margin-bottom: 0; + } + + a.tsd-index-link { + padding: 0.25rem 0 !important; + font-size: 1rem; + line-height: 1.25rem; + display: inline-flex; + align-items: center; + color: var(--color-text); + } + .tsd-accordion-summary { + list-style-type: none; /* hide marker on non-safari */ + outline: none; /* broken on safari, so just hide it */ + } + .tsd-accordion-summary::-webkit-details-marker { + display: none; /* hide marker on safari */ + } + .tsd-accordion-summary, + .tsd-accordion-summary a { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + + cursor: pointer; + } + .tsd-accordion-summary a { + width: calc(100% - 1.5rem); + } + .tsd-accordion-summary > * { + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; + } + .tsd-accordion .tsd-accordion-summary > svg { + margin-left: 0.25rem; + vertical-align: text-top; + } + /* + * We need to be careful to target the arrow indicating whether the accordion + * is open, but not any other SVGs included in the details element. + */ + .tsd-accordion:not([open]) > .tsd-accordion-summary > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h1 > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h2 > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h3 > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h4 > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h5 > svg:first-child { + transform: rotate(-90deg); + } + .tsd-index-content > :not(:first-child) { + margin-top: 0.75rem; + } + .tsd-index-heading { + margin-top: 1.5rem; + margin-bottom: 0.75rem; + } + + .tsd-no-select { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + .tsd-kind-icon { + margin-right: 0.5rem; + width: 1.25rem; + height: 1.25rem; + min-width: 1.25rem; + min-height: 1.25rem; + } + .tsd-signature > .tsd-kind-icon { + margin-right: 0.8rem; + } + + .tsd-panel { + margin-bottom: 2.5rem; + } + .tsd-panel.tsd-member { + margin-bottom: 4rem; + } + .tsd-panel:empty { + display: none; + } + .tsd-panel > h1, + .tsd-panel > h2, + .tsd-panel > h3 { + margin: 1.5rem -1.5rem 0.75rem -1.5rem; + padding: 0 1.5rem 0.75rem 1.5rem; + } + .tsd-panel > h1.tsd-before-signature, + .tsd-panel > h2.tsd-before-signature, + .tsd-panel > h3.tsd-before-signature { + margin-bottom: 0; + border-bottom: none; + } + + .tsd-panel-group { + margin: 2rem 0; + } + .tsd-panel-group.tsd-index-group { + margin: 2rem 0; + } + .tsd-panel-group.tsd-index-group details { + margin: 2rem 0; + } + .tsd-panel-group > .tsd-accordion-summary { + margin-bottom: 1rem; + } + + #tsd-search { + transition: background-color 0.2s; + } + #tsd-search .title { + position: relative; + z-index: 2; + } + #tsd-search .field { + position: absolute; + left: 0; + top: 0; + right: 2.5rem; + height: 100%; + } + #tsd-search .field input { + box-sizing: border-box; + position: relative; + top: -50px; + z-index: 1; + width: 100%; + padding: 0 10px; + opacity: 0; + outline: 0; + border: 0; + background: transparent; + color: var(--color-text); + } + #tsd-search .field label { + position: absolute; + overflow: hidden; + right: -40px; + } + #tsd-search .field input, + #tsd-search .title, + #tsd-toolbar-links a { + transition: opacity 0.2s; + } + #tsd-search .results { + position: absolute; + visibility: hidden; + top: 40px; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); + } + #tsd-search .results li { + background-color: var(--color-background); + line-height: initial; + padding: 4px; + } + #tsd-search .results li:nth-child(even) { + background-color: var(--color-background-secondary); + } + #tsd-search .results li.state { + display: none; + } + #tsd-search .results li.current:not(.no-results), + #tsd-search .results li:hover:not(.no-results) { + background-color: var(--color-accent); + } + #tsd-search .results a { + display: flex; + align-items: center; + padding: 0.25rem; + box-sizing: border-box; + } + #tsd-search .results a:before { + top: 10px; + } + #tsd-search .results span.parent { + color: var(--color-text-aside); + font-weight: normal; + } + #tsd-search.has-focus { + background-color: var(--color-accent); + } + #tsd-search.has-focus .field input { + top: 0; + opacity: 1; + } + #tsd-search.has-focus .title, + #tsd-search.has-focus #tsd-toolbar-links a { + z-index: 0; + opacity: 0; + } + #tsd-search.has-focus .results { + visibility: visible; + } + #tsd-search.loading .results li.state.loading { + display: block; + } + #tsd-search.failure .results li.state.failure { + display: block; + } + + #tsd-toolbar-links { + position: absolute; + top: 0; + right: 2rem; + height: 100%; + display: flex; + align-items: center; + justify-content: flex-end; + } + #tsd-toolbar-links a { + margin-left: 1.5rem; + } + #tsd-toolbar-links a:hover { + text-decoration: underline; + } + + .tsd-signature { + margin: 0 0 1rem 0; + padding: 1rem 0.5rem; + border: 1px solid var(--color-accent); + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + font-size: 14px; + overflow-x: auto; + } + + .tsd-signature-keyword { + color: var(--color-ts-keyword); + font-weight: normal; + } + + .tsd-signature-symbol { + color: var(--color-text-aside); + font-weight: normal; + } + + .tsd-signature-type { + font-style: italic; + font-weight: normal; + } + + .tsd-signatures { + padding: 0; + margin: 0 0 1em 0; + list-style-type: none; + } + .tsd-signatures .tsd-signature { + margin: 0; + border-color: var(--color-accent); + border-width: 1px 0; + transition: background-color 0.1s; + } + .tsd-signatures .tsd-index-signature:not(:last-child) { + margin-bottom: 1em; + } + .tsd-signatures .tsd-index-signature .tsd-signature { + border-width: 1px; + } + .tsd-description .tsd-signatures .tsd-signature { + border-width: 1px; + } + + ul.tsd-parameter-list, + ul.tsd-type-parameter-list { + list-style: square; + margin: 0; + padding-left: 20px; + } + ul.tsd-parameter-list > li.tsd-parameter-signature, + ul.tsd-type-parameter-list > li.tsd-parameter-signature { + list-style: none; + margin-left: -20px; + } + ul.tsd-parameter-list h5, + ul.tsd-type-parameter-list h5 { + font-size: 16px; + margin: 1em 0 0.5em 0; + } + .tsd-sources { + margin-top: 1rem; + font-size: 0.875em; + } + .tsd-sources a { + color: var(--color-text-aside); + text-decoration: underline; + } + .tsd-sources ul { + list-style: none; + padding: 0; + } + + .tsd-page-toolbar { + position: sticky; + z-index: 1; + top: 0; + left: 0; + width: 100%; + color: var(--color-text); + background: var(--color-background-secondary); + border-bottom: 1px var(--color-accent) solid; + transition: transform 0.3s ease-in-out; + } + .tsd-page-toolbar a { + color: var(--color-text); + text-decoration: none; + } + .tsd-page-toolbar a.title { + font-weight: bold; + } + .tsd-page-toolbar a.title:hover { + text-decoration: underline; + } + .tsd-page-toolbar .tsd-toolbar-contents { + display: flex; + justify-content: space-between; + height: 2.5rem; + margin: 0 auto; + } + .tsd-page-toolbar .table-cell { + position: relative; + white-space: nowrap; + line-height: 40px; + } + .tsd-page-toolbar .table-cell:first-child { + width: 100%; + } + .tsd-page-toolbar .tsd-toolbar-icon { + box-sizing: border-box; + line-height: 0; + padding: 12px 0; + } + + .tsd-widget { + display: inline-block; + overflow: hidden; + opacity: 0.8; + height: 40px; + transition: + opacity 0.1s, + background-color 0.2s; + vertical-align: bottom; + cursor: pointer; + } + .tsd-widget:hover { + opacity: 0.9; + } + .tsd-widget.active { + opacity: 1; + background-color: var(--color-accent); + } + .tsd-widget.no-caption { + width: 40px; + } + .tsd-widget.no-caption:before { + margin: 0; + } + + .tsd-widget.options, + .tsd-widget.menu { + display: none; + } + input[type="checkbox"] + .tsd-widget:before { + background-position: -120px 0; + } + input[type="checkbox"]:checked + .tsd-widget:before { + background-position: -160px 0; + } + + img { + max-width: 100%; + } + + .tsd-member-summary-name { + display: inline-flex; + align-items: center; + padding: 0.25rem; + text-decoration: none; + } + + .tsd-anchor-icon { + display: inline-flex; + align-items: center; + margin-left: 0.5rem; + color: var(--color-text); + } + + .tsd-anchor-icon svg { + width: 1em; + height: 1em; + visibility: hidden; + } + + .tsd-member-summary-name:hover > .tsd-anchor-icon svg, + .tsd-anchor-link:hover > .tsd-anchor-icon svg { + visibility: visible; + } + + .deprecated { + text-decoration: line-through !important; + } + + .warning { + padding: 1rem; + color: var(--color-warning-text); + background: var(--color-background-warning); + } + + .tsd-kind-project { + color: var(--color-ts-project); + } + .tsd-kind-module { + color: var(--color-ts-module); + } + .tsd-kind-namespace { + color: var(--color-ts-namespace); + } + .tsd-kind-enum { + color: var(--color-ts-enum); + } + .tsd-kind-enum-member { + color: var(--color-ts-enum-member); + } + .tsd-kind-variable { + color: var(--color-ts-variable); + } + .tsd-kind-function { + color: var(--color-ts-function); + } + .tsd-kind-class { + color: var(--color-ts-class); + } + .tsd-kind-interface { + color: var(--color-ts-interface); + } + .tsd-kind-constructor { + color: var(--color-ts-constructor); + } + .tsd-kind-property { + color: var(--color-ts-property); + } + .tsd-kind-method { + color: var(--color-ts-method); + } + .tsd-kind-reference { + color: var(--color-ts-reference); + } + .tsd-kind-call-signature { + color: var(--color-ts-call-signature); + } + .tsd-kind-index-signature { + color: var(--color-ts-index-signature); + } + .tsd-kind-constructor-signature { + color: var(--color-ts-constructor-signature); + } + .tsd-kind-parameter { + color: var(--color-ts-parameter); + } + .tsd-kind-type-parameter { + color: var(--color-ts-type-parameter); + } + .tsd-kind-accessor { + color: var(--color-ts-accessor); + } + .tsd-kind-get-signature { + color: var(--color-ts-get-signature); + } + .tsd-kind-set-signature { + color: var(--color-ts-set-signature); + } + .tsd-kind-type-alias { + color: var(--color-ts-type-alias); + } + + /* if we have a kind icon, don't color the text by kind */ + .tsd-kind-icon ~ span { + color: var(--color-text); + } + + * { + scrollbar-width: thin; + scrollbar-color: var(--color-accent) var(--color-icon-background); + } + + *::-webkit-scrollbar { + width: 0.75rem; + } + + *::-webkit-scrollbar-track { + background: var(--color-icon-background); + } + + *::-webkit-scrollbar-thumb { + background-color: var(--color-accent); + border-radius: 999rem; + border: 0.25rem solid var(--color-icon-background); + } + + /* mobile */ + @media (max-width: 769px) { + .tsd-widget.options, + .tsd-widget.menu { + display: inline-block; + } + + .container-main { + display: flex; + } + html .col-content { + float: none; + max-width: 100%; + width: 100%; + } + html .col-sidebar { + position: fixed !important; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + z-index: 1024; + top: 0 !important; + bottom: 0 !important; + left: auto !important; + right: 0 !important; + padding: 1.5rem 1.5rem 0 0; + width: 75vw; + visibility: hidden; + background-color: var(--color-background); + transform: translate(100%, 0); + } + html .col-sidebar > *:last-child { + padding-bottom: 20px; + } + html .overlay { + content: ""; + display: block; + position: fixed; + z-index: 1023; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.75); + visibility: hidden; + } + + .to-has-menu .overlay { + animation: fade-in 0.4s; + } + + .to-has-menu .col-sidebar { + animation: pop-in-from-right 0.4s; + } + + .from-has-menu .overlay { + animation: fade-out 0.4s; + } + + .from-has-menu .col-sidebar { + animation: pop-out-to-right 0.4s; + } + + .has-menu body { + overflow: hidden; + } + .has-menu .overlay { + visibility: visible; + } + .has-menu .col-sidebar { + visibility: visible; + transform: translate(0, 0); + display: flex; + flex-direction: column; + gap: 1.5rem; + max-height: 100vh; + padding: 1rem 2rem; + } + .has-menu .tsd-navigation { + max-height: 100%; + } + #tsd-toolbar-links { + display: none; + } + .tsd-navigation .tsd-nav-link { + display: flex; + } + } + + /* one sidebar */ + @media (min-width: 770px) { + .container-main { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); + grid-template-areas: "sidebar content"; + margin: 2rem auto; + } + + .col-sidebar { + grid-area: sidebar; + } + .col-content { + grid-area: content; + padding: 0 1rem; + } + } + @media (min-width: 770px) and (max-width: 1399px) { + .col-sidebar { + max-height: calc(100vh - 2rem - 42px); + overflow: auto; + position: sticky; + top: 42px; + padding-top: 1rem; + } + .site-menu { + margin-top: 1rem; + } + } + + /* two sidebars */ + @media (min-width: 1200px) { + .container-main { + grid-template-columns: minmax(0, 1fr) minmax(0, 2.5fr) minmax( + 0, + 20rem + ); + grid-template-areas: "sidebar content toc"; + } + + .col-sidebar { + display: contents; + } + + .page-menu { + grid-area: toc; + padding-left: 1rem; + } + .site-menu { + grid-area: sidebar; + } + + .site-menu { + margin-top: 1rem; + } + + .page-menu, + .site-menu { + max-height: calc(100vh - 2rem - 42px); + overflow: auto; + position: sticky; + top: 42px; + } + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/Call.RuntimeError.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/Call.RuntimeError.html new file mode 100644 index 000000000..0729f9ddc --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/Call.RuntimeError.html @@ -0,0 +1,11 @@ +RuntimeError | @wailsio/runtime

        Exception class that will be thrown in case the bound method returns an error. +The value of the RuntimeError#name property is "RuntimeError".

        +

        Hierarchy

        Constructors

        Properties

        Constructors

        • Constructs a new RuntimeError instance.

          +

          Parameters

          • Optionalmessage: string

            The error message.

            +
          • Optionaloptions: ErrorOptions

            Options to be forwarded to the Error constructor.

            +

          Returns RuntimeError

        Properties

        cause?: unknown
        message: string
        name: string
        stack?: string
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancelError.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancelError.html new file mode 100644 index 000000000..26a7c6a14 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancelError.html @@ -0,0 +1,13 @@ +CancelError | @wailsio/runtime

        Exception class that will be used as rejection reason +in case a CancellablePromise is cancelled successfully.

        +

        The value of the name property is the string "CancelError". +The value of the cause property is the cause passed to the cancel method, if any.

        +

        Hierarchy

        Constructors

        Properties

        Constructors

        Properties

        cause?: unknown
        message: string
        name: string
        stack?: string
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancellablePromise.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancellablePromise.html new file mode 100644 index 000000000..d0f67dd10 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancellablePromise.html @@ -0,0 +1,274 @@ +CancellablePromise | @wailsio/runtime

        Class CancellablePromise<T>

        A promise with an attached method for cancelling long-running operations (see CancellablePromise#cancel). +Cancellation can optionally be bound to an AbortSignal +for better composability (see CancellablePromise#cancelOn).

        +

        Cancelling a pending promise will result in an immediate rejection +with an instance of CancelError as reason, +but whoever started the promise will be responsible +for actually aborting the underlying operation. +To this purpose, the constructor and all chaining methods +accept optional cancellation callbacks.

        +

        If a CancellablePromise still resolves after having been cancelled, +the result will be discarded. If it rejects, the reason +will be reported as an unhandled rejection, +wrapped in a CancelledRejectionError instance. +To facilitate the handling of cancellation requests, +cancelled CancellablePromises will not report unhandled CancelErrors +whose cause field is the same as the one with which the current promise was cancelled.

        +

        All usual promise methods are defined and return a CancellablePromise +whose cancel method will cancel the parent operation as well, propagating the cancellation reason +upwards through promise chains. +Conversely, cancelling a promise will not automatically cancel dependent promises downstream:

        +
        let root = new CancellablePromise((resolve, reject) => { ... });
        let child1 = root.then(() => { ... });
        let child2 = child1.then(() => { ... });
        let child3 = root.catch(() => { ... });
        child1.cancel(); // Cancels child1 and root, but not child2 or child3 +
        + +

        Cancelling a promise that has already settled is safe and has no consequence.

        +

        The cancel method returns a promise that always fulfills +after the whole chain has processed the cancel request +and all attached callbacks up to that moment have run.

        +

        All ES2024 promise methods (static and instance) are defined on CancellablePromise, +but actual availability may vary with OS/webview version.

        +

        In line with the proposal at https://github.com/tc39/proposal-rm-builtin-subclassing, +CancellablePromise does not support transparent subclassing. +Extenders should take care to provide their own method implementations. +This might be reconsidered in case the proposal is retired.

        +

        CancellablePromise is a wrapper around the DOM Promise object +and is compliant with the Promises/A+ specification +(it passes the compliance suite) +if so is the underlying implementation.

        +

        Type Parameters

        • T

        Hierarchy

        Implements

        Constructors

        • Creates a new CancellablePromise.

          +

          Type Parameters

          • T

          Parameters

          • executor: CancellablePromiseExecutor<T>

            A callback used to initialize the promise. This callback is passed two arguments: +a resolve callback used to resolve the promise with a value +or the result of another promise (possibly cancellable), +and a reject callback used to reject the promise with a provided reason or error. +If the value provided to the resolve callback is a thenable and cancellable object +(it has a then and a cancel method), +cancellation requests will be forwarded to that object and the oncancelled will not be invoked anymore. +If any one of the two callbacks is called after the promise has been cancelled, +the provided values will be cancelled and resolved as usual, +but their results will be discarded. +However, if the resolution process ultimately ends up in a rejection +that is not due to cancellation, the rejection reason +will be wrapped in a CancelledRejectionError +and bubbled up as an unhandled rejection.

            +
          • Optionaloncancelled: CancellablePromiseCanceller

            It is the caller's responsibility to ensure that any operation +started by the executor is properly halted upon cancellation. +This optional callback can be used to that purpose. +It will be called synchronously with a cancellation cause +when cancellation is requested, after the promise has already rejected +with a CancelError, but before +any then/catch/finally callback runs. +If the callback returns a thenable, the promise returned from cancel +will only fulfill after the former has settled. +Unhandled exceptions or rejections from the callback will be wrapped +in a CancelledRejectionError and bubbled up as unhandled rejections. +If the resolve callback is called before cancellation with a cancellable promise, +cancellation requests on this promise will be diverted to that promise, +and the original oncancelled callback will be discarded.

            +

          Returns CancellablePromise<T>

        Properties

        "[toStringTag]": string

        Methods

        • Cancels immediately the execution of the operation associated with this promise. +The promise rejects with a CancelError instance as reason, +with the CancelError#cause property set to the given argument, if any.

          +

          Has no effect if called after the promise has already settled; +repeated calls in particular are safe, but only the first one +will set the cancellation cause.

          +

          The CancelError exception need not be handled explicitly on the promises that are being cancelled: +cancelling a promise with no attached rejection handler does not trigger an unhandled rejection event. +Therefore, the following idioms are all equally correct:

          +
          new CancellablePromise((resolve, reject) => { ... }).cancel();
          new CancellablePromise((resolve, reject) => { ... }).then(...).cancel();
          new CancellablePromise((resolve, reject) => { ... }).then(...).catch(...).cancel(); +
          + +

          Whenever some cancelled promise in a chain rejects with a CancelError +with the same cancellation cause as itself, the error will be discarded silently. +However, the CancelError will still be delivered to all attached rejection handlers +added by then and related methods:

          +
          let cancellable = new CancellablePromise((resolve, reject) => { ... });
          cancellable.then(() => { ... }).catch(console.log);
          cancellable.cancel(); // A CancelError is printed to the console. +
          + +

          If the CancelError is not handled downstream by the time it reaches +a non-cancelled promise, it will trigger an unhandled rejection event, +just like normal rejections would:

          +
          let cancellable = new CancellablePromise((resolve, reject) => { ... });
          let chained = cancellable.then(() => { ... }).then(() => { ... }); // No catch...
          cancellable.cancel(); // Unhandled rejection event on chained! +
          + +

          Therefore, it is important to either cancel whole promise chains from their tail, +as shown in the correct idioms above, or take care of handling errors everywhere.

          +

          Parameters

          • Optionalcause: any

          Returns CancellablePromise<void>

          A cancellable promise that fulfills after the cancel callback (if any) +and all handlers attached up to the call to cancel have run. +If the cancel callback returns a thenable, the promise returned by cancel +will also wait for that thenable to settle. +This enables callers to wait for the cancelled operation to terminate +without being forced to handle potential errors at the call site.

          +
          cancellable.cancel().then(() => {
          // Cleanup finished, it's safe to do something else.
          }, (err) => {
          // Unreachable: the promise returned from cancel will never reject.
          }); +
          + +

          Note that the returned promise will not handle implicitly any rejection +that might have occurred already in the cancelled chain. +It will just track whether registered handlers have been executed or not. +Therefore, unhandled rejections will never be silently handled by calling cancel.

          +
        • Binds promise cancellation to the abort event of the given AbortSignal. +If the signal has already aborted, the promise will be cancelled immediately. +When either condition is verified, the cancellation cause will be set +to the signal's abort reason (see AbortSignal.reason).

          +

          Has no effect if called (or if the signal aborts) after the promise has already settled. +Only the first signal to abort will set the cancellation cause.

          +

          For more details about the cancellation process, +see cancel and the CancellablePromise constructor.

          +

          This method enables awaiting cancellable promises without having +to store them for future cancellation, e.g.:

          +
          await longRunningOperation().cancelOn(signal);
          +
          + +

          instead of:

          +
          let promiseToBeCancelled = longRunningOperation();
          await promiseToBeCancelled; +
          + +

          Parameters

          Returns CancellablePromise<T>

          This promise, for method chaining.

          +
        • Attaches a callback for only the rejection of the Promise.

          +

          The optional oncancelled argument will be invoked when the returned promise is cancelled, +with the same semantics as the oncancelled argument of the constructor. +When the parent promise rejects or is cancelled, the onrejected callback will run, +even after the returned promise has been cancelled: +in that case, should it reject or throw, the reason will be wrapped +in a CancelledRejectionError and bubbled up as an unhandled rejection.

          +

          It is equivalent to

          +
          cancellablePromise.then(undefined, onrejected, oncancelled);
          +
          + +

          and the same caveats apply.

          +

          Type Parameters

          • TResult = never

          Parameters

          Returns CancellablePromise<T | TResult>

          A Promise for the completion of the callback. +Cancellation requests on the returned promise +will propagate up the chain to the parent promise, +but not in the other direction.

          +

          The promise returned from cancel will fulfill only after all attached handlers +up the entire promise chain have been run.

          +

          If onrejected returns a cancellable promise, +cancellation requests will be diverted to it, +and the specified oncancelled callback will be discarded. +See then for more details.

          +
        • Attaches a callback that is invoked when the CancellablePromise is settled (fulfilled or rejected). The +resolved value cannot be accessed or modified from the callback. +The returned promise will settle in the same state as the original one +after the provided callback has completed execution, +unless the callback throws or returns a rejecting promise, +in which case the returned promise will reject as well.

          +

          The optional oncancelled argument will be invoked when the returned promise is cancelled, +with the same semantics as the oncancelled argument of the constructor. +Once the parent promise settles, the onfinally callback will run, +even after the returned promise has been cancelled: +in that case, should it reject or throw, the reason will be wrapped +in a CancelledRejectionError and bubbled up as an unhandled rejection.

          +

          This method is implemented in terms of then and the same caveats apply. +It is polyfilled, hence available in every OS/webview version.

          +

          Parameters

          Returns CancellablePromise<T>

          A Promise for the completion of the callback. +Cancellation requests on the returned promise +will propagate up the chain to the parent promise, +but not in the other direction.

          +

          The promise returned from cancel will fulfill only after all attached handlers +up the entire promise chain have been run.

          +

          If onfinally returns a cancellable promise, +cancellation requests will be diverted to it, +and the specified oncancelled callback will be discarded. +See then for more details.

          +
        • Attaches callbacks for the resolution and/or rejection of the CancellablePromise.

          +

          The optional oncancelled argument will be invoked when the returned promise is cancelled, +with the same semantics as the oncancelled argument of the constructor. +When the parent promise rejects or is cancelled, the onrejected callback will run, +even after the returned promise has been cancelled: +in that case, should it reject or throw, the reason will be wrapped +in a CancelledRejectionError and bubbled up as an unhandled rejection.

          +

          Type Parameters

          • TResult1 = T
          • TResult2 = never

          Parameters

          Returns CancellablePromise<TResult1 | TResult2>

          A CancellablePromise for the completion of whichever callback is executed. +The returned promise is hooked up to propagate cancellation requests up the chain, but not down:

          +
            +
          • if the parent promise is cancelled, the onrejected handler will be invoked with a CancelError +and the returned promise will resolve regularly with its result;
          • +
          • conversely, if the returned promise is cancelled, the parent promise is cancelled too; +the onrejected handler will still be invoked with the parent's CancelError, +but its result will be discarded +and the returned promise will reject with a CancelError as well.
          • +
          +

          The promise returned from cancel will fulfill only after all attached handlers +up the entire promise chain have been run.

          +

          If either callback returns a cancellable promise, +cancellation requests will be diverted to it, +and the specified oncancelled callback will be discarded.

          +
        • Takes a callback of any kind (returns or throws, synchronously or asynchronously) and wraps its result +in a Promise.

          +

          Type Parameters

          • T
          • U extends unknown[]

          Parameters

          • callbackFn: (...args: U) => T | PromiseLike<T>

            A function that is called synchronously. It can do anything: either return +a value, throw an error, or return a promise.

            +
          • ...args: U

            Additional arguments, that will be passed to the callback.

            +

          Returns Promise<Awaited<T>>

          A Promise that is:

          +
            +
          • Already fulfilled, if the callback synchronously returns a value.
          • +
          • Already rejected, if the callback synchronously throws an error.
          • +
          • Asynchronously fulfilled or rejected, if the callback returns a promise.
          • +
          +

        Static Methods

        • Creates a CancellablePromise that is resolved with an array of results +when all of the provided Promises resolve, or rejected when any Promise is rejected.

          +

          Every one of the provided objects that is a thenable and cancellable object +will be cancelled when the returned promise is cancelled, with the same cause.

          +

          Type Parameters

          • T

          Parameters

          Returns CancellablePromise<Awaited<T>[]>

        • Creates a Promise that is resolved with an array of results when all of the provided Promises +resolve, or rejected when any Promise is rejected.

          +

          Type Parameters

          • T extends [] | readonly unknown[]

          Parameters

          • values: T

            An array of Promises.

            +

          Returns CancellablePromise<
              { -readonly [P in string
              | number
              | symbol]: Awaited<T[P<P>]> },
          >

          A new Promise.

          +
        • The any function returns a promise that is fulfilled by the first given promise to be fulfilled, +or rejected with an AggregateError containing an array of rejection reasons +if all of the given promises are rejected. +It resolves all elements of the passed iterable to promises as it runs this algorithm.

          +

          Every one of the provided objects that is a thenable and cancellable object +will be cancelled when the returned promise is cancelled, with the same cause.

          +

          Type Parameters

          • T

          Parameters

          Returns CancellablePromise<Awaited<T>>

        • The any function returns a promise that is fulfilled by the first given promise to be fulfilled, or rejected with an AggregateError containing an array of rejection reasons if all of the given promises are rejected. It resolves all elements of the passed iterable to promises as it runs this algorithm.

          +

          Type Parameters

          • T extends [] | readonly unknown[]

          Parameters

          • values: T

            An array or iterable of Promises.

            +

          Returns CancellablePromise<Awaited<T[number]>>

          A new Promise.

          +
        • Creates a new CancellablePromise that resolves after the specified timeout. +The returned promise can be cancelled without consequences.

          +

          Parameters

          • milliseconds: number

          Returns CancellablePromise<void>

        • Creates a new CancellablePromise that resolves after +the specified timeout, with the provided value. +The returned promise can be cancelled without consequences.

          +

          Type Parameters

          • T

          Parameters

          • milliseconds: number
          • value: T

          Returns CancellablePromise<T>

        • Creates a new CancellablePromise that cancels +after the specified timeout, with the provided cause.

          +

          If the AbortSignal.timeout factory method is available, +it is used to base the timeout on active time rather than elapsed time. +Otherwise, timeout falls back to setTimeout.

          +

          Type Parameters

          • T = never

          Parameters

          • milliseconds: number
          • Optionalcause: any

          Returns CancellablePromise<T>

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancelledRejectionError.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancelledRejectionError.html new file mode 100644 index 000000000..2445bf276 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancelledRejectionError.html @@ -0,0 +1,21 @@ +CancelledRejectionError | @wailsio/runtime

        Class CancelledRejectionError

        Exception class that will be reported as an unhandled rejection +in case a CancellablePromise rejects after being cancelled, +or when the oncancelled callback throws or rejects.

        +

        The value of the name property is the string "CancelledRejectionError". +The value of the cause property is the reason the promise rejected with.

        +

        Because the original promise was cancelled, +a wrapper promise will be passed to the unhandled rejection listener instead. +The promise property holds a reference to the original promise.

        +

        Hierarchy

        • Error
          • CancelledRejectionError

        Constructors

        Properties

        Constructors

        • Constructs a new CancelledRejectionError instance.

          +

          Parameters

          • promise: CancellablePromise<unknown>

            The promise that caused the error originally.

            +
          • Optionalreason: any

            The rejection reason.

            +
          • Optionalinfo: string

            An optional informative message specifying the circumstances in which the error was thrown. +Defaults to the string "Unhandled rejection in cancelled promise.".

            +

          Returns CancelledRejectionError

        Properties

        cause?: unknown
        message: string
        name: string
        promise: CancellablePromise<unknown>

        Holds a reference to the promise that was cancelled and then rejected.

        +
        stack?: string
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/Events.WailsEvent.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/Events.WailsEvent.html new file mode 100644 index 000000000..d1a7924fb --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/Events.WailsEvent.html @@ -0,0 +1,10 @@ +WailsEvent | @wailsio/runtime

        Represents a system event or a custom event emitted through wails-provided facilities.

        +

        Type Parameters

        Constructors

        Properties

        Constructors

        Properties

        Optional data associated with the emitted event.

        +
        name: E

        The name of the event.

        +
        sender?: string

        Name of the originating window. Omitted for application events. +Will be overridden if set manually.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/_internal_.Window.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/_internal_.Window.html new file mode 100644 index 000000000..af1191e71 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/_internal_.Window.html @@ -0,0 +1,145 @@ +Window | @wailsio/runtime

        Methods

        • Gets the specified window.

          +

          Parameters

          • name: string

            The name of the window to get.

            +

          Returns Window

          The corresponding window object.

          +
        • Handles file drops originating from platform-specific code (e.g., macOS/Linux native drag-and-drop). +Gathers information about the drop target element and sends it back to the Go backend.

          +

          Parameters

          • filenames: string[]

            An array of file paths (strings) that were dropped.

            +
          • x: number

            The x-coordinate of the drop event (CSS pixels).

            +
          • y: number

            The y-coordinate of the drop event (CSS pixels).

            +

          Returns void

        • Returns true if the window is focused.

          +

          Returns Promise<boolean>

          Whether the window is currently focused.

          +
        • Returns true if the window is fullscreen.

          +

          Returns Promise<boolean>

          Whether the window is currently fullscreen.

          +
        • Returns true if the window is maximised.

          +

          Returns Promise<boolean>

          Whether the window is currently maximised.

          +
        • Returns true if the window is minimised.

          +

          Returns Promise<boolean>

          Whether the window is currently minimised.

          +
        • Returns true if the window is resizable.

          +

          Returns Promise<boolean>

          Whether the window is currently resizable.

          +
        • Restores the window to its previous state if it was previously minimised, maximised or fullscreen.

          +

          Returns Promise<void>

        • Sets the window to be always on top.

          +

          Parameters

          • alwaysOnTop: boolean

            Whether the window should stay on top.

            +

          Returns Promise<void>

        • Sets the background colour of the window.

          +

          Parameters

          • r: number

            The desired red component of the window background.

            +
          • g: number

            The desired green component of the window background.

            +
          • b: number

            The desired blue component of the window background.

            +
          • a: number

            The desired alpha component of the window background.

            +

          Returns Promise<void>

        • Removes the window frame and title bar.

          +

          Parameters

          • frameless: boolean

            Whether the window should be frameless.

            +

          Returns Promise<void>

        • Disables the system fullscreen button.

          +

          Parameters

          • enabled: boolean

            Whether the fullscreen button should be enabled.

            +

          Returns Promise<void>

        • Sets the maximum size of the window.

          +

          Parameters

          • width: number

            The desired maximum width of the window.

            +
          • height: number

            The desired maximum height of the window.

            +

          Returns Promise<void>

        • Sets the minimum size of the window.

          +

          Parameters

          • width: number

            The desired minimum width of the window.

            +
          • height: number

            The desired minimum height of the window.

            +

          Returns Promise<void>

        • Sets the absolute position of the window.

          +

          Parameters

          • x: number

            The desired horizontal absolute position of the window.

            +
          • y: number

            The desired vertical absolute position of the window.

            +

          Returns Promise<void>

        • Sets the relative position of the window to the screen.

          +

          Parameters

          • x: number

            The desired horizontal relative position of the window.

            +
          • y: number

            The desired vertical relative position of the window.

            +

          Returns Promise<void>

        • Sets whether the window is resizable.

          +

          Parameters

          • resizable: boolean

            Whether the window should be resizable.

            +

          Returns Promise<void>

        • Sets the size of the window.

          +

          Parameters

          • width: number

            The desired width of the window.

            +
          • height: number

            The desired height of the window.

            +

          Returns Promise<void>

        • Sets the title of the window.

          +

          Parameters

          • title: string

            The desired title of the window.

            +

          Returns Promise<void>

        • Sets the zoom level of the window.

          +

          Parameters

          • zoom: number

            The desired zoom level.

            +

          Returns Promise<void>

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Application.Hide.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Application.Hide.html new file mode 100644 index 000000000..4828abe86 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Application.Hide.html @@ -0,0 +1,2 @@ +Hide | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Application.Quit.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Application.Quit.html new file mode 100644 index 000000000..bb3ec1cb1 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Application.Quit.html @@ -0,0 +1,2 @@ +Quit | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Application.Show.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Application.Show.html new file mode 100644 index 000000000..83e6d02be --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Application.Show.html @@ -0,0 +1,2 @@ +Show | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Browser.OpenURL.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Browser.OpenURL.html new file mode 100644 index 000000000..ee1fc3800 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Browser.OpenURL.html @@ -0,0 +1,3 @@ +OpenURL | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Call.ByID.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Call.ByID.html new file mode 100644 index 000000000..0b087b923 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Call.ByID.html @@ -0,0 +1,6 @@ +ByID | @wailsio/runtime
        • Calls a method by its numeric ID with the specified arguments. +See Call for details.

          +

          Parameters

          • methodID: number

            The ID of the method to call.

            +
          • ...args: any[]

            The arguments to pass to the method.

            +

          Returns CancellablePromise<any>

          The result of the method call.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Call.ByName.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Call.ByName.html new file mode 100644 index 000000000..e8e1d3202 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Call.ByName.html @@ -0,0 +1,6 @@ +ByName | @wailsio/runtime
        • Calls a bound method by name with the specified arguments. +See Call for details.

          +

          Parameters

          • methodName: string

            The name of the method in the format 'package.struct.method'.

            +
          • ...args: any[]

            The arguments to pass to the method.

            +

          Returns CancellablePromise<any>

          The result of the method call.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Call.Call.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Call.Call.html new file mode 100644 index 000000000..0be8d62e3 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Call.Call.html @@ -0,0 +1,9 @@ +Call | @wailsio/runtime
        • Call a bound method according to the given call options.

          +

          In case of failure, the returned promise will reject with an exception +among ReferenceError (unknown method), TypeError (wrong argument count or type), +RuntimeError (method returned an error), or other (network or internal errors). +The exception might have a "cause" field with the value returned +by the application- or service-level error marshaling functions.

          +

          Parameters

          Returns CancellablePromise<any>

          The result of the call.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Clipboard.SetText.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Clipboard.SetText.html new file mode 100644 index 000000000..681208368 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Clipboard.SetText.html @@ -0,0 +1,4 @@ +SetText | @wailsio/runtime
        • Sets the text to the Clipboard.

          +

          Parameters

          • text: string

            The text to be set to the Clipboard.

            +

          Returns Promise<void>

          A Promise that resolves when the operation is successful.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Clipboard.Text.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Clipboard.Text.html new file mode 100644 index 000000000..4e0514fe5 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Clipboard.Text.html @@ -0,0 +1,3 @@ +Text | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Error.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Error.html new file mode 100644 index 000000000..3d47a092f --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Error.html @@ -0,0 +1,4 @@ +Error | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Info.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Info.html new file mode 100644 index 000000000..554890e04 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Info.html @@ -0,0 +1,4 @@ +Info | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.OpenFile.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.OpenFile.html new file mode 100644 index 000000000..aeee248b4 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.OpenFile.html @@ -0,0 +1,10 @@ +OpenFile | @wailsio/runtime
        • Presents a file selection dialog to pick one or more files to open.

          +

          Parameters

          Returns Promise<string[]>

          Selected file or list of files, or a blank string/empty list if no file has been selected.

          +
        • Presents a file selection dialog to pick one or more files to open.

          +

          Parameters

          Returns Promise<string>

          Selected file or list of files, or a blank string/empty list if no file has been selected.

          +
        • Presents a file selection dialog to pick one or more files to open.

          +

          Parameters

          Returns Promise<string | string[]>

          Selected file or list of files, or a blank string/empty list if no file has been selected.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Question.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Question.html new file mode 100644 index 000000000..220b94548 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Question.html @@ -0,0 +1,4 @@ +Question | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.SaveFile.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.SaveFile.html new file mode 100644 index 000000000..38321e952 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.SaveFile.html @@ -0,0 +1,4 @@ +SaveFile | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Warning.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Warning.html new file mode 100644 index 000000000..01d600f5c --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Warning.html @@ -0,0 +1,4 @@ +Warning | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.Emit.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.Emit.html new file mode 100644 index 000000000..f7598d66a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.Emit.html @@ -0,0 +1,8 @@ +Emit | @wailsio/runtime
        • Emits an event.

          +

          Type Parameters

          • E extends string & {} = string & {}

          Parameters

          • name: E

            The name of the event to emit

            +
          • data: WailsEventData<E>

            The data that will be sent with the event

            +

          Returns Promise<boolean>

          A promise that will be fulfilled once the event has been emitted. Resolves to true if the event was cancelled.

          +
        • Emits an event.

          +

          Type Parameters

          • E extends string & {} = string & {}

          Parameters

          • name: WailsEventData<E> extends null | void ? E : never

            The name of the event to emit

            +

          Returns Promise<boolean>

          A promise that will be fulfilled once the event has been emitted. Resolves to true if the event was cancelled.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.Off.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.Off.html new file mode 100644 index 000000000..c473825f9 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.Off.html @@ -0,0 +1,3 @@ +Off | @wailsio/runtime
        • Removes event listeners for the specified event names.

          +

          Parameters

          • ...eventNames: [string & {}, ...(string & {})[]]

            The name of the events to remove listeners for.

            +

          Returns void

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.OffAll.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.OffAll.html new file mode 100644 index 000000000..1ef26eee0 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.OffAll.html @@ -0,0 +1,2 @@ +OffAll | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.On.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.On.html new file mode 100644 index 000000000..efa47b7d7 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.On.html @@ -0,0 +1,5 @@ +On | @wailsio/runtime
        • Registers a callback function to be executed when the specified event occurs.

          +

          Type Parameters

          • E extends string & {} = string & {}

          Parameters

          • eventName: E

            The name of the event to register the callback for.

            +
          • callback: WailsEventCallback<E>

            The callback function to be called when the event is triggered.

            +

          Returns () => void

          A function that, when called, will unregister the callback from the event.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.OnMultiple.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.OnMultiple.html new file mode 100644 index 000000000..138294c31 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.OnMultiple.html @@ -0,0 +1,6 @@ +OnMultiple | @wailsio/runtime
        • Register a callback function to be called multiple times for a specific event.

          +

          Type Parameters

          • E extends string & {} = string & {}

          Parameters

          • eventName: E

            The name of the event to register the callback for.

            +
          • callback: WailsEventCallback<E>

            The callback function to be called when the event is triggered.

            +
          • maxCallbacks: number

            The maximum number of times the callback can be called for the event. Once the maximum number is reached, the callback will no longer be called.

            +

          Returns () => void

          A function that, when called, will unregister the callback from the event.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.Once.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.Once.html new file mode 100644 index 000000000..3db37510e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.Once.html @@ -0,0 +1,5 @@ +Once | @wailsio/runtime
        • Registers a callback function to be executed only once for the specified event.

          +

          Type Parameters

          • E extends string & {} = string & {}

          Parameters

          • eventName: E

            The name of the event to register the callback for.

            +
          • callback: WailsEventCallback<E>

            The callback function to be called when the event is triggered.

            +

          Returns () => void

          A function that, when called, will unregister the callback from the event.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Flags.GetFlag.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Flags.GetFlag.html new file mode 100644 index 000000000..2c56d0f1b --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Flags.GetFlag.html @@ -0,0 +1,4 @@ +GetFlag | @wailsio/runtime
        • Retrieves the value associated with the specified key from the flag map.

          +

          Parameters

          • key: string

            The key to retrieve the value for.

            +

          Returns any

          The value associated with the specified key.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/IOS.Device.Info-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/IOS.Device.Info-1.html new file mode 100644 index 000000000..f9f97e0ed --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/IOS.Device.Info-1.html @@ -0,0 +1 @@ +Info | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/IOS.Haptics.Impact.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/IOS.Haptics.Impact.html new file mode 100644 index 000000000..0cd22171f --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/IOS.Haptics.Impact.html @@ -0,0 +1 @@ +Impact | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Screens.GetAll.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Screens.GetAll.html new file mode 100644 index 000000000..45d99bb96 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Screens.GetAll.html @@ -0,0 +1,3 @@ +GetAll | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Screens.GetCurrent.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Screens.GetCurrent.html new file mode 100644 index 000000000..b042871d5 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Screens.GetCurrent.html @@ -0,0 +1,3 @@ +GetCurrent | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Screens.GetPrimary.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Screens.GetPrimary.html new file mode 100644 index 000000000..a7bb9f8c7 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Screens.GetPrimary.html @@ -0,0 +1,3 @@ +GetPrimary | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.Capabilities.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.Capabilities.html new file mode 100644 index 000000000..d8b16a640 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.Capabilities.html @@ -0,0 +1,3 @@ +Capabilities | @wailsio/runtime
        • Fetches the capabilities of the application from the server.

          +

          Returns Promise<Record<string, any>>

          A promise that resolves to an object containing the capabilities.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.Environment.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.Environment.html new file mode 100644 index 000000000..83e16aed1 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.Environment.html @@ -0,0 +1,3 @@ +Environment | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsAMD64.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsAMD64.html new file mode 100644 index 000000000..8463205b4 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsAMD64.html @@ -0,0 +1,3 @@ +IsAMD64 | @wailsio/runtime
        • Checks if the current environment architecture is AMD64.

          +

          Returns boolean

          True if the current environment architecture is AMD64, false otherwise.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsARM.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsARM.html new file mode 100644 index 000000000..4e85b9117 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsARM.html @@ -0,0 +1,3 @@ +IsARM | @wailsio/runtime
        • Checks if the current architecture is ARM.

          +

          Returns boolean

          True if the current architecture is ARM, false otherwise.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsARM64.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsARM64.html new file mode 100644 index 000000000..8b6f9a174 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsARM64.html @@ -0,0 +1,3 @@ +IsARM64 | @wailsio/runtime
        • Checks if the current environment is ARM64 architecture.

          +

          Returns boolean

          Returns true if the environment is ARM64 architecture, otherwise returns false.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsDarkMode.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsDarkMode.html new file mode 100644 index 000000000..461dc8e2e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsDarkMode.html @@ -0,0 +1,3 @@ +IsDarkMode | @wailsio/runtime
        • Retrieves the system dark mode status.

          +

          Returns Promise<boolean>

          A promise that resolves to a boolean value indicating if the system is in dark mode.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsDebug.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsDebug.html new file mode 100644 index 000000000..f9fa983df --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsDebug.html @@ -0,0 +1,3 @@ +IsDebug | @wailsio/runtime
        • Reports whether the app is being run in debug mode.

          +

          Returns boolean

          True if the app is being run in debug mode.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsLinux.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsLinux.html new file mode 100644 index 000000000..98a91aeb2 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsLinux.html @@ -0,0 +1,3 @@ +IsLinux | @wailsio/runtime
        • Checks if the current operating system is Linux.

          +

          Returns boolean

          Returns true if the current operating system is Linux, false otherwise.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsMac.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsMac.html new file mode 100644 index 000000000..ec0843efc --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsMac.html @@ -0,0 +1,3 @@ +IsMac | @wailsio/runtime
        • Checks if the current environment is a macOS operating system.

          +

          Returns boolean

          True if the environment is macOS, false otherwise.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsWindows.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsWindows.html new file mode 100644 index 000000000..658f88991 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsWindows.html @@ -0,0 +1,3 @@ +IsWindows | @wailsio/runtime
        • Checks if the current operating system is Windows.

          +

          Returns boolean

          True if the operating system is Windows, otherwise false.

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.invoke.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.invoke.html new file mode 100644 index 000000000..5a660b237 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.invoke.html @@ -0,0 +1 @@ +invoke | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/WML.Enable.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/WML.Enable.html new file mode 100644 index 000000000..46bffbe7d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/WML.Enable.html @@ -0,0 +1,2 @@ +Enable | @wailsio/runtime
        • Schedules an automatic reload of WML to be performed as soon as the document is fully loaded.

          +

          Returns void

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/WML.Reload.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/WML.Reload.html new file mode 100644 index 000000000..41478c379 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/WML.Reload.html @@ -0,0 +1,2 @@ +Reload | @wailsio/runtime
        • Reloads the WML page by adding necessary event listeners and browser listeners.

          +

          Returns void

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/getTransport.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/getTransport.html new file mode 100644 index 000000000..76abb135d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/getTransport.html @@ -0,0 +1,2 @@ +getTransport | @wailsio/runtime

        Function getTransport

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/loadOptionalScript.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/loadOptionalScript.html new file mode 100644 index 000000000..54a710f6e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/loadOptionalScript.html @@ -0,0 +1,4 @@ +loadOptionalScript | @wailsio/runtime

        Function loadOptionalScript

        • Loads a script from the given URL if it exists. +Uses HEAD request to check existence, then injects a script tag. +Silently ignores if the script doesn't exist.

          +

          Parameters

          • url: string

          Returns Promise<void>

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/setTransport.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/setTransport.html new file mode 100644 index 000000000..365927745 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/setTransport.html @@ -0,0 +1,8 @@ +setTransport | @wailsio/runtime

        Function setTransport

        • Set a custom transport for all Wails runtime calls. +This allows you to replace the default HTTP fetch transport with +WebSockets, custom protocols, or any other mechanism.

          +

          Parameters

          Returns void

          import { setTransport } from '/wails/runtime.js';

          const wsTransport = {
          call: async (objectID, method, windowName, args) => {
          // Your WebSocket implementation
          }
          };

          setTransport(wsTransport); +
          + +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/hierarchy.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/hierarchy.html new file mode 100644 index 000000000..1589a9b89 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/hierarchy.html @@ -0,0 +1 @@ +@wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/index.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/index.html new file mode 100644 index 000000000..90f345405 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/index.html @@ -0,0 +1,7 @@ +@wailsio/runtime

        @wailsio/runtime

        README

        The index.js file in the compiled directory is the entrypoint for the runtime.js file that may be +loaded at runtime. This will add window.wails and window._wails to the global scope.

        +

        NOTE: It is preferable to use the @wailsio/runtime package to use the runtime.

        +

        ⚠️ Do not rebuild the runtime manually after updating TS code: +the CI pipeline will take care of this. +PRs that touch build artifacts will be blocked from merging.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/CancellablePromiseLike.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/CancellablePromiseLike.html new file mode 100644 index 000000000..e7a8ca6f3 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/CancellablePromiseLike.html @@ -0,0 +1,3 @@ +CancellablePromiseLike | @wailsio/runtime

        Interface CancellablePromiseLike<T>

        interface CancellablePromiseLike<T> {
            cancel(cause?: any): void | PromiseLike<void>;
            then<TResult1 = T, TResult2 = never>(
                onfulfilled?:
                    | null
                    | (
                        value: T,
                    ) => TResult1 | PromiseLike<TResult1> | CancellablePromiseLike<TResult1>,
                onrejected?:
                    | null
                    | (
                        reason: any,
                    ) => TResult2 | PromiseLike<TResult2> | CancellablePromiseLike<TResult2>,
            ): CancellablePromiseLike<TResult1 | TResult2>;
        }

        Type Parameters

        • T

        Implemented by

        Methods

        Methods

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/CancellablePromiseWithResolvers.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/CancellablePromiseWithResolvers.html new file mode 100644 index 000000000..3bd162eae --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/CancellablePromiseWithResolvers.html @@ -0,0 +1,7 @@ +CancellablePromiseWithResolvers | @wailsio/runtime

        Interface CancellablePromiseWithResolvers<T>

        Wraps a cancellable promise along with its resolution methods. +The oncancelled field will be null initially but may be set to provide a custom cancellation function.

        +
        interface CancellablePromiseWithResolvers<T> {
            oncancelled: null | CancellablePromiseCanceller;
            promise: CancellablePromise<T>;
            reject: CancellablePromiseRejector;
            resolve: CancellablePromiseResolver<T>;
        }

        Type Parameters

        • T

        Properties

        oncancelled: null | CancellablePromiseCanceller
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.Button.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.Button.html new file mode 100644 index 000000000..737759b54 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.Button.html @@ -0,0 +1,7 @@ +Button | @wailsio/runtime
        interface Button {
            IsCancel?: boolean;
            IsDefault?: boolean;
            Label?: string;
        }

        Properties

        IsCancel?: boolean

        True if the button should cancel an operation when clicked.

        +
        IsDefault?: boolean

        True if the button should be the default action when the user presses enter.

        +
        Label?: string

        Text that appears within the button.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.FileFilter.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.FileFilter.html new file mode 100644 index 000000000..1297d44d7 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.FileFilter.html @@ -0,0 +1,5 @@ +FileFilter | @wailsio/runtime
        interface FileFilter {
            DisplayName?: string;
            Pattern?: string;
        }

        Properties

        Properties

        DisplayName?: string

        Display name for the filter, it could be "Text Files", "Images" etc.

        +
        Pattern?: string

        Pattern to match for the filter, e.g. ".txt;.md" for text markdown files.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.MessageDialogOptions.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.MessageDialogOptions.html new file mode 100644 index 000000000..8ea47f0ac --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.MessageDialogOptions.html @@ -0,0 +1,9 @@ +MessageDialogOptions | @wailsio/runtime

        Interface MessageDialogOptions

        interface MessageDialogOptions {
            Buttons?: Button[];
            Detached?: boolean;
            Message?: string;
            Title?: string;
        }

        Properties

        Buttons?: Button[]

        Array of button options to show in the dialog.

        +
        Detached?: boolean

        True if the dialog should appear detached from the main window (if applicable).

        +
        Message?: string

        The main message to show in the dialog.

        +
        Title?: string

        The title of the dialog window.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.OpenFileDialogOptions.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.OpenFileDialogOptions.html new file mode 100644 index 000000000..b016009a2 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.OpenFileDialogOptions.html @@ -0,0 +1,33 @@ +OpenFileDialogOptions | @wailsio/runtime

        Interface OpenFileDialogOptions

        interface OpenFileDialogOptions {
            AllowsMultipleSelection?: boolean;
            AllowsOtherFiletypes?: boolean;
            ButtonText?: string;
            CanChooseDirectories?: boolean;
            CanChooseFiles?: boolean;
            CanCreateDirectories?: boolean;
            CanSelectHiddenExtension?: boolean;
            Detached?: boolean;
            Directory?: string;
            Filters?: FileFilter[];
            HideExtension?: boolean;
            Message?: string;
            ResolvesAliases?: boolean;
            ShowHiddenFiles?: boolean;
            Title?: string;
            TreatsFilePackagesAsDirectories?: boolean;
        }

        Properties

        AllowsMultipleSelection?: boolean

        Indicates if multiple selection is allowed.

        +
        AllowsOtherFiletypes?: boolean

        Indicates if other file types are allowed.

        +
        ButtonText?: string

        Text to display on the button.

        +
        CanChooseDirectories?: boolean

        Indicates if directories can be chosen.

        +
        CanChooseFiles?: boolean

        Indicates if files can be chosen.

        +
        CanCreateDirectories?: boolean

        Indicates if directories can be created.

        +
        CanSelectHiddenExtension?: boolean

        Indicates if hidden extensions can be selected.

        +
        Detached?: boolean

        Indicates if the dialog should appear detached from the main window.

        +
        Directory?: string

        Directory to open in the dialog.

        +
        Filters?: FileFilter[]

        Array of file filters.

        +
        HideExtension?: boolean

        Indicates if the extension should be hidden.

        +
        Message?: string

        Message to show in the dialog.

        +
        ResolvesAliases?: boolean

        Indicates if aliases should be resolved.

        +
        ShowHiddenFiles?: boolean

        Indicates if hidden files should be shown.

        +
        Title?: string

        Title of the dialog.

        +
        TreatsFilePackagesAsDirectories?: boolean

        Indicates if file packages should be treated as directories.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.SaveFileDialogOptions.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.SaveFileDialogOptions.html new file mode 100644 index 000000000..bf1438ef1 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Dialogs.SaveFileDialogOptions.html @@ -0,0 +1,33 @@ +SaveFileDialogOptions | @wailsio/runtime

        Interface SaveFileDialogOptions

        interface SaveFileDialogOptions {
            AllowsOtherFiletypes?: boolean;
            ButtonText?: string;
            CanChooseDirectories?: boolean;
            CanChooseFiles?: boolean;
            CanCreateDirectories?: boolean;
            CanSelectHiddenExtension?: boolean;
            Detached?: boolean;
            Directory?: string;
            Filename?: string;
            Filters?: FileFilter[];
            HideExtension?: boolean;
            Message?: string;
            ResolvesAliases?: boolean;
            ShowHiddenFiles?: boolean;
            Title?: string;
            TreatsFilePackagesAsDirectories?: boolean;
        }

        Properties

        AllowsOtherFiletypes?: boolean

        Indicates if other file types are allowed.

        +
        ButtonText?: string

        Text to display on the button.

        +
        CanChooseDirectories?: boolean

        Indicates if directories can be chosen.

        +
        CanChooseFiles?: boolean

        Indicates if files can be chosen.

        +
        CanCreateDirectories?: boolean

        Indicates if directories can be created.

        +
        CanSelectHiddenExtension?: boolean

        Indicates if hidden extensions can be selected.

        +
        Detached?: boolean

        Indicates if the dialog should appear detached from the main window.

        +
        Directory?: string

        Directory to open in the dialog.

        +
        Filename?: string

        Default filename to use in the dialog.

        +
        Filters?: FileFilter[]

        Array of file filters.

        +
        HideExtension?: boolean

        Indicates if the extension should be hidden.

        +
        Message?: string

        Message to show in the dialog.

        +
        ResolvesAliases?: boolean

        Indicates if aliases should be resolved.

        +
        ShowHiddenFiles?: boolean

        Indicates if hidden files should be shown.

        +
        Title?: string

        Title of the dialog.

        +
        TreatsFilePackagesAsDirectories?: boolean

        Indicates if file packages should be treated as directories.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Events.CustomEvents.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Events.CustomEvents.html new file mode 100644 index 000000000..c02514b38 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Events.CustomEvents.html @@ -0,0 +1,3 @@ +CustomEvents | @wailsio/runtime

        A table of data types for all known events. +Will be monkey-patched by the binding generator.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/IOS.Device.Info.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/IOS.Device.Info.html new file mode 100644 index 000000000..8e4739b95 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/IOS.Device.Info.html @@ -0,0 +1,6 @@ +Info | @wailsio/runtime
        interface Info {
            constructor: any;
            isSimulator: boolean;
            model: string;
            systemName: string;
            systemVersion: string;
        }

        Constructors

        constructor: any

        Properties

        isSimulator: boolean
        model: string
        systemName: string
        systemVersion: string
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/RuntimeTransport.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/RuntimeTransport.html new file mode 100644 index 000000000..6a07404d7 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/RuntimeTransport.html @@ -0,0 +1,11 @@ +RuntimeTransport | @wailsio/runtime

        Interface RuntimeTransport

        RuntimeTransport defines the interface for custom IPC transport implementations. +Implement this interface to use WebSockets, custom protocols, or any other +transport mechanism instead of the default HTTP fetch.

        +
        interface RuntimeTransport {
            call(
                objectID: number,
                method: number,
                windowName: string,
                args: any,
            ): Promise<any>;
        }

        Methods

        Methods

        • Send a runtime call and return the response.

          +

          Parameters

          • objectID: number

            The Wails object ID (0=Call, 1=Clipboard, etc.)

            +
          • method: number

            The method ID to call

            +
          • windowName: string

            Optional window name

            +
          • args: any

            Arguments to pass (will be JSON stringified if present)

            +

          Returns Promise<any>

          Promise that resolves with the response data

          +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Screens.Rect.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Screens.Rect.html new file mode 100644 index 000000000..3c3b36416 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Screens.Rect.html @@ -0,0 +1,9 @@ +Rect | @wailsio/runtime
        interface Rect {
            Height: number;
            Width: number;
            X: number;
            Y: number;
        }

        Properties

        Properties

        Height: number

        The height of the rectangle.

        +
        Width: number

        The width of the rectangle.

        +
        X: number

        The X coordinate of the origin.

        +
        Y: number

        The Y coordinate of the origin.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Screens.Screen.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Screens.Screen.html new file mode 100644 index 000000000..78e0df769 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Screens.Screen.html @@ -0,0 +1,25 @@ +Screen | @wailsio/runtime
        interface Screen {
            Bounds: Rect;
            ID: string;
            IsPrimary: boolean;
            Name: string;
            PhysicalBounds: Rect;
            PhysicalWorkArea: Rect;
            Rotation: number;
            ScaleFactor: number;
            Size: Screens.Size;
            WorkArea: Rect;
            X: number;
            Y: number;
        }

        Properties

        Bounds: Rect

        Contains the bounds of the screen in terms of X, Y, Width, and Height.

        +
        ID: string

        Unique identifier for the screen.

        +
        IsPrimary: boolean

        True if this is the primary monitor selected by the user in the operating system.

        +
        Name: string

        Human-readable name of the screen.

        +
        PhysicalBounds: Rect

        Contains the physical bounds of the screen in terms of X, Y, Width, and Height (before scaling).

        +
        PhysicalWorkArea: Rect

        Contains the physical WorkArea of the screen (before scaling).

        +
        Rotation: number

        The rotation of the screen.

        +
        ScaleFactor: number

        The scale factor of the screen (DPI/96). 1 = standard DPI, 2 = HiDPI (Retina), etc.

        +

        Contains the width and height of the screen.

        +
        WorkArea: Rect

        Contains the area of the screen that is actually usable (excluding taskbar and other system UI).

        +
        X: number

        The X coordinate of the screen.

        +
        Y: number

        The Y coordinate of the screen.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Screens.Size.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Screens.Size.html new file mode 100644 index 000000000..03f72fcaf --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/Screens.Size.html @@ -0,0 +1,5 @@ +Size | @wailsio/runtime
        interface Size {
            Height: number;
            Width: number;
        }

        Properties

        Properties

        Height: number

        The height of a rectangular area.

        +
        Width: number

        The width of a rectangular area.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/System.EnvironmentInfo.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/System.EnvironmentInfo.html new file mode 100644 index 000000000..16b194a24 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/System.EnvironmentInfo.html @@ -0,0 +1,11 @@ +EnvironmentInfo | @wailsio/runtime

        Interface EnvironmentInfo

        interface EnvironmentInfo {
            Arch: string;
            Debug: boolean;
            OS: string;
            OSInfo: OSInfo;
            PlatformInfo: Record<string, any>;
        }

        Properties

        Properties

        Arch: string

        The architecture of the system.

        +
        Debug: boolean

        True if the application is running in debug mode, otherwise false.

        +
        OS: string

        The operating system in use.

        +
        OSInfo: OSInfo

        Details of the operating system.

        +
        PlatformInfo: Record<string, any>

        Additional platform information.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/System.OSInfo.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/System.OSInfo.html new file mode 100644 index 000000000..a008978cf --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/System.OSInfo.html @@ -0,0 +1,9 @@ +OSInfo | @wailsio/runtime
        interface OSInfo {
            Branding: string;
            ID: string;
            Name: string;
            Version: string;
        }

        Properties

        Properties

        Branding: string

        The branding of the OS.

        +
        ID: string

        The ID of the OS.

        +
        Name: string

        The name of the OS.

        +
        Version: string

        The version of the OS.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.ErrorOptions.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.ErrorOptions.html new file mode 100644 index 000000000..12c1c1dec --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.ErrorOptions.html @@ -0,0 +1,2 @@ +ErrorOptions | @wailsio/runtime
        interface ErrorOptions {
            cause?: unknown;
        }

        Properties

        Properties

        cause?: unknown
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Iterable.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Iterable.html new file mode 100644 index 000000000..02cc4cf80 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Iterable.html @@ -0,0 +1,2 @@ +Iterable | @wailsio/runtime

        Interface Iterable<T, TReturn, TNext>

        interface Iterable<T, TReturn = any, TNext = any> {
            "[iterator]"(): Iterator<T, TReturn, TNext>;
        }

        Type Parameters

        • T
        • TReturn = any
        • TNext = any

        Methods

        Methods

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Position.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Position.html new file mode 100644 index 000000000..8cbcc9c3a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Position.html @@ -0,0 +1,6 @@ +Position | @wailsio/runtime

        A record describing the position of a window.

        +
        interface Position {
            x: number;
            y: number;
        }

        Properties

        x +y +

        Properties

        x: number

        The horizontal position of the window.

        +
        y: number

        The vertical position of the window.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.PromiseFulfilledResult.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.PromiseFulfilledResult.html new file mode 100644 index 000000000..0f1002ee8 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.PromiseFulfilledResult.html @@ -0,0 +1,3 @@ +PromiseFulfilledResult | @wailsio/runtime

        Interface PromiseFulfilledResult<T>

        interface PromiseFulfilledResult<T> {
            status: "fulfilled";
            value: T;
        }

        Type Parameters

        • T

        Properties

        Properties

        status: "fulfilled"
        value: T
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.PromiseLike.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.PromiseLike.html new file mode 100644 index 000000000..4add9f58e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.PromiseLike.html @@ -0,0 +1,6 @@ +PromiseLike | @wailsio/runtime
        interface PromiseLike<T> {
            then<TResult1 = T, TResult2 = never>(
                onfulfilled?: null | (value: T) => TResult1 | PromiseLike<TResult1>,
                onrejected?: null | (reason: any) => TResult2 | PromiseLike<TResult2>,
            ): PromiseLike<TResult1 | TResult2>;
        }

        Type Parameters

        • T

        Implemented by

        Methods

        Methods

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.PromiseRejectedResult.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.PromiseRejectedResult.html new file mode 100644 index 000000000..a364e257f --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.PromiseRejectedResult.html @@ -0,0 +1,3 @@ +PromiseRejectedResult | @wailsio/runtime
        interface PromiseRejectedResult {
            reason: any;
            status: "rejected";
        }

        Properties

        Properties

        reason: any
        status: "rejected"
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.PromiseWithResolvers.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.PromiseWithResolvers.html new file mode 100644 index 000000000..b942f497b --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.PromiseWithResolvers.html @@ -0,0 +1,4 @@ +PromiseWithResolvers | @wailsio/runtime

        Interface PromiseWithResolvers<T>

        interface PromiseWithResolvers<T> {
            promise: Promise<T>;
            reject: (reason?: any) => void;
            resolve: (value: T | PromiseLike<T>) => void;
        }

        Type Parameters

        • T

        Properties

        Properties

        promise: Promise<T>
        reject: (reason?: any) => void
        resolve: (value: T | PromiseLike<T>) => void
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Size.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Size.html new file mode 100644 index 000000000..e7b5f32de --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Size.html @@ -0,0 +1,6 @@ +Size | @wailsio/runtime

        A record describing the size of a window.

        +
        interface Size {
            height: number;
            width: number;
        }

        Properties

        Properties

        height: number

        The height of the window.

        +
        width: number

        The width of the window.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules.html new file mode 100644 index 000000000..a35d3b515 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules.html @@ -0,0 +1 @@ +@wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Application.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Application.html new file mode 100644 index 000000000..e6b5d57c5 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Application.html @@ -0,0 +1 @@ +Application | @wailsio/runtime

        Namespace Application

        Functions

        Hide
        Quit
        Show
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Browser.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Browser.html new file mode 100644 index 000000000..757bf8469 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Browser.html @@ -0,0 +1 @@ +Browser | @wailsio/runtime

        Namespace Browser

        Functions

        OpenURL
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Call.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Call.html new file mode 100644 index 000000000..8d06195ea --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Call.html @@ -0,0 +1 @@ +Call | @wailsio/runtime

        Namespace Call

        Classes

        RuntimeError

        Type Aliases

        CallOptions

        Functions

        ByID
        ByName
        Call
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Clipboard.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Clipboard.html new file mode 100644 index 000000000..1d15bfa14 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Clipboard.html @@ -0,0 +1 @@ +Clipboard | @wailsio/runtime

        Namespace Clipboard

        Functions

        SetText
        Text
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Dialogs.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Dialogs.html new file mode 100644 index 000000000..256f30a47 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Dialogs.html @@ -0,0 +1 @@ +Dialogs | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Events.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Events.html new file mode 100644 index 000000000..4f8d6b611 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Events.html @@ -0,0 +1 @@ +Events | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Flags.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Flags.html new file mode 100644 index 000000000..2004c8692 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Flags.html @@ -0,0 +1 @@ +Flags | @wailsio/runtime

        Namespace Flags

        Functions

        GetFlag
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/IOS.Device.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/IOS.Device.html new file mode 100644 index 000000000..53e87296b --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/IOS.Device.html @@ -0,0 +1 @@ +Device | @wailsio/runtime

        Interfaces

        Info

        Functions

        Info
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/IOS.Haptics.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/IOS.Haptics.html new file mode 100644 index 000000000..9ca587479 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/IOS.Haptics.html @@ -0,0 +1 @@ +Haptics | @wailsio/runtime

        Type Aliases

        ImpactStyle

        Functions

        Impact
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/IOS.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/IOS.html new file mode 100644 index 000000000..a55f035b1 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/IOS.html @@ -0,0 +1 @@ +IOS | @wailsio/runtime

        Namespace IOS

        Namespaces

        Device
        Haptics
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Screens.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Screens.html new file mode 100644 index 000000000..f25eaabb3 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Screens.html @@ -0,0 +1 @@ +Screens | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/System.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/System.html new file mode 100644 index 000000000..bc891a645 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/System.html @@ -0,0 +1 @@ +System | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/WML.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/WML.html new file mode 100644 index 000000000..e2d7d3912 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/WML.html @@ -0,0 +1 @@ +WML | @wailsio/runtime

        Namespace WML

        Functions

        Enable
        Reload
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/_internal_.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/_internal_.html new file mode 100644 index 000000000..3849b4822 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/_internal_.html @@ -0,0 +1 @@ +<internal> | @wailsio/runtime
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Call.CallOptions.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Call.CallOptions.html new file mode 100644 index 000000000..4ba71497e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Call.CallOptions.html @@ -0,0 +1,9 @@ +CallOptions | @wailsio/runtime

        Type Alias CallOptions

        CallOptions:
            | { args: any[]; methodID: number; methodName?: never }
            | { args: any[]; methodID?: never; methodName: string }

        Holds all required information for a binding call. +May provide either a method ID or a method name, but not both.

        +

        Type declaration

        • { args: any[]; methodID: number; methodName?: never }
          • args: any[]

            Arguments to be passed into the bound method.

            +
          • methodID: number

            The numeric ID of the bound method to call.

            +
          • OptionalmethodName?: never

            The fully qualified name of the bound method to call.

            +
        • { args: any[]; methodID?: never; methodName: string }
          • args: any[]

            Arguments to be passed into the bound method.

            +
          • OptionalmethodID?: never

            The numeric ID of the bound method to call.

            +
          • methodName: string

            The fully qualified name of the bound method to call.

            +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Events.WailsEventCallback.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Events.WailsEventCallback.html new file mode 100644 index 000000000..4848dc8e5 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Events.WailsEventCallback.html @@ -0,0 +1,2 @@ +WailsEventCallback | @wailsio/runtime

        Type Alias WailsEventCallback<E>

        WailsEventCallback: (ev: WailsEvent<E>) => void

        The type of handlers for a given event.

        +

        Type Parameters

        Type declaration

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Events.WailsEventData.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Events.WailsEventData.html new file mode 100644 index 000000000..a73367389 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Events.WailsEventData.html @@ -0,0 +1,2 @@ +WailsEventData | @wailsio/runtime

        Type Alias WailsEventData<E>

        WailsEventData: E extends keyof CustomEvents
            ? CustomEvents[E]
            : E extends SystemEventName ? void : any

        The data type associated to a given event.

        +

        Type Parameters

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Events.WailsEventName.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Events.WailsEventName.html new file mode 100644 index 000000000..7bbb4839b --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Events.WailsEventName.html @@ -0,0 +1,2 @@ +WailsEventName | @wailsio/runtime

        Type Alias WailsEventName<E>

        WailsEventName: E | string & {}

        Either a known event name or an arbitrary string.

        +

        Type Parameters

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/IOS.Haptics.ImpactStyle.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/IOS.Haptics.ImpactStyle.html new file mode 100644 index 000000000..e55fdb123 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/IOS.Haptics.ImpactStyle.html @@ -0,0 +1 @@ +ImpactStyle | @wailsio/runtime
        ImpactStyle: "light" | "medium" | "heavy" | "soft" | "rigid"
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Awaited.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Awaited.html new file mode 100644 index 000000000..7d51c3785 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Awaited.html @@ -0,0 +1,2 @@ +Awaited | @wailsio/runtime
        Awaited: T extends null
        | undefined
            ? T
            : T extends object & { then(onfulfilled: F, ...args: _): any }
                ? F extends (value: infer V, ...args: infer _) => any
                    ? Awaited<V>
                    : never
                : T

        Recursively unwraps the "awaited type" of a type. Non-promise "thenables" should resolve to never. This emulates the behavior of await.

        +

        Type Parameters

        • T
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.CancellablePromiseCanceller.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.CancellablePromiseCanceller.html new file mode 100644 index 000000000..3258a70d0 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.CancellablePromiseCanceller.html @@ -0,0 +1 @@ +CancellablePromiseCanceller | @wailsio/runtime

        Type Alias CancellablePromiseCanceller

        CancellablePromiseCanceller: (cause?: any) => void | PromiseLike<void>

        Type declaration

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.CancellablePromiseExecutor.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.CancellablePromiseExecutor.html new file mode 100644 index 000000000..85b0f8526 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.CancellablePromiseExecutor.html @@ -0,0 +1 @@ +CancellablePromiseExecutor | @wailsio/runtime

        Type Alias CancellablePromiseExecutor<T>

        CancellablePromiseExecutor: (
            resolve: CancellablePromiseResolver<T>,
            reject: CancellablePromiseRejector,
        ) => void

        Type Parameters

        • T

        Type declaration

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.CancellablePromiseRejector.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.CancellablePromiseRejector.html new file mode 100644 index 000000000..b75c628c3 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.CancellablePromiseRejector.html @@ -0,0 +1 @@ +CancellablePromiseRejector | @wailsio/runtime

        Type Alias CancellablePromiseRejector

        CancellablePromiseRejector: (reason?: any) => void

        Type declaration

          • (reason?: any): void
          • Parameters

            • Optionalreason: any

            Returns void

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.CancellablePromiseResolver.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.CancellablePromiseResolver.html new file mode 100644 index 000000000..81f2b806b --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.CancellablePromiseResolver.html @@ -0,0 +1 @@ +CancellablePromiseResolver | @wailsio/runtime

        Type Alias CancellablePromiseResolver<T>

        CancellablePromiseResolver: (
            value: T | PromiseLike<T> | CancellablePromiseLike<T>,
        ) => void

        Type Parameters

        • T

        Type declaration

        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Partial.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Partial.html new file mode 100644 index 000000000..c2c89e799 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Partial.html @@ -0,0 +1,2 @@ +Partial | @wailsio/runtime
        Partial: { [P in keyof T]?: T[P] }

        Make all properties in T optional

        +

        Type Parameters

        • T
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.PromiseSettledResult.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.PromiseSettledResult.html new file mode 100644 index 000000000..c71a7894b --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.PromiseSettledResult.html @@ -0,0 +1 @@ +PromiseSettledResult | @wailsio/runtime

        Type Alias PromiseSettledResult<T>

        Type Parameters

        • T
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Readonly.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Readonly.html new file mode 100644 index 000000000..3f66bd1ba --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Readonly.html @@ -0,0 +1,2 @@ +Readonly | @wailsio/runtime
        Readonly: { readonly [P in keyof T]: T[P] }

        Make all properties in T readonly

        +

        Type Parameters

        • T
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Record.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Record.html new file mode 100644 index 000000000..8930f14b9 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Record.html @@ -0,0 +1,2 @@ +Record | @wailsio/runtime

        Type Alias Record<K, T>

        Record: { [P in K]: T }

        Construct a type with a set of properties K of type T

        +

        Type Parameters

        • K extends keyof any
        • T
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.SystemEventName.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.SystemEventName.html new file mode 100644 index 000000000..5710c2b3e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.SystemEventName.html @@ -0,0 +1,2 @@ +SystemEventName | @wailsio/runtime
        SystemEventName: {
            [K in keyof typeof Types]: typeof Types[K][keyof typeof Types[K]]
        } extends infer M
            ? M[keyof M]
            : never

        Union of all known system event names.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Events.Types.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Events.Types.html new file mode 100644 index 000000000..564d38e84 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Events.Types.html @@ -0,0 +1 @@ +Types | @wailsio/runtime

        Variable TypesConst

        Types: Readonly<
            {
                Common: Readonly<
                    {
                        ApplicationLaunchedWithUrl: "common:ApplicationLaunchedWithUrl";
                        ApplicationOpenedWithFile: "common:ApplicationOpenedWithFile";
                        ApplicationStarted: "common:ApplicationStarted";
                        ThemeChanged: "common:ThemeChanged";
                        WindowClosing: "common:WindowClosing";
                        WindowDidMove: "common:WindowDidMove";
                        WindowDidResize: "common:WindowDidResize";
                        WindowDPIChanged: "common:WindowDPIChanged";
                        WindowFilesDropped: "common:WindowFilesDropped";
                        WindowFocus: "common:WindowFocus";
                        WindowFullscreen: "common:WindowFullscreen";
                        WindowHide: "common:WindowHide";
                        WindowLostFocus: "common:WindowLostFocus";
                        WindowMaximise: "common:WindowMaximise";
                        WindowMinimise: "common:WindowMinimise";
                        WindowRestore: "common:WindowRestore";
                        WindowRuntimeReady: "common:WindowRuntimeReady";
                        WindowShow: "common:WindowShow";
                        WindowToggleFrameless: "common:WindowToggleFrameless";
                        WindowUnFullscreen: "common:WindowUnFullscreen";
                        WindowUnMaximise: "common:WindowUnMaximise";
                        WindowUnMinimise: "common:WindowUnMinimise";
                        WindowZoom: "common:WindowZoom";
                        WindowZoomIn: "common:WindowZoomIn";
                        WindowZoomOut: "common:WindowZoomOut";
                        WindowZoomReset: "common:WindowZoomReset";
                    },
                >;
                iOS: Readonly<
                    {
                        ApplicationDidBecomeActive: "ios:ApplicationDidBecomeActive";
                        ApplicationDidEnterBackground: "ios:ApplicationDidEnterBackground";
                        ApplicationDidFinishLaunching: "ios:ApplicationDidFinishLaunching";
                        ApplicationDidReceiveMemoryWarning: "ios:ApplicationDidReceiveMemoryWarning";
                        ApplicationWillEnterForeground: "ios:ApplicationWillEnterForeground";
                        ApplicationWillResignActive: "ios:ApplicationWillResignActive";
                        ApplicationWillTerminate: "ios:ApplicationWillTerminate";
                        WebViewDecidePolicyForNavigationAction: "ios:WebViewDecidePolicyForNavigationAction";
                        WebViewDidFailNavigation: "ios:WebViewDidFailNavigation";
                        WebViewDidFinishNavigation: "ios:WebViewDidFinishNavigation";
                        WebViewDidStartNavigation: "ios:WebViewDidStartNavigation";
                        WindowDidAppear: "ios:WindowDidAppear";
                        WindowDidDisappear: "ios:WindowDidDisappear";
                        WindowDidLoad: "ios:WindowDidLoad";
                        WindowOrientationChanged: "ios:WindowOrientationChanged";
                        WindowSafeAreaInsetsChanged: "ios:WindowSafeAreaInsetsChanged";
                        WindowTouchBegan: "ios:WindowTouchBegan";
                        WindowTouchCancelled: "ios:WindowTouchCancelled";
                        WindowTouchEnded: "ios:WindowTouchEnded";
                        WindowTouchMoved: "ios:WindowTouchMoved";
                        WindowWillAppear: "ios:WindowWillAppear";
                        WindowWillDisappear: "ios:WindowWillDisappear";
                    },
                >;
                Linux: Readonly<
                    {
                        ApplicationStartup: "linux:ApplicationStartup";
                        SystemThemeChanged: "linux:SystemThemeChanged";
                        WindowDeleteEvent: "linux:WindowDeleteEvent";
                        WindowDidMove: "linux:WindowDidMove";
                        WindowDidResize: "linux:WindowDidResize";
                        WindowFocusIn: "linux:WindowFocusIn";
                        WindowFocusOut: "linux:WindowFocusOut";
                        WindowLoadCommitted: "linux:WindowLoadCommitted";
                        WindowLoadFinished: "linux:WindowLoadFinished";
                        WindowLoadRedirected: "linux:WindowLoadRedirected";
                        WindowLoadStarted: "linux:WindowLoadStarted";
                    },
                >;
                Mac: Readonly<
                    {
                        ApplicationDidBecomeActive: "mac:ApplicationDidBecomeActive";
                        ApplicationDidChangeBackingProperties: "mac:ApplicationDidChangeBackingProperties";
                        ApplicationDidChangeEffectiveAppearance: "mac:ApplicationDidChangeEffectiveAppearance";
                        ApplicationDidChangeIcon: "mac:ApplicationDidChangeIcon";
                        ApplicationDidChangeOcclusionState: "mac:ApplicationDidChangeOcclusionState";
                        ApplicationDidChangeScreenParameters: "mac:ApplicationDidChangeScreenParameters";
                        ApplicationDidChangeStatusBarFrame: "mac:ApplicationDidChangeStatusBarFrame";
                        ApplicationDidChangeStatusBarOrientation: "mac:ApplicationDidChangeStatusBarOrientation";
                        ApplicationDidChangeTheme: "mac:ApplicationDidChangeTheme";
                        ApplicationDidFinishLaunching: "mac:ApplicationDidFinishLaunching";
                        ApplicationDidHide: "mac:ApplicationDidHide";
                        ApplicationDidResignActive: "mac:ApplicationDidResignActive";
                        ApplicationDidUnhide: "mac:ApplicationDidUnhide";
                        ApplicationDidUpdate: "mac:ApplicationDidUpdate";
                        ApplicationShouldHandleReopen: "mac:ApplicationShouldHandleReopen";
                        ApplicationWillBecomeActive: "mac:ApplicationWillBecomeActive";
                        ApplicationWillFinishLaunching: "mac:ApplicationWillFinishLaunching";
                        ApplicationWillHide: "mac:ApplicationWillHide";
                        ApplicationWillResignActive: "mac:ApplicationWillResignActive";
                        ApplicationWillTerminate: "mac:ApplicationWillTerminate";
                        ApplicationWillUnhide: "mac:ApplicationWillUnhide";
                        ApplicationWillUpdate: "mac:ApplicationWillUpdate";
                        MenuDidAddItem: "mac:MenuDidAddItem";
                        MenuDidBeginTracking: "mac:MenuDidBeginTracking";
                        MenuDidClose: "mac:MenuDidClose";
                        MenuDidDisplayItem: "mac:MenuDidDisplayItem";
                        MenuDidEndTracking: "mac:MenuDidEndTracking";
                        MenuDidHighlightItem: "mac:MenuDidHighlightItem";
                        MenuDidOpen: "mac:MenuDidOpen";
                        MenuDidPopUp: "mac:MenuDidPopUp";
                        MenuDidRemoveItem: "mac:MenuDidRemoveItem";
                        MenuDidSendAction: "mac:MenuDidSendAction";
                        MenuDidSendActionToItem: "mac:MenuDidSendActionToItem";
                        MenuDidUpdate: "mac:MenuDidUpdate";
                        MenuWillAddItem: "mac:MenuWillAddItem";
                        MenuWillBeginTracking: "mac:MenuWillBeginTracking";
                        MenuWillDisplayItem: "mac:MenuWillDisplayItem";
                        MenuWillEndTracking: "mac:MenuWillEndTracking";
                        MenuWillHighlightItem: "mac:MenuWillHighlightItem";
                        MenuWillOpen: "mac:MenuWillOpen";
                        MenuWillPopUp: "mac:MenuWillPopUp";
                        MenuWillRemoveItem: "mac:MenuWillRemoveItem";
                        MenuWillSendAction: "mac:MenuWillSendAction";
                        MenuWillSendActionToItem: "mac:MenuWillSendActionToItem";
                        MenuWillUpdate: "mac:MenuWillUpdate";
                        WebViewDidCommitNavigation: "mac:WebViewDidCommitNavigation";
                        WebViewDidFinishNavigation: "mac:WebViewDidFinishNavigation";
                        WebViewDidReceiveServerRedirectForProvisionalNavigation: "mac:WebViewDidReceiveServerRedirectForProvisionalNavigation";
                        WebViewDidStartProvisionalNavigation: "mac:WebViewDidStartProvisionalNavigation";
                        WindowDidBecomeKey: "mac:WindowDidBecomeKey";
                        WindowDidBecomeMain: "mac:WindowDidBecomeMain";
                        WindowDidBeginSheet: "mac:WindowDidBeginSheet";
                        WindowDidChangeAlpha: "mac:WindowDidChangeAlpha";
                        WindowDidChangeBackingLocation: "mac:WindowDidChangeBackingLocation";
                        WindowDidChangeBackingProperties: "mac:WindowDidChangeBackingProperties";
                        WindowDidChangeCollectionBehavior: "mac:WindowDidChangeCollectionBehavior";
                        WindowDidChangeEffectiveAppearance: "mac:WindowDidChangeEffectiveAppearance";
                        WindowDidChangeOcclusionState: "mac:WindowDidChangeOcclusionState";
                        WindowDidChangeOrderingMode: "mac:WindowDidChangeOrderingMode";
                        WindowDidChangeScreen: "mac:WindowDidChangeScreen";
                        WindowDidChangeScreenParameters: "mac:WindowDidChangeScreenParameters";
                        WindowDidChangeScreenProfile: "mac:WindowDidChangeScreenProfile";
                        WindowDidChangeScreenSpace: "mac:WindowDidChangeScreenSpace";
                        WindowDidChangeScreenSpaceProperties: "mac:WindowDidChangeScreenSpaceProperties";
                        WindowDidChangeSharingType: "mac:WindowDidChangeSharingType";
                        WindowDidChangeSpace: "mac:WindowDidChangeSpace";
                        WindowDidChangeSpaceOrderingMode: "mac:WindowDidChangeSpaceOrderingMode";
                        WindowDidChangeTitle: "mac:WindowDidChangeTitle";
                        WindowDidChangeToolbar: "mac:WindowDidChangeToolbar";
                        WindowDidDeminiaturize: "mac:WindowDidDeminiaturize";
                        WindowDidEndSheet: "mac:WindowDidEndSheet";
                        WindowDidEnterFullScreen: "mac:WindowDidEnterFullScreen";
                        WindowDidEnterVersionBrowser: "mac:WindowDidEnterVersionBrowser";
                        WindowDidExitFullScreen: "mac:WindowDidExitFullScreen";
                        WindowDidExitVersionBrowser: "mac:WindowDidExitVersionBrowser";
                        WindowDidExpose: "mac:WindowDidExpose";
                        WindowDidFocus: "mac:WindowDidFocus";
                        WindowDidMiniaturize: "mac:WindowDidMiniaturize";
                        WindowDidMove: "mac:WindowDidMove";
                        WindowDidOrderOffScreen: "mac:WindowDidOrderOffScreen";
                        WindowDidOrderOnScreen: "mac:WindowDidOrderOnScreen";
                        WindowDidResignKey: "mac:WindowDidResignKey";
                        WindowDidResignMain: "mac:WindowDidResignMain";
                        WindowDidResize: "mac:WindowDidResize";
                        WindowDidUpdate: "mac:WindowDidUpdate";
                        WindowDidUpdateAlpha: "mac:WindowDidUpdateAlpha";
                        WindowDidUpdateCollectionBehavior: "mac:WindowDidUpdateCollectionBehavior";
                        WindowDidUpdateCollectionProperties: "mac:WindowDidUpdateCollectionProperties";
                        WindowDidUpdateShadow: "mac:WindowDidUpdateShadow";
                        WindowDidUpdateTitle: "mac:WindowDidUpdateTitle";
                        WindowDidUpdateToolbar: "mac:WindowDidUpdateToolbar";
                        WindowDidZoom: "mac:WindowDidZoom";
                        WindowFileDraggingEntered: "mac:WindowFileDraggingEntered";
                        WindowFileDraggingExited: "mac:WindowFileDraggingExited";
                        WindowFileDraggingPerformed: "mac:WindowFileDraggingPerformed";
                        WindowHide: "mac:WindowHide";
                        WindowMaximise: "mac:WindowMaximise";
                        WindowMinimise: "mac:WindowMinimise";
                        WindowShouldClose: "mac:WindowShouldClose";
                        WindowShow: "mac:WindowShow";
                        WindowUnMaximise: "mac:WindowUnMaximise";
                        WindowUnMinimise: "mac:WindowUnMinimise";
                        WindowWillBecomeKey: "mac:WindowWillBecomeKey";
                        WindowWillBecomeMain: "mac:WindowWillBecomeMain";
                        WindowWillBeginSheet: "mac:WindowWillBeginSheet";
                        WindowWillChangeOrderingMode: "mac:WindowWillChangeOrderingMode";
                        WindowWillClose: "mac:WindowWillClose";
                        WindowWillDeminiaturize: "mac:WindowWillDeminiaturize";
                        WindowWillEnterFullScreen: "mac:WindowWillEnterFullScreen";
                        WindowWillEnterVersionBrowser: "mac:WindowWillEnterVersionBrowser";
                        WindowWillExitFullScreen: "mac:WindowWillExitFullScreen";
                        WindowWillExitVersionBrowser: "mac:WindowWillExitVersionBrowser";
                        WindowWillFocus: "mac:WindowWillFocus";
                        WindowWillMiniaturize: "mac:WindowWillMiniaturize";
                        WindowWillMove: "mac:WindowWillMove";
                        WindowWillOrderOffScreen: "mac:WindowWillOrderOffScreen";
                        WindowWillOrderOnScreen: "mac:WindowWillOrderOnScreen";
                        WindowWillResignMain: "mac:WindowWillResignMain";
                        WindowWillResize: "mac:WindowWillResize";
                        WindowWillUnfocus: "mac:WindowWillUnfocus";
                        WindowWillUpdate: "mac:WindowWillUpdate";
                        WindowWillUpdateAlpha: "mac:WindowWillUpdateAlpha";
                        WindowWillUpdateCollectionBehavior: "mac:WindowWillUpdateCollectionBehavior";
                        WindowWillUpdateCollectionProperties: "mac:WindowWillUpdateCollectionProperties";
                        WindowWillUpdateShadow: "mac:WindowWillUpdateShadow";
                        WindowWillUpdateTitle: "mac:WindowWillUpdateTitle";
                        WindowWillUpdateToolbar: "mac:WindowWillUpdateToolbar";
                        WindowWillUpdateVisibility: "mac:WindowWillUpdateVisibility";
                        WindowWillUseStandardFrame: "mac:WindowWillUseStandardFrame";
                        WindowZoomIn: "mac:WindowZoomIn";
                        WindowZoomOut: "mac:WindowZoomOut";
                        WindowZoomReset: "mac:WindowZoomReset";
                    },
                >;
                Windows: Readonly<
                    {
                        APMPowerSettingChange: "windows:APMPowerSettingChange";
                        APMPowerStatusChange: "windows:APMPowerStatusChange";
                        APMResumeAutomatic: "windows:APMResumeAutomatic";
                        APMResumeSuspend: "windows:APMResumeSuspend";
                        APMSuspend: "windows:APMSuspend";
                        ApplicationStarted: "windows:ApplicationStarted";
                        SystemThemeChanged: "windows:SystemThemeChanged";
                        WebViewNavigationCompleted: "windows:WebViewNavigationCompleted";
                        WindowActive: "windows:WindowActive";
                        WindowBackgroundErase: "windows:WindowBackgroundErase";
                        WindowClickActive: "windows:WindowClickActive";
                        WindowClosing: "windows:WindowClosing";
                        WindowDidMove: "windows:WindowDidMove";
                        WindowDidResize: "windows:WindowDidResize";
                        WindowDPIChanged: "windows:WindowDPIChanged";
                        WindowDragDrop: "windows:WindowDragDrop";
                        WindowDragEnter: "windows:WindowDragEnter";
                        WindowDragLeave: "windows:WindowDragLeave";
                        WindowDragOver: "windows:WindowDragOver";
                        WindowEndMove: "windows:WindowEndMove";
                        WindowEndResize: "windows:WindowEndResize";
                        WindowFullscreen: "windows:WindowFullscreen";
                        WindowHide: "windows:WindowHide";
                        WindowInactive: "windows:WindowInactive";
                        WindowKeyDown: "windows:WindowKeyDown";
                        WindowKeyUp: "windows:WindowKeyUp";
                        WindowKillFocus: "windows:WindowKillFocus";
                        WindowMaximise: "windows:WindowMaximise";
                        WindowMinimise: "windows:WindowMinimise";
                        WindowNonClientHit: "windows:WindowNonClientHit";
                        WindowNonClientMouseDown: "windows:WindowNonClientMouseDown";
                        WindowNonClientMouseLeave: "windows:WindowNonClientMouseLeave";
                        WindowNonClientMouseMove: "windows:WindowNonClientMouseMove";
                        WindowNonClientMouseUp: "windows:WindowNonClientMouseUp";
                        WindowPaint: "windows:WindowPaint";
                        WindowRestore: "windows:WindowRestore";
                        WindowSetFocus: "windows:WindowSetFocus";
                        WindowShow: "windows:WindowShow";
                        WindowStartMove: "windows:WindowStartMove";
                        WindowStartResize: "windows:WindowStartResize";
                        WindowUnFullscreen: "windows:WindowUnFullscreen";
                        WindowUnMaximise: "windows:WindowUnMaximise";
                        WindowUnMinimise: "windows:WindowUnMinimise";
                        WindowZOrderChanged: "windows:WindowZOrderChanged";
                    },
                >;
            },
        > = ...
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Window.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Window.html new file mode 100644 index 000000000..8b7134654 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Window.html @@ -0,0 +1,2 @@ +Window | @wailsio/runtime

        Variable WindowConst

        Window: Window = ...

        The window within which the script is running.

        +
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/clientId.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/clientId.html new file mode 100644 index 000000000..b1673b468 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/clientId.html @@ -0,0 +1 @@ +clientId | @wailsio/runtime

        Variable clientId

        clientId: string = ...
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/objectNames.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/objectNames.html new file mode 100644 index 000000000..110e6e600 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/objectNames.html @@ -0,0 +1 @@ +objectNames | @wailsio/runtime

        Variable objectNamesConst

        objectNames: Readonly<
            {
                Application: 2;
                Browser: 9;
                Call: 0;
                CancelCall: 10;
                Clipboard: 1;
                ContextMenu: 4;
                Dialog: 5;
                Events: 3;
                IOS: 11;
                Screens: 7;
                System: 8;
                Window: 6;
            },
        > = ...
        diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json b/v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json new file mode 100644 index 000000000..6935b38ac --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json @@ -0,0 +1,3093 @@ +{ + "name": "@wailsio/runtime", + "version": "3.0.0-alpha.79", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@wailsio/runtime", + "version": "3.0.0-alpha.79", + "license": "MIT", + "devDependencies": { + "happy-dom": "^17.1.1", + "promises-aplus-tests": "2.1.2", + "rimraf": "^5.0.5", + "typedoc": "^0.27.7", + "typedoc-plugin-markdown": "^4.4.2", + "typedoc-plugin-mdn-links": "^4.0.13", + "typedoc-plugin-missing-exports": "^3.1.0", + "typescript": "^5.7.3", + "vite": "^5.2.0", + "vitest": "^3.0.6" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@gerrit0/mini-shiki": { + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-1.27.2.tgz", + "integrity": "sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-oniguruma": "^1.27.2", + "@shikijs/types": "^1.27.2", + "@shikijs/vscode-textmate": "^10.0.1" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz", + "integrity": "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.9.tgz", + "integrity": "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", + "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", + "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.9.tgz", + "integrity": "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.9.tgz", + "integrity": "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.9.tgz", + "integrity": "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.9.tgz", + "integrity": "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", + "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", + "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.9.tgz", + "integrity": "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.9.tgz", + "integrity": "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.9.tgz", + "integrity": "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.9.tgz", + "integrity": "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", + "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", + "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", + "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.9.tgz", + "integrity": "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", + "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", + "integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1" + } + }, + "node_modules/@shikijs/types": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz", + "integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.1", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" + } + }, + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "dev": true, + "license": "(Unlicense OR Apache-2.0)" + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.7.tgz", + "integrity": "sha512-QP25f+YJhzPfHrHfYHtvRn+uvkCFCqFtW9CktfBxmB+25QqWsx7VB2As6f4GmwllHLDhXNHvqedwhvMmSnNmjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.0.7", + "@vitest/utils": "3.0.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.7.tgz", + "integrity": "sha512-qui+3BLz9Eonx4EAuR/i+QlCX6AUZ35taDQgwGkK/Tw6/WgwodSrjN1X2xf69IA/643ZX5zNKIn2svvtZDrs4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.0.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.7.tgz", + "integrity": "sha512-CiRY0BViD/V8uwuEzz9Yapyao+M9M008/9oMOSQydwbwb+CMokEq3XVaF3XK/VWaOK0Jm9z7ENhybg70Gtxsmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.7.tgz", + "integrity": "sha512-WeEl38Z0S2ZcuRTeyYqaZtm4e26tq6ZFqh5y8YD9YxfWuu0OFiGFUbnxNynwLjNRHPsXyee2M9tV7YxOTPZl2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.0.7", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.7.tgz", + "integrity": "sha512-eqTUryJWQN0Rtf5yqCGTQWsCFOQe4eNz5Twsu21xYEcnFJtMU5XvmG0vgebhdLlrHQTSq5p8vWHJIeJQV8ovsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.0.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.7.tgz", + "integrity": "sha512-4T4WcsibB0B6hrKdAZTM37ekuyFZt2cGbEGd2+L0P8ov15J1/HUsUaqkXEQPNAWr4BtPPe1gI+FYfMHhEKfR8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.7.tgz", + "integrity": "sha512-xePVpCRfooFX3rANQjwoditoXgWb1MaFbzmGuPP59MK6i13mrnDw/yEIyJudLeW6/38mCNcwCiJIGmpDPibAIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.0.7", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.0.tgz", + "integrity": "sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/happy-dom": { + "version": "17.1.9", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.1.9.tgz", + "integrity": "sha512-HL26ajjMVe/wr3xlzjF0sCPCiAKaZJcIRFZHmG4yKHRJp4YAkHPG5X6GfWxCeDTpOmuHhNiOyNKUoZjjnm0tjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "webidl-conversions": "^7.0.0", + "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mocha": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", + "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", + "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.1", + "@sinonjs/text-encoding": "^0.7.3", + "just-extend": "^6.2.0", + "path-to-regexp": "^8.1.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/promises-aplus-tests": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/promises-aplus-tests/-/promises-aplus-tests-2.1.2.tgz", + "integrity": "sha512-XiDfjQqx+rHLof8CU9xPOMLsjiXXxr3fkjE7WJjUzXttffB8K/nsnNsPTcwS4VvHliSjGVsYVqIjFeTHw53f5w==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "mocha": "^2.5.3", + "sinon": "^1.10.3", + "underscore": "~1.8.3" + }, + "bin": { + "promises-aplus-tests": "lib/cli.js" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.9.tgz", + "integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.34.9", + "@rollup/rollup-android-arm64": "4.34.9", + "@rollup/rollup-darwin-arm64": "4.34.9", + "@rollup/rollup-darwin-x64": "4.34.9", + "@rollup/rollup-freebsd-arm64": "4.34.9", + "@rollup/rollup-freebsd-x64": "4.34.9", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.9", + "@rollup/rollup-linux-arm-musleabihf": "4.34.9", + "@rollup/rollup-linux-arm64-gnu": "4.34.9", + "@rollup/rollup-linux-arm64-musl": "4.34.9", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.9", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.9", + "@rollup/rollup-linux-riscv64-gnu": "4.34.9", + "@rollup/rollup-linux-s390x-gnu": "4.34.9", + "@rollup/rollup-linux-x64-gnu": "4.34.9", + "@rollup/rollup-linux-x64-musl": "4.34.9", + "@rollup/rollup-win32-arm64-msvc": "4.34.9", + "@rollup/rollup-win32-ia32-msvc": "4.34.9", + "@rollup/rollup-win32-x64-msvc": "4.34.9", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sinon": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", + "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.2", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "nise": "^6.1.1", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.1.tgz", + "integrity": "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/typedoc": { + "version": "0.27.9", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.9.tgz", + "integrity": "sha512-/z585740YHURLl9DN2jCWe6OW7zKYm6VoQ93H0sxZ1cwHQEQrUn5BJrEnkWhfzUdyO+BLGjnKUZ9iz9hKloFDw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gerrit0/mini-shiki": "^1.24.0", + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "yaml": "^2.6.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x" + } + }, + "node_modules/typedoc-plugin-markdown": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.4.2.tgz", + "integrity": "sha512-kJVkU2Wd+AXQpyL6DlYXXRrfNrHrEIUgiABWH8Z+2Lz5Sq6an4dQ/hfvP75bbokjNDUskOdFlEEm/0fSVyC7eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typedoc": "0.27.x" + } + }, + "node_modules/typedoc-plugin-mdn-links": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-4.0.15.tgz", + "integrity": "sha512-ZdZLBVMSJzHckVJt902j5xIbujza5Z8l802sXd6G38bOZS6R+ZkjB6RmSdYdKlxiEle8Yqjxn4lZ2NZIeUe2lA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typedoc": "0.26.x || 0.27.x" + } + }, + "node_modules/typedoc-plugin-missing-exports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-3.1.0.tgz", + "integrity": "sha512-Sogbaj+qDa21NjB3SlIw4JXSwmcl/WOjwiPNaVEcPhpNG/MiRTtpwV81cT7h1cbu9StpONFPbddYWR0KV/fTWA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typedoc": "0.26.x || 0.27.x" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.7.tgz", + "integrity": "sha512-2fX0QwX4GkkkpULXdT1Pf4q0tC1i1lFOyseKoonavXUNlQ77KpW2XqBGGNIm/J4Ows4KxgGJzDguYVPKwG/n5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.7.tgz", + "integrity": "sha512-IP7gPK3LS3Fvn44x30X1dM9vtawm0aesAa2yBIZ9vQf+qB69NXC5776+Qmcr7ohUXIQuLhk7xQR0aSUIDPqavg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "3.0.7", + "@vitest/mocker": "3.0.7", + "@vitest/pretty-format": "^3.0.7", + "@vitest/runner": "3.0.7", + "@vitest/snapshot": "3.0.7", + "@vitest/spy": "3.0.7", + "@vitest/utils": "3.0.7", + "chai": "^5.2.0", + "debug": "^4.4.0", + "expect-type": "^1.1.0", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.0.7", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.0.7", + "@vitest/ui": "3.0.7", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/package.json b/v3/internal/runtime/desktop/@wailsio/runtime/package.json new file mode 100644 index 000000000..5e5dcb262 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/package.json @@ -0,0 +1,69 @@ +{ + "name": "@wailsio/runtime", + "type": "module", + "version": "3.0.0-alpha.79", + "description": "Wails Runtime", + "types": "types/index.d.ts", + "exports": { + ".": { + "types": "./types/index.d.ts", + "default": "./dist/index.js" + }, + "./plugins/*": { + "types": "./types/plugins/*.d.ts", + "default": "./dist/plugins/*.js" + } + }, + "repository": { + "type": "git", + "url": "git+https://github.com/wailsapp/wails.git", + "directory": "v3/internal/runtime/desktop/@wailsio/runtime" + }, + "author": "The Wails Team", + "license": "MIT", + "homepage": "https://v3.wails.io", + "bugs": { + "url": "https://github.com/wailsapp/wails/issues" + }, + "files": [ + "./dist", + "./types" + ], + "sideEffects": [ + "./dist/index.js", + "./dist/contextmenu.js", + "./dist/drag.js" + ], + "scripts": { + "check": "npx tsc --noEmit", + "test": "npx vitest run", + "clean": "npx rimraf ./dist ./docs ./types ./tsconfig.tsbuildinfo", + "generate:events": "task generate:events", + "generate": "npm run generate:events", + "prebuild": "npm run clean && npm run generate", + "build:code": "npx tsc", + "build:docs": "npx typedoc --gitRevision v3-alpha --plugin typedoc-plugin-mdn-links --plugin typedoc-plugin-missing-exports ./src/index.ts", + "build:docs:md": "npx typedoc --gitRevision v3-alpha --plugin typedoc-plugin-markdown --plugin typedoc-plugin-mdn-links --plugin typedoc-plugin-missing-exports ./src/index.ts", + "build": "npm run build:code & npm run build:docs & wait", + "prepack": "npm run build" + }, + "devDependencies": { + "happy-dom": "^17.1.1", + "promises-aplus-tests": "2.1.2", + "rimraf": "^5.0.5", + "typedoc": "^0.27.7", + "typedoc-plugin-markdown": "^4.4.2", + "typedoc-plugin-mdn-links": "^4.0.13", + "typedoc-plugin-missing-exports": "^3.1.0", + "typescript": "^5.7.3", + "vite": "^5.2.0", + "vitest": "^3.0.6" + }, + "overrides": { + "promises-aplus-tests": { + "mocha": "^11.1.0", + "sinon": "^19.0.2", + "underscore": "^1.13.7" + } + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/application.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/application.ts new file mode 100644 index 000000000..57a41ac9e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/application.ts @@ -0,0 +1,37 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { newRuntimeCaller, objectNames } from "./runtime.js"; +const call = newRuntimeCaller(objectNames.Application); + +const HideMethod = 0; +const ShowMethod = 1; +const QuitMethod = 2; + +/** + * Hides a certain method by calling the HideMethod function. + */ +export function Hide(): Promise { + return call(HideMethod); +} + +/** + * Calls the ShowMethod and returns the result. + */ +export function Show(): Promise { + return call(ShowMethod); +} + +/** + * Calls the QuitMethod to terminate the program. + */ +export function Quit(): Promise { + return call(QuitMethod); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/browser.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/browser.ts new file mode 100644 index 000000000..465310d3d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/browser.ts @@ -0,0 +1,24 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { newRuntimeCaller, objectNames } from "./runtime.js"; + +const call = newRuntimeCaller(objectNames.Browser); + +const BrowserOpenURL = 0; + +/** + * Open a browser window to the given URL. + * + * @param url - The URL to open + */ +export function OpenURL(url: string | URL): Promise { + return call(BrowserOpenURL, {url: url.toString()}); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/callable.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/callable.ts new file mode 100644 index 000000000..e8e2e4087 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/callable.ts @@ -0,0 +1,125 @@ +// Source: https://github.com/inspect-js/is-callable + +// The MIT License (MIT) +// +// Copyright (c) 2015 Jordan Harband +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +var fnToStr = Function.prototype.toString; +var reflectApply: typeof Reflect.apply | false | null = typeof Reflect === 'object' && Reflect !== null && Reflect.apply; +var badArrayLike: any; +var isCallableMarker: any; +if (typeof reflectApply === 'function' && typeof Object.defineProperty === 'function') { + try { + badArrayLike = Object.defineProperty({}, 'length', { + get: function () { + throw isCallableMarker; + } + }); + isCallableMarker = {}; + // eslint-disable-next-line no-throw-literal + reflectApply(function () { throw 42; }, null, badArrayLike); + } catch (_) { + if (_ !== isCallableMarker) { + reflectApply = null; + } + } +} else { + reflectApply = null; +} + +var constructorRegex = /^\s*class\b/; +var isES6ClassFn = function isES6ClassFunction(value: any): boolean { + try { + var fnStr = fnToStr.call(value); + return constructorRegex.test(fnStr); + } catch (e) { + return false; // not a function + } +}; + +var tryFunctionObject = function tryFunctionToStr(value: any): boolean { + try { + if (isES6ClassFn(value)) { return false; } + fnToStr.call(value); + return true; + } catch (e) { + return false; + } +}; +var toStr = Object.prototype.toString; +var objectClass = '[object Object]'; +var fnClass = '[object Function]'; +var genClass = '[object GeneratorFunction]'; +var ddaClass = '[object HTMLAllCollection]'; // IE 11 +var ddaClass2 = '[object HTML document.all class]'; +var ddaClass3 = '[object HTMLCollection]'; // IE 9-10 +var hasToStringTag = typeof Symbol === 'function' && !!Symbol.toStringTag; // better: use `has-tostringtag` + +var isIE68 = !(0 in [,]); // eslint-disable-line no-sparse-arrays, comma-spacing + +var isDDA: (value: any) => boolean = function isDocumentDotAll() { return false; }; +if (typeof document === 'object') { + // Firefox 3 canonicalizes DDA to undefined when it's not accessed directly + var all = document.all; + if (toStr.call(all) === toStr.call(document.all)) { + isDDA = function isDocumentDotAll(value) { + /* globals document: false */ + // in IE 6-8, typeof document.all is "object" and it's truthy + if ((isIE68 || !value) && (typeof value === 'undefined' || typeof value === 'object')) { + try { + var str = toStr.call(value); + return ( + str === ddaClass + || str === ddaClass2 + || str === ddaClass3 // opera 12.16 + || str === objectClass // IE 6-8 + ) && value('') == null; // eslint-disable-line eqeqeq + } catch (e) { /**/ } + } + return false; + }; + } +} + +function isCallableRefApply(value: T | unknown): value is (...args: any[]) => any { + if (isDDA(value)) { return true; } + if (!value) { return false; } + if (typeof value !== 'function' && typeof value !== 'object') { return false; } + try { + (reflectApply as any)(value, null, badArrayLike); + } catch (e) { + if (e !== isCallableMarker) { return false; } + } + return !isES6ClassFn(value) && tryFunctionObject(value); +} + +function isCallableNoRefApply(value: T | unknown): value is (...args: any[]) => any { + if (isDDA(value)) { return true; } + if (!value) { return false; } + if (typeof value !== 'function' && typeof value !== 'object') { return false; } + if (hasToStringTag) { return tryFunctionObject(value); } + if (isES6ClassFn(value)) { return false; } + var strClass = toStr.call(value); + if (strClass !== fnClass && strClass !== genClass && !(/^\[object HTML/).test(strClass)) { return false; } + return tryFunctionObject(value); +}; + +export default reflectApply ? isCallableRefApply : isCallableNoRefApply; diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/calls.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/calls.ts new file mode 100644 index 000000000..c80529857 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/calls.ts @@ -0,0 +1,147 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { CancellablePromise, type CancellablePromiseWithResolvers } from "./cancellable.js"; +import { newRuntimeCaller, objectNames } from "./runtime.js"; +import { nanoid } from "./nanoid.js"; + +// Setup +window._wails = window._wails || {}; + +type PromiseResolvers = Omit, "promise" | "oncancelled"> + +const call = newRuntimeCaller(objectNames.Call); +const cancelCall = newRuntimeCaller(objectNames.CancelCall); +const callResponses = new Map(); + +const CallBinding = 0; +const CancelMethod = 0 + +/** + * Holds all required information for a binding call. + * May provide either a method ID or a method name, but not both. + */ +export type CallOptions = { + /** The numeric ID of the bound method to call. */ + methodID: number; + /** The fully qualified name of the bound method to call. */ + methodName?: never; + /** Arguments to be passed into the bound method. */ + args: any[]; +} | { + /** The numeric ID of the bound method to call. */ + methodID?: never; + /** The fully qualified name of the bound method to call. */ + methodName: string; + /** Arguments to be passed into the bound method. */ + args: any[]; +}; + +/** + * Exception class that will be thrown in case the bound method returns an error. + * The value of the {@link RuntimeError#name} property is "RuntimeError". + */ +export class RuntimeError extends Error { + /** + * Constructs a new RuntimeError instance. + * @param message - The error message. + * @param options - Options to be forwarded to the Error constructor. + */ + constructor(message?: string, options?: ErrorOptions) { + super(message, options); + this.name = "RuntimeError"; + } +} + +/** + * Generates a unique ID using the nanoid library. + * + * @returns A unique ID that does not exist in the callResponses set. + */ +function generateID(): string { + let result; + do { + result = nanoid(); + } while (callResponses.has(result)); + return result; +} + +/** + * Call a bound method according to the given call options. + * + * In case of failure, the returned promise will reject with an exception + * among ReferenceError (unknown method), TypeError (wrong argument count or type), + * {@link RuntimeError} (method returned an error), or other (network or internal errors). + * The exception might have a "cause" field with the value returned + * by the application- or service-level error marshaling functions. + * + * @param options - A method call descriptor. + * @returns The result of the call. + */ +export function Call(options: CallOptions): CancellablePromise { + const id = generateID(); + + const result = CancellablePromise.withResolvers(); + callResponses.set(id, { resolve: result.resolve, reject: result.reject }); + + const request = call(CallBinding, Object.assign({ "call-id": id }, options)); + let running = true; + + request.then((res) => { + running = false; + callResponses.delete(id); + result.resolve(res); + }, (err) => { + running = false; + callResponses.delete(id); + result.reject(err); + }); + + const cancel = () => { + callResponses.delete(id); + return cancelCall(CancelMethod, {"call-id": id}).catch((err) => { + console.error("Error while requesting binding call cancellation:", err); + }); + }; + + result.oncancelled = () => { + if (running) { + return cancel(); + } else { + return request.then(cancel); + } + }; + + return result.promise; +} + +/** + * Calls a bound method by name with the specified arguments. + * See {@link Call} for details. + * + * @param methodName - The name of the method in the format 'package.struct.method'. + * @param args - The arguments to pass to the method. + * @returns The result of the method call. + */ +export function ByName(methodName: string, ...args: any[]): CancellablePromise { + return Call({ methodName, args }); +} + +/** + * Calls a method by its numeric ID with the specified arguments. + * See {@link Call} for details. + * + * @param methodID - The ID of the method to call. + * @param args - The arguments to pass to the method. + * @return The result of the method call. + */ +export function ByID(methodID: number, ...args: any[]): CancellablePromise { + return Call({ methodID, args }); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.test.js b/v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.test.js new file mode 100644 index 000000000..b618c1459 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.test.js @@ -0,0 +1,431 @@ +import * as util from "node:util"; +import { describe, it, beforeEach, afterEach, assert, expect, vi } from "vitest"; +import { CancelError, CancellablePromise, CancelledRejectionError } from "./cancellable"; + +// TODO: In order of importance: +// TODO: test cancellation of subpromises the main promise resolves to. +// TODO: test cancellation of promise chains built by calling then() and friends: +// - all promises up the chain should be cancelled; +// - rejection handlers should be always executed with the CancelError of their parent promise in the chain; +// - promises returned from rejection handlers should be cancelled too; +// - if a rejection handler throws or returns a promise that ultimately rejects, +// it should be reported as an unhandled rejection, +// - unless it is a CancelError with the same reason given for cancelling the returned promise. +// TODO: test multiple calls to cancel() (second and later should have no effect). +// TODO: test static factory methods and their cancellation support. + +let expectedUnhandled = new Map(); + +process.on('unhandledRejection', function (error, promise) { + let reason = error; + if (reason instanceof CancelledRejectionError) { + promise = reason.promise; + reason = reason.cause; + } + + let reasons = expectedUnhandled.get(promise); + const callbacks = reasons?.get(reason); + if (callbacks) { + for (const cb of callbacks) { + try { + cb(reason, promise); + } catch (e) { + console.error("Exception in unhandled rejection callback.", e); + } + } + + reasons.delete(reason); + if (reasons.size === 0) { + expectedUnhandled.delete(promise); + } + return; + } + + console.log(util.format("Unhandled rejection.\nReason: %o\nPromise: %o", reason, promise)); + throw error; +}); + +function ignoreUnhandled(reason, promise) { + expectUnhandled(reason, promise, null); +} + +function expectUnhandled(reason, promise, cb) { + let reasons = expectedUnhandled.get(promise); + if (!reasons) { + reasons = new Map(); + expectedUnhandled.set(promise, reasons); + } + let callbacks = reasons.get(reason); + if (!callbacks) { + callbacks = []; + reasons.set(reason, callbacks); + } + if (cb) { + callbacks.push(cb); + } +} + +afterEach(() => { + vi.resetAllMocks(); + vi.restoreAllMocks(); +}); + +const dummyValue = { value: "value" }; +const dummyCause = { dummy: "dummy" }; +const dummyError = new Error("dummy"); +const oncancelled = vi.fn().mockName("oncancelled"); +const sentinel = vi.fn().mockName("sentinel"); +const unhandled = vi.fn().mockName("unhandled"); + +const resolutionPatterns = [ + ["forever", "pending", (test, value, { cls = CancellablePromise, cb = oncancelled } = {}) => test( + new cls(() => {}, cb) + )], + ["already", "fulfilled", (test, value, { cls = CancellablePromise, cb = oncancelled } = {}) => { + const prw = cls.withResolvers(); + prw.oncancelled = cb; + prw.resolve(value ?? dummyValue); + return test(prw.promise); + }], + ["immediately", "fulfilled", (test, value, { cls = CancellablePromise, cb = oncancelled } = {}) => { + const prw = cls.withResolvers(); + prw.oncancelled = cb; + const tp = test(prw.promise); + prw.resolve(value ?? dummyValue); + return tp; + }], + ["eventually", "fulfilled", async (test, value, { cls = CancellablePromise, cb = oncancelled } = {}) => { + const prw = cls.withResolvers(); + prw.oncancelled = cb; + const tp = test(prw.promise); + await new Promise((resolve) => { + setTimeout(() => { + prw.resolve(value ?? dummyValue); + resolve(); + }, 50); + }); + return tp; + }], + ["already", "rejected", (test, reason, { cls = CancellablePromise, cb = oncancelled } = {}) => { + const prw = cls.withResolvers(); + prw.oncancelled = cb; + prw.reject(reason ?? dummyError); + return test(prw.promise); + }], + ["immediately", "rejected", (test, reason, { cls = CancellablePromise, cb = oncancelled } = {}) => { + const prw = cls.withResolvers(); + prw.oncancelled = cb; + const tp = test(prw.promise); + prw.reject(reason ?? dummyError); + return tp; + }], + ["eventually", "rejected", async (test, reason, { cls = CancellablePromise, cb = oncancelled } = {}) => { + const prw = cls.withResolvers(); + prw.oncancelled = cb; + const tp = test(prw.promise); + await new Promise((resolve) => { + setTimeout(() => { + prw.reject(reason ?? dummyError); + resolve(); + }, 50); + }); + return tp; + }], +]; + +describe("CancellablePromise.cancel", ()=> { + it("should suppress its own unhandled cancellation error", async () => { + const p = new CancellablePromise(() => {}); + p.cancel(); + + process.on('unhandledRejection', sentinel); + await new Promise((resolve) => setTimeout(resolve, 100)); + process.off('unhandledRejection', sentinel); + + expect(sentinel).not.toHaveBeenCalled(); + }); + + it.for([ + ["rejections", dummyError], + ["cancellation errors", new CancelError("dummy", { cause: dummyCause })], + ])("should not suppress arbitrary unhandled %s", async ([kind, err]) => { + const p = new CancellablePromise(() => { throw err; }); + p.cancel(); + + await new Promise((resolve) => { + expectUnhandled(err, p, unhandled); + expectUnhandled(err, p, resolve); + }); + + expect(unhandled).toHaveBeenCalledExactlyOnceWith(err, p); + }); + + describe.for(resolutionPatterns)("when applied to %s %s promises", ([time, state, test]) => { + if (time === "already") { + it("should have no effect", () => test(async (promise) => { + promise.then(sentinel, sentinel); + + let reason; + try { + promise.cancel(); + await promise; + assert(state === "fulfilled", "Promise fulfilled unexpectedly"); + } catch (err) { + reason = err; + assert(state === "rejected", "Promise rejected unexpectedly"); + } + + expect(sentinel).toHaveBeenCalled(); + expect(oncancelled).not.toHaveBeenCalled(); + expect(reason).not.toBeInstanceOf(CancelError); + })); + } else { + if (state === "rejected") { + it("should report late rejections as unhandled", () => test(async (promise) => { + promise.cancel(); + + await new Promise((resolve) => { + expectUnhandled(dummyError, promise, unhandled); + expectUnhandled(dummyError, promise, resolve); + }); + + expect(unhandled).toHaveBeenCalledExactlyOnceWith(dummyError, promise); + })); + } + + it("should reject with a CancelError", () => test(async (promise) => { + // Ignore the unhandled rejection from the test promise. + if (state === "rejected") { ignoreUnhandled(dummyError, promise); } + + let reason; + try { + promise.cancel(); + await promise; + } catch (err) { + reason = err; + } + + expect(reason).toBeInstanceOf(CancelError); + })); + + it("should call the oncancelled callback synchronously", () => test(async (promise) => { + // Ignore the unhandled rejection from the test promise. + if (state === "rejected") { ignoreUnhandled(dummyError, promise); } + + try { + promise.cancel(); + sentinel(); + await promise; + } catch {} + + expect(oncancelled).toHaveBeenCalledBefore(sentinel); + })); + + it("should propagate the given cause", () => test(async (promise) => { + // Ignore the unhandled rejection from the test promise. + if (state === "rejected") { ignoreUnhandled(dummyError, promise); } + + let reason; + try { + promise.cancel(dummyCause); + await promise; + } catch (err) { + reason = err; + } + + expect(reason).toBeInstanceOf(CancelError); + expect(reason).toHaveProperty('cause', dummyCause); + expect(oncancelled).toHaveBeenCalledWith(reason.cause); + })); + } + }); +}); + +const onabort = vi.fn().mockName("abort"); + +const abortPatterns = [ + ["never", "standalone", (test) => { + const signal = new AbortSignal(); + signal.addEventListener('abort', onabort, { capture: true }); + return test(signal); + }], + ["already", "standalone", (test) => { + const signal = AbortSignal.abort(dummyCause); + onabort(); + return test(signal); + }], + ["eventually", "standalone", (test) => { + const signal = AbortSignal.timeout(25); + signal.addEventListener('abort', onabort, { capture: true }); + return test(signal); + }], + ["never", "controller-bound", (test) => { + const signal = new AbortController().signal; + signal.addEventListener('abort', onabort, { capture: true }); + return test(signal); + }], + ["already", " controller-bound", (test) => { + const ctrl = new AbortController(); + ctrl.signal.addEventListener('abort', onabort, { capture: true }); + ctrl.abort(dummyCause); + return test(ctrl.signal); + }], + ["immediately", "controller-bound", (test) => { + const ctrl = new AbortController(); + ctrl.signal.addEventListener('abort', onabort, { capture: true }); + const tp = test(ctrl.signal); + ctrl.abort(dummyCause); + return tp; + }], + ["eventually", "controller-bound", (test) => { + const ctrl = new AbortController(); + ctrl.signal.addEventListener('abort', onabort, { capture: true }); + const tp = test(ctrl.signal); + setTimeout(() => ctrl.abort(dummyCause), 25); + return tp; + }] +]; + +describe("CancellablePromise.cancelOn", ()=> { + it("should return the target promise for chaining", () => { + const p = new CancellablePromise(() => {}); + expect(p.cancelOn(AbortSignal.abort())).toBe(p); + }); + + function tests(abortTime, mode, testSignal, resolveTime, state, testPromise) { + if (abortTime !== "never") { + it(`should call CancellablePromise.cancel ${abortTime === "already" ? "immediately" : "on abort"} with the abort reason as cause`, () => testSignal((signal) => testPromise(async (promise) => { + // Ignore the unhandled rejection from the test promise. + if (state === "rejected") { ignoreUnhandled(dummyError, promise); } + + const cancelSpy = vi.spyOn(promise, 'cancel'); + + promise.catch(() => {}); + promise.cancelOn(signal); + + if (signal.aborted) { + sentinel(); + } else { + await new Promise((resolve) => { + signal.onabort = () => { + sentinel(); + resolve(); + }; + }); + } + + expect(cancelSpy).toHaveBeenCalledAfter(onabort); + expect(cancelSpy).toHaveBeenCalledBefore(sentinel); + expect(cancelSpy).toHaveBeenCalledExactlyOnceWith(signal.reason); + }))); + } + + if ( + resolveTime === "already" + || abortTime === "never" + || ( + ["immediately", "eventually"].includes(abortTime) + && ["already", "immediately"].includes(resolveTime) + ) + ) { + it("should have no effect", () => testSignal((signal) => testPromise(async (promise) => { + promise.then(sentinel, sentinel); + + let reason; + try { + if (resolveTime !== "forever") { + await promise.cancelOn(signal); + assert(state === "fulfilled", "Promise fulfilled unexpectedly"); + } else { + await Promise.race([promise, new Promise((resolve) => setTimeout(resolve, 100))]).then(sentinel); + } + } catch (err) { + reason = err; + assert(state === "rejected", "Promise rejected unexpectedly"); + } + + if (abortTime !== "never" && !signal.aborted) { + // Wait for the AbortSignal to have actually aborted. + await new Promise((resolve) => signal.onabort = resolve); + } + + expect(sentinel).toHaveBeenCalled(); + expect(oncancelled).not.toHaveBeenCalled(); + expect(reason).not.toBeInstanceOf(CancelError); + }))); + } else { + if (state === "rejected") { + it("should report late rejections as unhandled", () => testSignal((signal) => testPromise(async (promise) => { + promise.cancelOn(signal); + + await new Promise((resolve) => { + expectUnhandled(dummyError, promise, unhandled); + expectUnhandled(dummyError, promise, resolve); + }); + + expect(unhandled).toHaveBeenCalledExactlyOnceWith(dummyError, promise); + }))); + } + + it("should reject with a CancelError", () => testSignal((signal) => testPromise(async (promise)=> { + // Ignore the unhandled rejection from the test promise. + if (state === "rejected") { ignoreUnhandled(dummyError, promise); } + + let reason; + try { + await promise.cancelOn(signal); + } catch (err) { + reason = err; + } + + expect(reason).toBeInstanceOf(CancelError); + }))); + + it(`should call the oncancelled callback ${abortTime === "already" ? "" : "a"}synchronously`, () => testSignal((signal) => testPromise(async (promise) => { + // Ignore the unhandled rejection from the test promise. + if (state === "rejected") { ignoreUnhandled(dummyError, promise); } + + try { + promise.cancelOn(signal); + sentinel(); + await promise; + } catch {} + + expect(oncancelled).toHaveBeenCalledAfter(onabort); + if (abortTime === "already") { + expect(oncancelled).toHaveBeenCalledBefore(sentinel); + } else { + expect(oncancelled).toHaveBeenCalledAfter(sentinel); + } + }))); + + it("should propagate the abort reason as cause", () => testSignal((signal) => testPromise(async (promise) => { + // Ignore the unhandled rejection from the test promise. + if (state === "rejected") { ignoreUnhandled(dummyError, promise); } + + let reason; + try { + await promise.cancelOn(signal); + } catch (err) { + reason = err; + } + + expect(reason).toBeInstanceOf(CancelError); + expect(reason).toHaveProperty('cause', signal.reason); + expect(oncancelled).toHaveBeenCalledWith(signal.reason); + }))); + } + } + + describe.for(abortPatterns)("when called with %s aborted %s signals", ([abortTime, mode, testSignal]) => { + describe.for(resolutionPatterns)("when applied to %s %s promises", ([resolveTime, state, testPromise]) => { + tests(abortTime, mode, testSignal, resolveTime, state, testPromise); + }); + }); + + describe.for(resolutionPatterns)("when applied to %s %s promises", ([resolveTime, state, testPromise]) => { + describe.for(abortPatterns)("when called with %s aborted %s signals", ([abortTime, mode, testSignal]) => { + tests(abortTime, mode, testSignal, resolveTime, state, testPromise); + }); + }); +}); \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.ts new file mode 100644 index 000000000..8c16b0cd7 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.ts @@ -0,0 +1,934 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import isCallable from "./callable.js"; + +/** + * Exception class that will be used as rejection reason + * in case a {@link CancellablePromise} is cancelled successfully. + * + * The value of the {@link name} property is the string `"CancelError"`. + * The value of the {@link cause} property is the cause passed to the cancel method, if any. + */ +export class CancelError extends Error { + /** + * Constructs a new `CancelError` instance. + * @param message - The error message. + * @param options - Options to be forwarded to the Error constructor. + */ + constructor(message?: string, options?: ErrorOptions) { + super(message, options); + this.name = "CancelError"; + } +} + +/** + * Exception class that will be reported as an unhandled rejection + * in case a {@link CancellablePromise} rejects after being cancelled, + * or when the `oncancelled` callback throws or rejects. + * + * The value of the {@link name} property is the string `"CancelledRejectionError"`. + * The value of the {@link cause} property is the reason the promise rejected with. + * + * Because the original promise was cancelled, + * a wrapper promise will be passed to the unhandled rejection listener instead. + * The {@link promise} property holds a reference to the original promise. + */ +export class CancelledRejectionError extends Error { + /** + * Holds a reference to the promise that was cancelled and then rejected. + */ + promise: CancellablePromise; + + /** + * Constructs a new `CancelledRejectionError` instance. + * @param promise - The promise that caused the error originally. + * @param reason - The rejection reason. + * @param info - An optional informative message specifying the circumstances in which the error was thrown. + * Defaults to the string `"Unhandled rejection in cancelled promise."`. + */ + constructor(promise: CancellablePromise, reason?: any, info?: string) { + super((info ?? "Unhandled rejection in cancelled promise.") + " Reason: " + errorMessage(reason), { cause: reason }); + this.promise = promise; + this.name = "CancelledRejectionError"; + } +} + +type CancellablePromiseResolver = (value: T | PromiseLike | CancellablePromiseLike) => void; +type CancellablePromiseRejector = (reason?: any) => void; +type CancellablePromiseCanceller = (cause?: any) => void | PromiseLike; +type CancellablePromiseExecutor = (resolve: CancellablePromiseResolver, reject: CancellablePromiseRejector) => void; + +export interface CancellablePromiseLike { + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike | CancellablePromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike | CancellablePromiseLike) | undefined | null): CancellablePromiseLike; + cancel(cause?: any): void | PromiseLike; +} + +/** + * Wraps a cancellable promise along with its resolution methods. + * The `oncancelled` field will be null initially but may be set to provide a custom cancellation function. + */ +export interface CancellablePromiseWithResolvers { + promise: CancellablePromise; + resolve: CancellablePromiseResolver; + reject: CancellablePromiseRejector; + oncancelled: CancellablePromiseCanceller | null; +} + +interface CancellablePromiseState { + readonly root: CancellablePromiseState; + resolving: boolean; + settled: boolean; + reason?: CancelError; +} + +// Private field names. +const barrierSym = Symbol("barrier"); +const cancelImplSym = Symbol("cancelImpl"); +const species: typeof Symbol.species = Symbol.species ?? Symbol("speciesPolyfill"); + +/** + * A promise with an attached method for cancelling long-running operations (see {@link CancellablePromise#cancel}). + * Cancellation can optionally be bound to an {@link AbortSignal} + * for better composability (see {@link CancellablePromise#cancelOn}). + * + * Cancelling a pending promise will result in an immediate rejection + * with an instance of {@link CancelError} as reason, + * but whoever started the promise will be responsible + * for actually aborting the underlying operation. + * To this purpose, the constructor and all chaining methods + * accept optional cancellation callbacks. + * + * If a `CancellablePromise` still resolves after having been cancelled, + * the result will be discarded. If it rejects, the reason + * will be reported as an unhandled rejection, + * wrapped in a {@link CancelledRejectionError} instance. + * To facilitate the handling of cancellation requests, + * cancelled `CancellablePromise`s will _not_ report unhandled `CancelError`s + * whose `cause` field is the same as the one with which the current promise was cancelled. + * + * All usual promise methods are defined and return a `CancellablePromise` + * whose cancel method will cancel the parent operation as well, propagating the cancellation reason + * upwards through promise chains. + * Conversely, cancelling a promise will not automatically cancel dependent promises downstream: + * ```ts + * let root = new CancellablePromise((resolve, reject) => { ... }); + * let child1 = root.then(() => { ... }); + * let child2 = child1.then(() => { ... }); + * let child3 = root.catch(() => { ... }); + * child1.cancel(); // Cancels child1 and root, but not child2 or child3 + * ``` + * Cancelling a promise that has already settled is safe and has no consequence. + * + * The `cancel` method returns a promise that _always fulfills_ + * after the whole chain has processed the cancel request + * and all attached callbacks up to that moment have run. + * + * All ES2024 promise methods (static and instance) are defined on CancellablePromise, + * but actual availability may vary with OS/webview version. + * + * In line with the proposal at https://github.com/tc39/proposal-rm-builtin-subclassing, + * `CancellablePromise` does not support transparent subclassing. + * Extenders should take care to provide their own method implementations. + * This might be reconsidered in case the proposal is retired. + * + * CancellablePromise is a wrapper around the DOM Promise object + * and is compliant with the [Promises/A+ specification](https://promisesaplus.com/) + * (it passes the [compliance suite](https://github.com/promises-aplus/promises-tests)) + * if so is the underlying implementation. + */ +export class CancellablePromise extends Promise implements PromiseLike, CancellablePromiseLike { + // Private fields. + /** @internal */ + private [barrierSym]!: Partial> | null; + /** @internal */ + private readonly [cancelImplSym]!: (reason: CancelError) => void | PromiseLike; + + /** + * Creates a new `CancellablePromise`. + * + * @param executor - A callback used to initialize the promise. This callback is passed two arguments: + * a `resolve` callback used to resolve the promise with a value + * or the result of another promise (possibly cancellable), + * and a `reject` callback used to reject the promise with a provided reason or error. + * If the value provided to the `resolve` callback is a thenable _and_ cancellable object + * (it has a `then` _and_ a `cancel` method), + * cancellation requests will be forwarded to that object and the oncancelled will not be invoked anymore. + * If any one of the two callbacks is called _after_ the promise has been cancelled, + * the provided values will be cancelled and resolved as usual, + * but their results will be discarded. + * However, if the resolution process ultimately ends up in a rejection + * that is not due to cancellation, the rejection reason + * will be wrapped in a {@link CancelledRejectionError} + * and bubbled up as an unhandled rejection. + * @param oncancelled - It is the caller's responsibility to ensure that any operation + * started by the executor is properly halted upon cancellation. + * This optional callback can be used to that purpose. + * It will be called _synchronously_ with a cancellation cause + * when cancellation is requested, _after_ the promise has already rejected + * with a {@link CancelError}, but _before_ + * any {@link then}/{@link catch}/{@link finally} callback runs. + * If the callback returns a thenable, the promise returned from {@link cancel} + * will only fulfill after the former has settled. + * Unhandled exceptions or rejections from the callback will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as unhandled rejections. + * If the `resolve` callback is called before cancellation with a cancellable promise, + * cancellation requests on this promise will be diverted to that promise, + * and the original `oncancelled` callback will be discarded. + */ + constructor(executor: CancellablePromiseExecutor, oncancelled?: CancellablePromiseCanceller) { + let resolve!: (value: T | PromiseLike) => void; + let reject!: (reason?: any) => void; + super((res, rej) => { resolve = res; reject = rej; }); + + if ((this.constructor as any)[species] !== Promise) { + throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property."); + } + + let promise: CancellablePromiseWithResolvers = { + promise: this, + resolve, + reject, + get oncancelled() { return oncancelled ?? null; }, + set oncancelled(cb) { oncancelled = cb ?? undefined; } + }; + + const state: CancellablePromiseState = { + get root() { return state; }, + resolving: false, + settled: false + }; + + // Setup cancellation system. + void Object.defineProperties(this, { + [barrierSym]: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + [cancelImplSym]: { + configurable: false, + enumerable: false, + writable: false, + value: cancellerFor(promise, state) + } + }); + + // Run the actual executor. + const rejector = rejectorFor(promise, state); + try { + executor(resolverFor(promise, state), rejector); + } catch (err) { + if (state.resolving) { + console.log("Unhandled exception in CancellablePromise executor.", err); + } else { + rejector(err); + } + } + } + + /** + * Cancels immediately the execution of the operation associated with this promise. + * The promise rejects with a {@link CancelError} instance as reason, + * with the {@link CancelError#cause} property set to the given argument, if any. + * + * Has no effect if called after the promise has already settled; + * repeated calls in particular are safe, but only the first one + * will set the cancellation cause. + * + * The `CancelError` exception _need not_ be handled explicitly _on the promises that are being cancelled:_ + * cancelling a promise with no attached rejection handler does not trigger an unhandled rejection event. + * Therefore, the following idioms are all equally correct: + * ```ts + * new CancellablePromise((resolve, reject) => { ... }).cancel(); + * new CancellablePromise((resolve, reject) => { ... }).then(...).cancel(); + * new CancellablePromise((resolve, reject) => { ... }).then(...).catch(...).cancel(); + * ``` + * Whenever some cancelled promise in a chain rejects with a `CancelError` + * with the same cancellation cause as itself, the error will be discarded silently. + * However, the `CancelError` _will still be delivered_ to all attached rejection handlers + * added by {@link then} and related methods: + * ```ts + * let cancellable = new CancellablePromise((resolve, reject) => { ... }); + * cancellable.then(() => { ... }).catch(console.log); + * cancellable.cancel(); // A CancelError is printed to the console. + * ``` + * If the `CancelError` is not handled downstream by the time it reaches + * a _non-cancelled_ promise, it _will_ trigger an unhandled rejection event, + * just like normal rejections would: + * ```ts + * let cancellable = new CancellablePromise((resolve, reject) => { ... }); + * let chained = cancellable.then(() => { ... }).then(() => { ... }); // No catch... + * cancellable.cancel(); // Unhandled rejection event on chained! + * ``` + * Therefore, it is important to either cancel whole promise chains from their tail, + * as shown in the correct idioms above, or take care of handling errors everywhere. + * + * @returns A cancellable promise that _fulfills_ after the cancel callback (if any) + * and all handlers attached up to the call to cancel have run. + * If the cancel callback returns a thenable, the promise returned by `cancel` + * will also wait for that thenable to settle. + * This enables callers to wait for the cancelled operation to terminate + * without being forced to handle potential errors at the call site. + * ```ts + * cancellable.cancel().then(() => { + * // Cleanup finished, it's safe to do something else. + * }, (err) => { + * // Unreachable: the promise returned from cancel will never reject. + * }); + * ``` + * Note that the returned promise will _not_ handle implicitly any rejection + * that might have occurred already in the cancelled chain. + * It will just track whether registered handlers have been executed or not. + * Therefore, unhandled rejections will never be silently handled by calling cancel. + */ + cancel(cause?: any): CancellablePromise { + return new CancellablePromise((resolve) => { + // INVARIANT: the result of this[cancelImplSym] and the barrier do not ever reject. + // Unfortunately macOS High Sierra does not support Promise.allSettled. + Promise.all([ + this[cancelImplSym](new CancelError("Promise cancelled.", { cause })), + currentBarrier(this) + ]).then(() => resolve(), () => resolve()); + }); + } + + /** + * Binds promise cancellation to the abort event of the given {@link AbortSignal}. + * If the signal has already aborted, the promise will be cancelled immediately. + * When either condition is verified, the cancellation cause will be set + * to the signal's abort reason (see {@link AbortSignal#reason}). + * + * Has no effect if called (or if the signal aborts) _after_ the promise has already settled. + * Only the first signal to abort will set the cancellation cause. + * + * For more details about the cancellation process, + * see {@link cancel} and the `CancellablePromise` constructor. + * + * This method enables `await`ing cancellable promises without having + * to store them for future cancellation, e.g.: + * ```ts + * await longRunningOperation().cancelOn(signal); + * ``` + * instead of: + * ```ts + * let promiseToBeCancelled = longRunningOperation(); + * await promiseToBeCancelled; + * ``` + * + * @returns This promise, for method chaining. + */ + cancelOn(signal: AbortSignal): CancellablePromise { + if (signal.aborted) { + void this.cancel(signal.reason) + } else { + signal.addEventListener('abort', () => void this.cancel(signal.reason), {capture: true}); + } + + return this; + } + + /** + * Attaches callbacks for the resolution and/or rejection of the `CancellablePromise`. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * When the parent promise rejects or is cancelled, the `onrejected` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A `CancellablePromise` for the completion of whichever callback is executed. + * The returned promise is hooked up to propagate cancellation requests up the chain, but not down: + * + * - if the parent promise is cancelled, the `onrejected` handler will be invoked with a `CancelError` + * and the returned promise _will resolve regularly_ with its result; + * - conversely, if the returned promise is cancelled, _the parent promise is cancelled too;_ + * the `onrejected` handler will still be invoked with the parent's `CancelError`, + * but its result will be discarded + * and the returned promise will reject with a `CancelError` as well. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If either callback returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike | CancellablePromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike | CancellablePromiseLike) | undefined | null, oncancelled?: CancellablePromiseCanceller): CancellablePromise { + if (!(this instanceof CancellablePromise)) { + throw new TypeError("CancellablePromise.prototype.then called on an invalid object."); + } + + // NOTE: TypeScript's built-in type for then is broken, + // as it allows specifying an arbitrary TResult1 != T even when onfulfilled is not a function. + // We cannot fix it if we want to CancellablePromise to implement PromiseLike. + + if (!isCallable(onfulfilled)) { onfulfilled = identity as any; } + if (!isCallable(onrejected)) { onrejected = thrower; } + + if (onfulfilled === identity && onrejected == thrower) { + // Shortcut for trivial arguments. + return new CancellablePromise((resolve) => resolve(this as any)); + } + + const barrier: Partial> = {}; + this[barrierSym] = barrier; + + return new CancellablePromise((resolve, reject) => { + void super.then( + (value) => { + if (this[barrierSym] === barrier) { this[barrierSym] = null; } + barrier.resolve?.(); + + try { + resolve(onfulfilled!(value)); + } catch (err) { + reject(err); + } + }, + (reason?) => { + if (this[barrierSym] === barrier) { this[barrierSym] = null; } + barrier.resolve?.(); + + try { + resolve(onrejected!(reason)); + } catch (err) { + reject(err); + } + } + ); + }, async (cause?) => { + //cancelled = true; + try { + return oncancelled?.(cause); + } finally { + await this.cancel(cause); + } + }); + } + + /** + * Attaches a callback for only the rejection of the Promise. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * When the parent promise rejects or is cancelled, the `onrejected` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * It is equivalent to + * ```ts + * cancellablePromise.then(undefined, onrejected, oncancelled); + * ``` + * and the same caveats apply. + * + * @returns A Promise for the completion of the callback. + * Cancellation requests on the returned promise + * will propagate up the chain to the parent promise, + * but not in the other direction. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If `onrejected` returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + * See {@link then} for more details. + */ + catch(onrejected?: ((reason: any) => (PromiseLike | TResult)) | undefined | null, oncancelled?: CancellablePromiseCanceller): CancellablePromise { + return this.then(undefined, onrejected, oncancelled); + } + + /** + * Attaches a callback that is invoked when the CancellablePromise is settled (fulfilled or rejected). The + * resolved value cannot be accessed or modified from the callback. + * The returned promise will settle in the same state as the original one + * after the provided callback has completed execution, + * unless the callback throws or returns a rejecting promise, + * in which case the returned promise will reject as well. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * Once the parent promise settles, the `onfinally` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * This method is implemented in terms of {@link then} and the same caveats apply. + * It is polyfilled, hence available in every OS/webview version. + * + * @returns A Promise for the completion of the callback. + * Cancellation requests on the returned promise + * will propagate up the chain to the parent promise, + * but not in the other direction. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If `onfinally` returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + * See {@link then} for more details. + */ + finally(onfinally?: (() => void) | undefined | null, oncancelled?: CancellablePromiseCanceller): CancellablePromise { + if (!(this instanceof CancellablePromise)) { + throw new TypeError("CancellablePromise.prototype.finally called on an invalid object."); + } + + if (!isCallable(onfinally)) { + return this.then(onfinally, onfinally, oncancelled); + } + + return this.then( + (value) => CancellablePromise.resolve(onfinally()).then(() => value), + (reason?) => CancellablePromise.resolve(onfinally()).then(() => { throw reason; }), + oncancelled, + ); + } + + /** + * We use the `[Symbol.species]` static property, if available, + * to disable the built-in automatic subclassing features from {@link Promise}. + * It is critical for performance reasons that extenders do not override this. + * Once the proposal at https://github.com/tc39/proposal-rm-builtin-subclassing + * is either accepted or retired, this implementation will have to be revised accordingly. + * + * @ignore + * @internal + */ + static get [species]() { + return Promise; + } + + /** + * Creates a CancellablePromise that is resolved with an array of results + * when all of the provided Promises resolve, or rejected when any Promise is rejected. + * + * Every one of the provided objects that is a thenable _and_ cancellable object + * will be cancelled when the returned promise is cancelled, with the same cause. + * + * @group Static Methods + */ + static all(values: Iterable>): CancellablePromise[]>; + static all(values: T): CancellablePromise<{ -readonly [P in keyof T]: Awaited; }>; + static all | ArrayLike>(values: T): CancellablePromise { + let collected = Array.from(values); + const promise = collected.length === 0 + ? CancellablePromise.resolve(collected) + : new CancellablePromise((resolve, reject) => { + void Promise.all(collected).then(resolve, reject); + }, (cause?): Promise => cancelAll(promise, collected, cause)); + return promise; + } + + /** + * Creates a CancellablePromise that is resolved with an array of results + * when all of the provided Promises resolve or reject. + * + * Every one of the provided objects that is a thenable _and_ cancellable object + * will be cancelled when the returned promise is cancelled, with the same cause. + * + * @group Static Methods + */ + static allSettled(values: Iterable>): CancellablePromise>[]>; + static allSettled(values: T): CancellablePromise<{ -readonly [P in keyof T]: PromiseSettledResult>; }>; + static allSettled | ArrayLike>(values: T): CancellablePromise { + let collected = Array.from(values); + const promise = collected.length === 0 + ? CancellablePromise.resolve(collected) + : new CancellablePromise((resolve, reject) => { + void Promise.allSettled(collected).then(resolve, reject); + }, (cause?): Promise => cancelAll(promise, collected, cause)); + return promise; + } + + /** + * The any function returns a promise that is fulfilled by the first given promise to be fulfilled, + * or rejected with an AggregateError containing an array of rejection reasons + * if all of the given promises are rejected. + * It resolves all elements of the passed iterable to promises as it runs this algorithm. + * + * Every one of the provided objects that is a thenable _and_ cancellable object + * will be cancelled when the returned promise is cancelled, with the same cause. + * + * @group Static Methods + */ + static any(values: Iterable>): CancellablePromise>; + static any(values: T): CancellablePromise>; + static any | ArrayLike>(values: T): CancellablePromise { + let collected = Array.from(values); + const promise = collected.length === 0 + ? CancellablePromise.resolve(collected) + : new CancellablePromise((resolve, reject) => { + void Promise.any(collected).then(resolve, reject); + }, (cause?): Promise => cancelAll(promise, collected, cause)); + return promise; + } + + /** + * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved or rejected. + * + * Every one of the provided objects that is a thenable _and_ cancellable object + * will be cancelled when the returned promise is cancelled, with the same cause. + * + * @group Static Methods + */ + static race(values: Iterable>): CancellablePromise>; + static race(values: T): CancellablePromise>; + static race | ArrayLike>(values: T): CancellablePromise { + let collected = Array.from(values); + const promise = new CancellablePromise((resolve, reject) => { + void Promise.race(collected).then(resolve, reject); + }, (cause?): Promise => cancelAll(promise, collected, cause)); + return promise; + } + + /** + * Creates a new cancelled CancellablePromise for the provided cause. + * + * @group Static Methods + */ + static cancel(cause?: any): CancellablePromise { + const p = new CancellablePromise(() => {}); + p.cancel(cause); + return p; + } + + /** + * Creates a new CancellablePromise that cancels + * after the specified timeout, with the provided cause. + * + * If the {@link AbortSignal.timeout} factory method is available, + * it is used to base the timeout on _active_ time rather than _elapsed_ time. + * Otherwise, `timeout` falls back to {@link setTimeout}. + * + * @group Static Methods + */ + static timeout(milliseconds: number, cause?: any): CancellablePromise { + const promise = new CancellablePromise(() => {}); + if (AbortSignal && typeof AbortSignal === 'function' && AbortSignal.timeout && typeof AbortSignal.timeout === 'function') { + AbortSignal.timeout(milliseconds).addEventListener('abort', () => void promise.cancel(cause)); + } else { + setTimeout(() => void promise.cancel(cause), milliseconds); + } + return promise; + } + + /** + * Creates a new CancellablePromise that resolves after the specified timeout. + * The returned promise can be cancelled without consequences. + * + * @group Static Methods + */ + static sleep(milliseconds: number): CancellablePromise; + /** + * Creates a new CancellablePromise that resolves after + * the specified timeout, with the provided value. + * The returned promise can be cancelled without consequences. + * + * @group Static Methods + */ + static sleep(milliseconds: number, value: T): CancellablePromise; + static sleep(milliseconds: number, value?: T): CancellablePromise { + return new CancellablePromise((resolve) => { + setTimeout(() => resolve(value!), milliseconds); + }); + } + + /** + * Creates a new rejected CancellablePromise for the provided reason. + * + * @group Static Methods + */ + static reject(reason?: any): CancellablePromise { + return new CancellablePromise((_, reject) => reject(reason)); + } + + /** + * Creates a new resolved CancellablePromise. + * + * @group Static Methods + */ + static resolve(): CancellablePromise; + /** + * Creates a new resolved CancellablePromise for the provided value. + * + * @group Static Methods + */ + static resolve(value: T): CancellablePromise>; + /** + * Creates a new resolved CancellablePromise for the provided value. + * + * @group Static Methods + */ + static resolve(value: T | PromiseLike): CancellablePromise>; + static resolve(value?: T | PromiseLike): CancellablePromise> { + if (value instanceof CancellablePromise) { + // Optimise for cancellable promises. + return value; + } + return new CancellablePromise((resolve) => resolve(value)); + } + + /** + * Creates a new CancellablePromise and returns it in an object, along with its resolve and reject functions + * and a getter/setter for the cancellation callback. + * + * This method is polyfilled, hence available in every OS/webview version. + * + * @group Static Methods + */ + static withResolvers(): CancellablePromiseWithResolvers { + let result: CancellablePromiseWithResolvers = { oncancelled: null } as any; + result.promise = new CancellablePromise((resolve, reject) => { + result.resolve = resolve; + result.reject = reject; + }, (cause?: any) => { result.oncancelled?.(cause); }); + return result; + } +} + +/** + * Returns a callback that implements the cancellation algorithm for the given cancellable promise. + * The promise returned from the resulting function does not reject. + */ +function cancellerFor(promise: CancellablePromiseWithResolvers, state: CancellablePromiseState) { + let cancellationPromise: void | PromiseLike = undefined; + + return (reason: CancelError): void | PromiseLike => { + if (!state.settled) { + state.settled = true; + state.reason = reason; + promise.reject(reason); + + // Attach an error handler that ignores this specific rejection reason and nothing else. + // In theory, a sane underlying implementation at this point + // should always reject with our cancellation reason, + // hence the handler will never throw. + void Promise.prototype.then.call(promise.promise, undefined, (err) => { + if (err !== reason) { + throw err; + } + }); + } + + // If reason is not set, the promise resolved regularly, hence we must not call oncancelled. + // If oncancelled is unset, no need to go any further. + if (!state.reason || !promise.oncancelled) { return; } + + cancellationPromise = new Promise((resolve) => { + try { + resolve(promise.oncancelled!(state.reason!.cause)); + } catch (err) { + Promise.reject(new CancelledRejectionError(promise.promise, err, "Unhandled exception in oncancelled callback.")); + } + }).catch((reason?) => { + Promise.reject(new CancelledRejectionError(promise.promise, reason, "Unhandled rejection in oncancelled callback.")); + }); + + // Unset oncancelled to prevent repeated calls. + promise.oncancelled = null; + + return cancellationPromise; + } +} + +/** + * Returns a callback that implements the resolution algorithm for the given cancellable promise. + */ +function resolverFor(promise: CancellablePromiseWithResolvers, state: CancellablePromiseState): CancellablePromiseResolver { + return (value) => { + if (state.resolving) { return; } + state.resolving = true; + + if (value === promise.promise) { + if (state.settled) { return; } + state.settled = true; + promise.reject(new TypeError("A promise cannot be resolved with itself.")); + return; + } + + if (value != null && (typeof value === 'object' || typeof value === 'function')) { + let then: any; + try { + then = (value as any).then; + } catch (err) { + state.settled = true; + promise.reject(err); + return; + } + + if (isCallable(then)) { + try { + let cancel = (value as any).cancel; + if (isCallable(cancel)) { + const oncancelled = (cause?: any) => { + Reflect.apply(cancel, value, [cause]); + }; + if (state.reason) { + // If already cancelled, propagate cancellation. + // The promise returned from the canceller algorithm does not reject + // so it can be discarded safely. + void cancellerFor({ ...promise, oncancelled }, state)(state.reason); + } else { + promise.oncancelled = oncancelled; + } + } + } catch {} + + const newState: CancellablePromiseState = { + root: state.root, + resolving: false, + get settled() { return this.root.settled }, + set settled(value) { this.root.settled = value; }, + get reason() { return this.root.reason } + }; + + const rejector = rejectorFor(promise, newState); + try { + Reflect.apply(then, value, [resolverFor(promise, newState), rejector]); + } catch (err) { + rejector(err); + } + return; // IMPORTANT! + } + } + + if (state.settled) { return; } + state.settled = true; + promise.resolve(value); + }; +} + +/** + * Returns a callback that implements the rejection algorithm for the given cancellable promise. + */ +function rejectorFor(promise: CancellablePromiseWithResolvers, state: CancellablePromiseState): CancellablePromiseRejector { + return (reason?) => { + if (state.resolving) { return; } + state.resolving = true; + + if (state.settled) { + try { + if (reason instanceof CancelError && state.reason instanceof CancelError && Object.is(reason.cause, state.reason.cause)) { + // Swallow late rejections that are CancelErrors whose cancellation cause is the same as ours. + return; + } + } catch {} + + void Promise.reject(new CancelledRejectionError(promise.promise, reason)); + } else { + state.settled = true; + promise.reject(reason); + } + } +} + +/** + * Cancels all values in an array that look like cancellable thenables. + * Returns a promise that fulfills once all cancellation procedures for the given values have settled. + */ +function cancelAll(parent: CancellablePromise, values: any[], cause?: any): Promise { + const results: Promise[] = []; + + for (const value of values) { + let cancel: CancellablePromiseCanceller; + try { + if (!isCallable(value.then)) { continue; } + cancel = value.cancel; + if (!isCallable(cancel)) { continue; } + } catch { continue; } + + let result: void | PromiseLike; + try { + result = Reflect.apply(cancel, value, [cause]); + } catch (err) { + Promise.reject(new CancelledRejectionError(parent, err, "Unhandled exception in cancel method.")); + continue; + } + + if (!result) { continue; } + results.push( + (result instanceof Promise ? result : Promise.resolve(result)).catch((reason?) => { + Promise.reject(new CancelledRejectionError(parent, reason, "Unhandled rejection in cancel method.")); + }) + ); + } + + return Promise.all(results) as any; +} + +/** + * Returns its argument. + */ +function identity(x: T): T { + return x; +} + +/** + * Throws its argument. + */ +function thrower(reason?: any): never { + throw reason; +} + +/** + * Attempts various strategies to convert an error to a string. + */ +function errorMessage(err: any): string { + try { + if (err instanceof Error || typeof err !== 'object' || err.toString !== Object.prototype.toString) { + return "" + err; + } + } catch {} + + try { + return JSON.stringify(err); + } catch {} + + try { + return Object.prototype.toString.call(err); + } catch {} + + return ""; +} + +/** + * Gets the current barrier promise for the given cancellable promise. If necessary, initialises the barrier. + */ +function currentBarrier(promise: CancellablePromise): Promise { + let pwr: Partial> = promise[barrierSym] ?? {}; + if (!('promise' in pwr)) { + Object.assign(pwr, promiseWithResolvers()); + } + if (promise[barrierSym] == null) { + pwr.resolve!(); + promise[barrierSym] = pwr; + } + return pwr.promise!; +} + +// Polyfill Promise.withResolvers. +let promiseWithResolvers = Promise.withResolvers; +if (promiseWithResolvers && typeof promiseWithResolvers === 'function') { + promiseWithResolvers = promiseWithResolvers.bind(Promise); +} else { + promiseWithResolvers = function (): PromiseWithResolvers { + let resolve!: (value: T | PromiseLike) => void; + let reject!: (reason?: any) => void; + const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); + return { promise, resolve, reject }; + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/clipboard.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/clipboard.ts new file mode 100644 index 000000000..a6f2f1985 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/clipboard.ts @@ -0,0 +1,35 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import {newRuntimeCaller, objectNames} from "./runtime.js"; + +const call = newRuntimeCaller(objectNames.Clipboard); + +const ClipboardSetText = 0; +const ClipboardText = 1; + +/** + * Sets the text to the Clipboard. + * + * @param text - The text to be set to the Clipboard. + * @return A Promise that resolves when the operation is successful. + */ +export function SetText(text: string): Promise { + return call(ClipboardSetText, {text}); +} + +/** + * Get the Clipboard text + * + * @returns A promise that resolves with the text from the Clipboard. + */ +export function Text(): Promise { + return call(ClipboardText); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/contextmenu.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/contextmenu.ts new file mode 100644 index 000000000..c1615d1c2 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/contextmenu.ts @@ -0,0 +1,94 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { newRuntimeCaller, objectNames } from "./runtime.js"; +import { IsDebug } from "./system.js"; +import { eventTarget } from "./utils.js"; + +// setup +window.addEventListener('contextmenu', contextMenuHandler); + +const call = newRuntimeCaller(objectNames.ContextMenu); + +const ContextMenuOpen = 0; + +function openContextMenu(id: string, x: number, y: number, data: any): void { + void call(ContextMenuOpen, {id, x, y, data}); +} + +function contextMenuHandler(event: MouseEvent) { + const target = eventTarget(event); + + // Check for custom context menu + const customContextMenu = window.getComputedStyle(target).getPropertyValue("--custom-contextmenu").trim(); + + if (customContextMenu) { + event.preventDefault(); + const data = window.getComputedStyle(target).getPropertyValue("--custom-contextmenu-data"); + openContextMenu(customContextMenu, event.clientX, event.clientY, data); + } else { + processDefaultContextMenu(event, target); + } +} + + +/* +--default-contextmenu: auto; (default) will show the default context menu if contentEditable is true OR text has been selected OR element is input or textarea +--default-contextmenu: show; will always show the default context menu +--default-contextmenu: hide; will always hide the default context menu + +This rule is inherited like normal CSS rules, so nesting works as expected +*/ +function processDefaultContextMenu(event: MouseEvent, target: HTMLElement) { + // Debug builds always show the menu + if (IsDebug()) { + return; + } + + // Process default context menu + switch (window.getComputedStyle(target).getPropertyValue("--default-contextmenu").trim()) { + case 'show': + return; + case 'hide': + event.preventDefault(); + return; + } + + // Check if contentEditable is true + if (target.isContentEditable) { + return; + } + + // Check if text has been selected + const selection = window.getSelection(); + const hasSelection = selection && selection.toString().length > 0; + if (hasSelection) { + for (let i = 0; i < selection.rangeCount; i++) { + const range = selection.getRangeAt(i); + const rects = range.getClientRects(); + for (let j = 0; j < rects.length; j++) { + const rect = rects[j]; + if (document.elementFromPoint(rect.left, rect.top) === target) { + return; + } + } + } + } + + // Check if tag is input or textarea. + if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) { + if (hasSelection || (!target.readOnly && !target.disabled)) { + return; + } + } + + // hide default context menu + event.preventDefault(); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/create.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/create.ts new file mode 100644 index 000000000..56de58add --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/create.ts @@ -0,0 +1,112 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/** + * Any is a dummy creation function for simple or unknown types. + */ +export function Any(source: any): T { + return source; +} + +/** + * ByteSlice is a creation function that replaces + * null strings with empty strings. + */ +export function ByteSlice(source: any): string { + return ((source == null) ? "" : source); +} + +/** + * Array takes a creation function for an arbitrary type + * and returns an in-place creation function for an array + * whose elements are of that type. + */ +export function Array(element: (source: any) => T): (source: any) => T[] { + if (element === Any) { + return (source) => (source === null ? [] : source); + } + + return (source) => { + if (source === null) { + return []; + } + for (let i = 0; i < source.length; i++) { + source[i] = element(source[i]); + } + return source; + }; +} + +/** + * Map takes creation functions for two arbitrary types + * and returns an in-place creation function for an object + * whose keys and values are of those types. + */ +export function Map(key: (source: any) => string, value: (source: any) => V): (source: any) => Record { + if (value === Any) { + return (source) => (source === null ? {} : source); + } + + return (source) => { + if (source === null) { + return {}; + } + for (const key in source) { + source[key] = value(source[key]); + } + return source; + }; +} + +/** + * Nullable takes a creation function for an arbitrary type + * and returns a creation function for a nullable value of that type. + */ +export function Nullable(element: (source: any) => T): (source: any) => (T | null) { + if (element === Any) { + return Any; + } + + return (source) => (source === null ? null : element(source)); +} + +/** + * Struct takes an object mapping field names to creation functions + * and returns an in-place creation function for a struct. + */ +export function Struct(createField: Record any>): + = any>(source: any) => U +{ + let allAny = true; + for (const name in createField) { + if (createField[name] !== Any) { + allAny = false; + break; + } + } + if (allAny) { + return Any; + } + + return (source) => { + for (const name in createField) { + if (name in source) { + source[name] = createField[name](source[name]); + } + } + return source; + }; +} + +/** + * Maps known event names to creation functions for their data types. + * Will be monkey-patched by the binding generator. + */ +export const Events: Record any> = {}; diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/dialogs.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/dialogs.ts new file mode 100644 index 000000000..3ce18ef26 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/dialogs.ts @@ -0,0 +1,183 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import {newRuntimeCaller, objectNames} from "./runtime.js"; + +// setup +window._wails = window._wails || {}; + +const call = newRuntimeCaller(objectNames.Dialog); + +// Define constants from the `methods` object in Title Case +const DialogInfo = 0; +const DialogWarning = 1; +const DialogError = 2; +const DialogQuestion = 3; +const DialogOpenFile = 4; +const DialogSaveFile = 5; + +export interface OpenFileDialogOptions { + /** Indicates if directories can be chosen. */ + CanChooseDirectories?: boolean; + /** Indicates if files can be chosen. */ + CanChooseFiles?: boolean; + /** Indicates if directories can be created. */ + CanCreateDirectories?: boolean; + /** Indicates if hidden files should be shown. */ + ShowHiddenFiles?: boolean; + /** Indicates if aliases should be resolved. */ + ResolvesAliases?: boolean; + /** Indicates if multiple selection is allowed. */ + AllowsMultipleSelection?: boolean; + /** Indicates if the extension should be hidden. */ + HideExtension?: boolean; + /** Indicates if hidden extensions can be selected. */ + CanSelectHiddenExtension?: boolean; + /** Indicates if file packages should be treated as directories. */ + TreatsFilePackagesAsDirectories?: boolean; + /** Indicates if other file types are allowed. */ + AllowsOtherFiletypes?: boolean; + /** Array of file filters. */ + Filters?: FileFilter[]; + /** Title of the dialog. */ + Title?: string; + /** Message to show in the dialog. */ + Message?: string; + /** Text to display on the button. */ + ButtonText?: string; + /** Directory to open in the dialog. */ + Directory?: string; + /** Indicates if the dialog should appear detached from the main window. */ + Detached?: boolean; +} + +export interface SaveFileDialogOptions { + /** Default filename to use in the dialog. */ + Filename?: string; + /** Indicates if directories can be chosen. */ + CanChooseDirectories?: boolean; + /** Indicates if files can be chosen. */ + CanChooseFiles?: boolean; + /** Indicates if directories can be created. */ + CanCreateDirectories?: boolean; + /** Indicates if hidden files should be shown. */ + ShowHiddenFiles?: boolean; + /** Indicates if aliases should be resolved. */ + ResolvesAliases?: boolean; + /** Indicates if the extension should be hidden. */ + HideExtension?: boolean; + /** Indicates if hidden extensions can be selected. */ + CanSelectHiddenExtension?: boolean; + /** Indicates if file packages should be treated as directories. */ + TreatsFilePackagesAsDirectories?: boolean; + /** Indicates if other file types are allowed. */ + AllowsOtherFiletypes?: boolean; + /** Array of file filters. */ + Filters?: FileFilter[]; + /** Title of the dialog. */ + Title?: string; + /** Message to show in the dialog. */ + Message?: string; + /** Text to display on the button. */ + ButtonText?: string; + /** Directory to open in the dialog. */ + Directory?: string; + /** Indicates if the dialog should appear detached from the main window. */ + Detached?: boolean; +} + +export interface MessageDialogOptions { + /** The title of the dialog window. */ + Title?: string; + /** The main message to show in the dialog. */ + Message?: string; + /** Array of button options to show in the dialog. */ + Buttons?: Button[]; + /** True if the dialog should appear detached from the main window (if applicable). */ + Detached?: boolean; +} + +export interface Button { + /** Text that appears within the button. */ + Label?: string; + /** True if the button should cancel an operation when clicked. */ + IsCancel?: boolean; + /** True if the button should be the default action when the user presses enter. */ + IsDefault?: boolean; +} + +export interface FileFilter { + /** Display name for the filter, it could be "Text Files", "Images" etc. */ + DisplayName?: string; + /** Pattern to match for the filter, e.g. "*.txt;*.md" for text markdown files. */ + Pattern?: string; +} + +/** + * Presents a dialog of specified type with the given options. + * + * @param type - Dialog type. + * @param options - Options for the dialog. + * @returns A promise that resolves with result of dialog. + */ +function dialog(type: number, options: MessageDialogOptions | OpenFileDialogOptions | SaveFileDialogOptions = {}): Promise { + return call(type, options); +} + +/** + * Presents an info dialog. + * + * @param options - Dialog options + * @returns A promise that resolves with the label of the chosen button. + */ +export function Info(options: MessageDialogOptions): Promise { return dialog(DialogInfo, options); } + +/** + * Presents a warning dialog. + * + * @param options - Dialog options. + * @returns A promise that resolves with the label of the chosen button. + */ +export function Warning(options: MessageDialogOptions): Promise { return dialog(DialogWarning, options); } + +/** + * Presents an error dialog. + * + * @param options - Dialog options. + * @returns A promise that resolves with the label of the chosen button. + */ +export function Error(options: MessageDialogOptions): Promise { return dialog(DialogError, options); } + +/** + * Presents a question dialog. + * + * @param options - Dialog options. + * @returns A promise that resolves with the label of the chosen button. + */ +export function Question(options: MessageDialogOptions): Promise { return dialog(DialogQuestion, options); } + +/** + * Presents a file selection dialog to pick one or more files to open. + * + * @param options - Dialog options. + * @returns Selected file or list of files, or a blank string/empty list if no file has been selected. + */ +export function OpenFile(options: OpenFileDialogOptions & { AllowsMultipleSelection: true }): Promise; +export function OpenFile(options: OpenFileDialogOptions & { AllowsMultipleSelection?: false | undefined }): Promise; +export function OpenFile(options: OpenFileDialogOptions): Promise; +export function OpenFile(options: OpenFileDialogOptions): Promise { return dialog(DialogOpenFile, options) ?? []; } + +/** + * Presents a file selection dialog to pick a file to save. + * + * @param options - Dialog options. + * @returns Selected file, or a blank string if no file has been selected. + */ +export function SaveFile(options: SaveFileDialogOptions): Promise { return dialog(DialogSaveFile, options); } diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/drag.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/drag.ts new file mode 100644 index 000000000..54624f7fc --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/drag.ts @@ -0,0 +1,262 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { invoke, IsWindows } from "./system.js"; +import { GetFlag } from "./flags.js"; +import { canTrackButtons, eventTarget } from "./utils.js"; + +// Setup +let canDrag = false; +let dragging = false; + +let resizable = false; +let canResize = false; +let resizing = false; +let resizeEdge: string = ""; +let defaultCursor = "auto"; + +let buttons = 0; +const buttonsTracked = canTrackButtons(); + +window._wails = window._wails || {}; +window._wails.setResizable = (value: boolean): void => { + resizable = value; + if (!resizable) { + // Stop resizing if in progress. + canResize = resizing = false; + setResize(); + } +}; + +// Defer attaching mouse listeners until we know we're not on mobile. +let dragInitDone = false; +function isMobile(): boolean { + const os = (window as any)._wails?.environment?.OS; + if (os === "ios" || os === "android") return true; + // Fallback heuristic if environment not yet set + const ua = navigator.userAgent || navigator.vendor || (window as any).opera || ""; + return /android|iphone|ipad|ipod|iemobile|wpdesktop/i.test(ua); +} +function tryInitDragHandlers(): void { + if (dragInitDone) return; + if (isMobile()) return; + window.addEventListener('mousedown', update, { capture: true }); + window.addEventListener('mousemove', update, { capture: true }); + window.addEventListener('mouseup', update, { capture: true }); + for (const ev of ['click', 'contextmenu', 'dblclick']) { + window.addEventListener(ev, suppressEvent, { capture: true }); + } + dragInitDone = true; +} +// Attempt immediate init (in case environment already present) +tryInitDragHandlers(); +// Also attempt on DOM ready +document.addEventListener('DOMContentLoaded', tryInitDragHandlers, { once: true }); +// As a last resort, poll for environment for a short period +let dragEnvPolls = 0; +const dragEnvPoll = window.setInterval(() => { + if (dragInitDone) { window.clearInterval(dragEnvPoll); return; } + tryInitDragHandlers(); + if (++dragEnvPolls > 100) { window.clearInterval(dragEnvPoll); } +}, 50); + +function suppressEvent(event: Event) { + // Suppress click events while resizing or dragging. + if (dragging || resizing) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } +} + +// Use constants to avoid comparing strings multiple times. +const MouseDown = 0; +const MouseUp = 1; +const MouseMove = 2; + +function update(event: MouseEvent) { + // Windows suppresses mouse events at the end of dragging or resizing, + // so we need to be smart and synthesize button events. + + let eventType: number, eventButtons = event.buttons; + switch (event.type) { + case 'mousedown': + eventType = MouseDown; + if (!buttonsTracked) { eventButtons = buttons | (1 << event.button); } + break; + case 'mouseup': + eventType = MouseUp; + if (!buttonsTracked) { eventButtons = buttons & ~(1 << event.button); } + break; + default: + eventType = MouseMove; + if (!buttonsTracked) { eventButtons = buttons; } + break; + } + + let released = buttons & ~eventButtons; + let pressed = eventButtons & ~buttons; + + buttons = eventButtons; + + // Synthesize a release-press sequence if we detect a press of an already pressed button. + if (eventType === MouseDown && !(pressed & event.button)) { + released |= (1 << event.button); + pressed |= (1 << event.button); + } + + // Suppress all button events during dragging and resizing, + // unless this is a mouseup event that is ending a drag action. + if ( + eventType !== MouseMove // Fast path for mousemove + && resizing + || ( + dragging + && ( + eventType === MouseDown + || event.button !== 0 + ) + ) + ) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } + + // Handle releases + if (released & 1) { primaryUp(event); } + // Handle presses + if (pressed & 1) { primaryDown(event); } + + // Handle mousemove + if (eventType === MouseMove) { onMouseMove(event); }; +} + +function primaryDown(event: MouseEvent): void { + // Reset readiness state. + canDrag = false; + canResize = false; + + // Ignore repeated clicks on macOS and Linux. + if (!IsWindows()) { + if (event.type === 'mousedown' && event.button === 0 && event.detail !== 1) { + return; + } + } + + if (resizeEdge) { + // Ready to resize if the primary button was pressed for the first time. + canResize = true; + // Do not start drag operations when on resize edges. + return; + } + + // Retrieve target element + const target = eventTarget(event); + + // Ready to drag if the primary button was pressed for the first time on a draggable element. + // Ignore clicks on the scrollbar. + const style = window.getComputedStyle(target); + canDrag = ( + style.getPropertyValue("--wails-draggable").trim() === "drag" + && ( + event.offsetX - parseFloat(style.paddingLeft) < target.clientWidth + && event.offsetY - parseFloat(style.paddingTop) < target.clientHeight + ) + ); +} + +function primaryUp(event: MouseEvent) { + // Stop dragging and resizing. + canDrag = false; + dragging = false; + canResize = false; + resizing = false; +} + +const cursorForEdge = Object.freeze({ + "se-resize": "nwse-resize", + "sw-resize": "nesw-resize", + "nw-resize": "nwse-resize", + "ne-resize": "nesw-resize", + "w-resize": "ew-resize", + "n-resize": "ns-resize", + "s-resize": "ns-resize", + "e-resize": "ew-resize", +}) + +function setResize(edge?: keyof typeof cursorForEdge): void { + if (edge) { + if (!resizeEdge) { defaultCursor = document.body.style.cursor; } + document.body.style.cursor = cursorForEdge[edge]; + } else if (!edge && resizeEdge) { + document.body.style.cursor = defaultCursor; + } + + resizeEdge = edge || ""; +} + +function onMouseMove(event: MouseEvent): void { + if (canResize && resizeEdge) { + // Start resizing. + resizing = true; + invoke("wails:resize:" + resizeEdge); + } else if (canDrag) { + // Start dragging. + dragging = true; + invoke("wails:drag"); + } + + if (dragging || resizing) { + // Either drag or resize is ongoing, + // reset readiness and stop processing. + canDrag = canResize = false; + return; + } + + if (!resizable || !IsWindows()) { + if (resizeEdge) { setResize(); } + return; + } + + const resizeHandleHeight = GetFlag("system.resizeHandleHeight") || 5; + const resizeHandleWidth = GetFlag("system.resizeHandleWidth") || 5; + + // Extra pixels for the corner areas. + const cornerExtra = GetFlag("resizeCornerExtra") || 10; + + const rightBorder = (window.outerWidth - event.clientX) < resizeHandleWidth; + const leftBorder = event.clientX < resizeHandleWidth; + const topBorder = event.clientY < resizeHandleHeight; + const bottomBorder = (window.outerHeight - event.clientY) < resizeHandleHeight; + + // Adjust for corner areas. + const rightCorner = (window.outerWidth - event.clientX) < (resizeHandleWidth + cornerExtra); + const leftCorner = event.clientX < (resizeHandleWidth + cornerExtra); + const topCorner = event.clientY < (resizeHandleHeight + cornerExtra); + const bottomCorner = (window.outerHeight - event.clientY) < (resizeHandleHeight + cornerExtra); + + if (!leftCorner && !topCorner && !bottomCorner && !rightCorner) { + // Optimisation: out of all corner areas implies out of borders. + setResize(); + } + // Detect corners. + else if (rightCorner && bottomCorner) setResize("se-resize"); + else if (leftCorner && bottomCorner) setResize("sw-resize"); + else if (leftCorner && topCorner) setResize("nw-resize"); + else if (topCorner && rightCorner) setResize("ne-resize"); + // Detect borders. + else if (leftBorder) setResize("w-resize"); + else if (topBorder) setResize("n-resize"); + else if (bottomBorder) setResize("s-resize"); + else if (rightBorder) setResize("e-resize"); + // Out of border area. + else setResize(); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts new file mode 100644 index 000000000..db381077d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts @@ -0,0 +1,260 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export const Types = Object.freeze({ + Windows: Object.freeze({ + APMPowerSettingChange: "windows:APMPowerSettingChange", + APMPowerStatusChange: "windows:APMPowerStatusChange", + APMResumeAutomatic: "windows:APMResumeAutomatic", + APMResumeSuspend: "windows:APMResumeSuspend", + APMSuspend: "windows:APMSuspend", + ApplicationStarted: "windows:ApplicationStarted", + SystemThemeChanged: "windows:SystemThemeChanged", + WebViewNavigationCompleted: "windows:WebViewNavigationCompleted", + WindowActive: "windows:WindowActive", + WindowBackgroundErase: "windows:WindowBackgroundErase", + WindowClickActive: "windows:WindowClickActive", + WindowClosing: "windows:WindowClosing", + WindowDidMove: "windows:WindowDidMove", + WindowDidResize: "windows:WindowDidResize", + WindowDPIChanged: "windows:WindowDPIChanged", + WindowDragDrop: "windows:WindowDragDrop", + WindowDragEnter: "windows:WindowDragEnter", + WindowDragLeave: "windows:WindowDragLeave", + WindowDragOver: "windows:WindowDragOver", + WindowEndMove: "windows:WindowEndMove", + WindowEndResize: "windows:WindowEndResize", + WindowFullscreen: "windows:WindowFullscreen", + WindowHide: "windows:WindowHide", + WindowInactive: "windows:WindowInactive", + WindowKeyDown: "windows:WindowKeyDown", + WindowKeyUp: "windows:WindowKeyUp", + WindowKillFocus: "windows:WindowKillFocus", + WindowNonClientHit: "windows:WindowNonClientHit", + WindowNonClientMouseDown: "windows:WindowNonClientMouseDown", + WindowNonClientMouseLeave: "windows:WindowNonClientMouseLeave", + WindowNonClientMouseMove: "windows:WindowNonClientMouseMove", + WindowNonClientMouseUp: "windows:WindowNonClientMouseUp", + WindowPaint: "windows:WindowPaint", + WindowRestore: "windows:WindowRestore", + WindowSetFocus: "windows:WindowSetFocus", + WindowShow: "windows:WindowShow", + WindowStartMove: "windows:WindowStartMove", + WindowStartResize: "windows:WindowStartResize", + WindowUnFullscreen: "windows:WindowUnFullscreen", + WindowZOrderChanged: "windows:WindowZOrderChanged", + WindowMinimise: "windows:WindowMinimise", + WindowUnMinimise: "windows:WindowUnMinimise", + WindowMaximise: "windows:WindowMaximise", + WindowUnMaximise: "windows:WindowUnMaximise", + }), + Mac: Object.freeze({ + ApplicationDidBecomeActive: "mac:ApplicationDidBecomeActive", + ApplicationDidChangeBackingProperties: "mac:ApplicationDidChangeBackingProperties", + ApplicationDidChangeEffectiveAppearance: "mac:ApplicationDidChangeEffectiveAppearance", + ApplicationDidChangeIcon: "mac:ApplicationDidChangeIcon", + ApplicationDidChangeOcclusionState: "mac:ApplicationDidChangeOcclusionState", + ApplicationDidChangeScreenParameters: "mac:ApplicationDidChangeScreenParameters", + ApplicationDidChangeStatusBarFrame: "mac:ApplicationDidChangeStatusBarFrame", + ApplicationDidChangeStatusBarOrientation: "mac:ApplicationDidChangeStatusBarOrientation", + ApplicationDidChangeTheme: "mac:ApplicationDidChangeTheme", + ApplicationDidFinishLaunching: "mac:ApplicationDidFinishLaunching", + ApplicationDidHide: "mac:ApplicationDidHide", + ApplicationDidResignActive: "mac:ApplicationDidResignActive", + ApplicationDidUnhide: "mac:ApplicationDidUnhide", + ApplicationDidUpdate: "mac:ApplicationDidUpdate", + ApplicationShouldHandleReopen: "mac:ApplicationShouldHandleReopen", + ApplicationWillBecomeActive: "mac:ApplicationWillBecomeActive", + ApplicationWillFinishLaunching: "mac:ApplicationWillFinishLaunching", + ApplicationWillHide: "mac:ApplicationWillHide", + ApplicationWillResignActive: "mac:ApplicationWillResignActive", + ApplicationWillTerminate: "mac:ApplicationWillTerminate", + ApplicationWillUnhide: "mac:ApplicationWillUnhide", + ApplicationWillUpdate: "mac:ApplicationWillUpdate", + MenuDidAddItem: "mac:MenuDidAddItem", + MenuDidBeginTracking: "mac:MenuDidBeginTracking", + MenuDidClose: "mac:MenuDidClose", + MenuDidDisplayItem: "mac:MenuDidDisplayItem", + MenuDidEndTracking: "mac:MenuDidEndTracking", + MenuDidHighlightItem: "mac:MenuDidHighlightItem", + MenuDidOpen: "mac:MenuDidOpen", + MenuDidPopUp: "mac:MenuDidPopUp", + MenuDidRemoveItem: "mac:MenuDidRemoveItem", + MenuDidSendAction: "mac:MenuDidSendAction", + MenuDidSendActionToItem: "mac:MenuDidSendActionToItem", + MenuDidUpdate: "mac:MenuDidUpdate", + MenuWillAddItem: "mac:MenuWillAddItem", + MenuWillBeginTracking: "mac:MenuWillBeginTracking", + MenuWillDisplayItem: "mac:MenuWillDisplayItem", + MenuWillEndTracking: "mac:MenuWillEndTracking", + MenuWillHighlightItem: "mac:MenuWillHighlightItem", + MenuWillOpen: "mac:MenuWillOpen", + MenuWillPopUp: "mac:MenuWillPopUp", + MenuWillRemoveItem: "mac:MenuWillRemoveItem", + MenuWillSendAction: "mac:MenuWillSendAction", + MenuWillSendActionToItem: "mac:MenuWillSendActionToItem", + MenuWillUpdate: "mac:MenuWillUpdate", + WebViewDidCommitNavigation: "mac:WebViewDidCommitNavigation", + WebViewDidFinishNavigation: "mac:WebViewDidFinishNavigation", + WebViewDidReceiveServerRedirectForProvisionalNavigation: "mac:WebViewDidReceiveServerRedirectForProvisionalNavigation", + WebViewDidStartProvisionalNavigation: "mac:WebViewDidStartProvisionalNavigation", + WindowDidBecomeKey: "mac:WindowDidBecomeKey", + WindowDidBecomeMain: "mac:WindowDidBecomeMain", + WindowDidBeginSheet: "mac:WindowDidBeginSheet", + WindowDidChangeAlpha: "mac:WindowDidChangeAlpha", + WindowDidChangeBackingLocation: "mac:WindowDidChangeBackingLocation", + WindowDidChangeBackingProperties: "mac:WindowDidChangeBackingProperties", + WindowDidChangeCollectionBehavior: "mac:WindowDidChangeCollectionBehavior", + WindowDidChangeEffectiveAppearance: "mac:WindowDidChangeEffectiveAppearance", + WindowDidChangeOcclusionState: "mac:WindowDidChangeOcclusionState", + WindowDidChangeOrderingMode: "mac:WindowDidChangeOrderingMode", + WindowDidChangeScreen: "mac:WindowDidChangeScreen", + WindowDidChangeScreenParameters: "mac:WindowDidChangeScreenParameters", + WindowDidChangeScreenProfile: "mac:WindowDidChangeScreenProfile", + WindowDidChangeScreenSpace: "mac:WindowDidChangeScreenSpace", + WindowDidChangeScreenSpaceProperties: "mac:WindowDidChangeScreenSpaceProperties", + WindowDidChangeSharingType: "mac:WindowDidChangeSharingType", + WindowDidChangeSpace: "mac:WindowDidChangeSpace", + WindowDidChangeSpaceOrderingMode: "mac:WindowDidChangeSpaceOrderingMode", + WindowDidChangeTitle: "mac:WindowDidChangeTitle", + WindowDidChangeToolbar: "mac:WindowDidChangeToolbar", + WindowDidDeminiaturize: "mac:WindowDidDeminiaturize", + WindowDidEndSheet: "mac:WindowDidEndSheet", + WindowDidEnterFullScreen: "mac:WindowDidEnterFullScreen", + WindowDidEnterVersionBrowser: "mac:WindowDidEnterVersionBrowser", + WindowDidExitFullScreen: "mac:WindowDidExitFullScreen", + WindowDidExitVersionBrowser: "mac:WindowDidExitVersionBrowser", + WindowDidExpose: "mac:WindowDidExpose", + WindowDidFocus: "mac:WindowDidFocus", + WindowDidMiniaturize: "mac:WindowDidMiniaturize", + WindowDidMove: "mac:WindowDidMove", + WindowDidOrderOffScreen: "mac:WindowDidOrderOffScreen", + WindowDidOrderOnScreen: "mac:WindowDidOrderOnScreen", + WindowDidResignKey: "mac:WindowDidResignKey", + WindowDidResignMain: "mac:WindowDidResignMain", + WindowDidResize: "mac:WindowDidResize", + WindowDidUpdate: "mac:WindowDidUpdate", + WindowDidUpdateAlpha: "mac:WindowDidUpdateAlpha", + WindowDidUpdateCollectionBehavior: "mac:WindowDidUpdateCollectionBehavior", + WindowDidUpdateCollectionProperties: "mac:WindowDidUpdateCollectionProperties", + WindowDidUpdateShadow: "mac:WindowDidUpdateShadow", + WindowDidUpdateTitle: "mac:WindowDidUpdateTitle", + WindowDidUpdateToolbar: "mac:WindowDidUpdateToolbar", + WindowDidZoom: "mac:WindowDidZoom", + WindowFileDraggingEntered: "mac:WindowFileDraggingEntered", + WindowFileDraggingExited: "mac:WindowFileDraggingExited", + WindowFileDraggingPerformed: "mac:WindowFileDraggingPerformed", + WindowHide: "mac:WindowHide", + WindowMaximise: "mac:WindowMaximise", + WindowUnMaximise: "mac:WindowUnMaximise", + WindowMinimise: "mac:WindowMinimise", + WindowUnMinimise: "mac:WindowUnMinimise", + WindowShouldClose: "mac:WindowShouldClose", + WindowShow: "mac:WindowShow", + WindowWillBecomeKey: "mac:WindowWillBecomeKey", + WindowWillBecomeMain: "mac:WindowWillBecomeMain", + WindowWillBeginSheet: "mac:WindowWillBeginSheet", + WindowWillChangeOrderingMode: "mac:WindowWillChangeOrderingMode", + WindowWillClose: "mac:WindowWillClose", + WindowWillDeminiaturize: "mac:WindowWillDeminiaturize", + WindowWillEnterFullScreen: "mac:WindowWillEnterFullScreen", + WindowWillEnterVersionBrowser: "mac:WindowWillEnterVersionBrowser", + WindowWillExitFullScreen: "mac:WindowWillExitFullScreen", + WindowWillExitVersionBrowser: "mac:WindowWillExitVersionBrowser", + WindowWillFocus: "mac:WindowWillFocus", + WindowWillMiniaturize: "mac:WindowWillMiniaturize", + WindowWillMove: "mac:WindowWillMove", + WindowWillOrderOffScreen: "mac:WindowWillOrderOffScreen", + WindowWillOrderOnScreen: "mac:WindowWillOrderOnScreen", + WindowWillResignMain: "mac:WindowWillResignMain", + WindowWillResize: "mac:WindowWillResize", + WindowWillUnfocus: "mac:WindowWillUnfocus", + WindowWillUpdate: "mac:WindowWillUpdate", + WindowWillUpdateAlpha: "mac:WindowWillUpdateAlpha", + WindowWillUpdateCollectionBehavior: "mac:WindowWillUpdateCollectionBehavior", + WindowWillUpdateCollectionProperties: "mac:WindowWillUpdateCollectionProperties", + WindowWillUpdateShadow: "mac:WindowWillUpdateShadow", + WindowWillUpdateTitle: "mac:WindowWillUpdateTitle", + WindowWillUpdateToolbar: "mac:WindowWillUpdateToolbar", + WindowWillUpdateVisibility: "mac:WindowWillUpdateVisibility", + WindowWillUseStandardFrame: "mac:WindowWillUseStandardFrame", + WindowZoomIn: "mac:WindowZoomIn", + WindowZoomOut: "mac:WindowZoomOut", + WindowZoomReset: "mac:WindowZoomReset", + }), + Linux: Object.freeze({ + ApplicationStartup: "linux:ApplicationStartup", + SystemThemeChanged: "linux:SystemThemeChanged", + WindowDeleteEvent: "linux:WindowDeleteEvent", + WindowDidMove: "linux:WindowDidMove", + WindowDidResize: "linux:WindowDidResize", + WindowFocusIn: "linux:WindowFocusIn", + WindowFocusOut: "linux:WindowFocusOut", + WindowLoadStarted: "linux:WindowLoadStarted", + WindowLoadRedirected: "linux:WindowLoadRedirected", + WindowLoadCommitted: "linux:WindowLoadCommitted", + WindowLoadFinished: "linux:WindowLoadFinished", + }), + iOS: Object.freeze({ + ApplicationDidBecomeActive: "ios:ApplicationDidBecomeActive", + ApplicationDidEnterBackground: "ios:ApplicationDidEnterBackground", + ApplicationDidFinishLaunching: "ios:ApplicationDidFinishLaunching", + ApplicationDidReceiveMemoryWarning: "ios:ApplicationDidReceiveMemoryWarning", + ApplicationWillEnterForeground: "ios:ApplicationWillEnterForeground", + ApplicationWillResignActive: "ios:ApplicationWillResignActive", + ApplicationWillTerminate: "ios:ApplicationWillTerminate", + WindowDidLoad: "ios:WindowDidLoad", + WindowWillAppear: "ios:WindowWillAppear", + WindowDidAppear: "ios:WindowDidAppear", + WindowWillDisappear: "ios:WindowWillDisappear", + WindowDidDisappear: "ios:WindowDidDisappear", + WindowSafeAreaInsetsChanged: "ios:WindowSafeAreaInsetsChanged", + WindowOrientationChanged: "ios:WindowOrientationChanged", + WindowTouchBegan: "ios:WindowTouchBegan", + WindowTouchMoved: "ios:WindowTouchMoved", + WindowTouchEnded: "ios:WindowTouchEnded", + WindowTouchCancelled: "ios:WindowTouchCancelled", + WebViewDidStartNavigation: "ios:WebViewDidStartNavigation", + WebViewDidFinishNavigation: "ios:WebViewDidFinishNavigation", + WebViewDidFailNavigation: "ios:WebViewDidFailNavigation", + WebViewDecidePolicyForNavigationAction: "ios:WebViewDecidePolicyForNavigationAction", + }), + Common: Object.freeze({ + ApplicationOpenedWithFile: "common:ApplicationOpenedWithFile", + ApplicationStarted: "common:ApplicationStarted", + ApplicationLaunchedWithUrl: "common:ApplicationLaunchedWithUrl", + ThemeChanged: "common:ThemeChanged", + WindowClosing: "common:WindowClosing", + WindowDidMove: "common:WindowDidMove", + WindowDidResize: "common:WindowDidResize", + WindowDPIChanged: "common:WindowDPIChanged", + WindowFilesDropped: "common:WindowFilesDropped", + WindowFocus: "common:WindowFocus", + WindowFullscreen: "common:WindowFullscreen", + WindowHide: "common:WindowHide", + WindowLostFocus: "common:WindowLostFocus", + WindowMaximise: "common:WindowMaximise", + WindowMinimise: "common:WindowMinimise", + WindowToggleFrameless: "common:WindowToggleFrameless", + WindowRestore: "common:WindowRestore", + WindowRuntimeReady: "common:WindowRuntimeReady", + WindowShow: "common:WindowShow", + WindowUnFullscreen: "common:WindowUnFullscreen", + WindowUnMaximise: "common:WindowUnMaximise", + WindowUnMinimise: "common:WindowUnMinimise", + WindowZoom: "common:WindowZoom", + WindowZoomIn: "common:WindowZoomIn", + WindowZoomOut: "common:WindowZoomOut", + WindowZoomReset: "common:WindowZoomReset", + }), +}); diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/events.test.js b/v3/internal/runtime/desktop/@wailsio/runtime/src/events.test.js new file mode 100644 index 000000000..e8157a17a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/events.test.js @@ -0,0 +1,155 @@ +import { On, Off, OffAll, OnMultiple, WailsEvent, Once } from './events'; +import { eventListeners } from "./listener"; +import { expect, describe, it, vi, afterEach, beforeEach } from 'vitest'; + +const dispatchWailsEvent = window._wails.dispatchWailsEvent; + +afterEach(() => { + OffAll(); + vi.resetAllMocks(); +}); + +describe("OnMultiple", () => { + const testEvent = { name: 'a', data: ["hello", "events"] }; + const cb = vi.fn((ev) => { + expect(ev).toBeInstanceOf(WailsEvent); + expect(ev).toMatchObject(testEvent); + }); + + it("should dispatch a properly initialised WailsEvent", () => { + OnMultiple('a', cb, 5); + dispatchWailsEvent(testEvent); + expect(cb).toHaveBeenCalled(); + }); + + it("should stop after the specified number of times", () => { + OnMultiple('a', cb, 5); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + expect(cb).toHaveBeenCalledTimes(5); + }); + + it("should return a cancel fn", () => { + const cancel = OnMultiple('a', cb, 5); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + cancel(); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + expect(cb).toBeCalledTimes(2); + }); +}); + +describe("On", () => { + let testEvent = { name: 'a', data: ["hello", "events"], sender: "window" }; + const cb = vi.fn((ev) => { + expect(ev).toBeInstanceOf(WailsEvent); + expect(ev).toMatchObject(testEvent); + }); + + it("should dispatch a properly initialised WailsEvent", () => { + On('a', cb); + dispatchWailsEvent(testEvent); + expect(cb).toHaveBeenCalled(); + }); + + it("should never stop", () => { + On('a', cb); + expect(eventListeners.get('a')[0].maxCallbacks).toBe(-1); + dispatchWailsEvent(testEvent); + expect(eventListeners.get('a')[0].maxCallbacks).toBe(-1); + }); + + it("should return a cancel fn", () => { + const cancel = On('a', cb) + dispatchWailsEvent(testEvent); + cancel(); + dispatchWailsEvent(testEvent); + expect(cb).toHaveBeenCalledTimes(1); + }); +}); + +describe("Once", () => { + const testEvent = { name: 'a', data: ["hello", "events"] }; + const cb = vi.fn((ev) => { + expect(ev).toBeInstanceOf(WailsEvent); + expect(ev).toMatchObject(testEvent); + }); + + it("should dispatch a properly initialised WailsEvent", () => { + Once('a', cb); + dispatchWailsEvent(testEvent); + expect(cb).toHaveBeenCalled(); + }); + + it("should stop after one time", () => { + Once('a', cb) + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + expect(cb).toHaveBeenCalledTimes(1); + }); + + it("should return a cancel fn", () => { + const cancel = Once('a', cb) + cancel(); + dispatchWailsEvent(testEvent); + expect(cb).not.toHaveBeenCalled(); + }); +}) + +describe("Off", () => { + const cba = vi.fn(), cbb = vi.fn(), cbc = vi.fn(); + + beforeEach(() => { + On('a', cba); + On('a', cba); + On('a', cba); + On('b', cbb); + On('c', cbc); + On('c', cbc); + }); + + it("should cancel all event listeners for a single type", () => { + Off('a'); + dispatchWailsEvent({ name: 'a' }); + dispatchWailsEvent({ name: 'b' }); + dispatchWailsEvent({ name: 'c' }); + expect(cba).not.toHaveBeenCalled(); + expect(cbb).toHaveBeenCalledTimes(1); + expect(cbc).toHaveBeenCalledTimes(2); + }); + + it("should cancel all event listeners for multiple types", () => { + Off('a', 'c') + dispatchWailsEvent({ name: 'a' }); + dispatchWailsEvent({ name: 'b' }); + dispatchWailsEvent({ name: 'c' }); + expect(cba).not.toHaveBeenCalled(); + expect(cbb).toHaveBeenCalledTimes(1); + expect(cbc).not.toHaveBeenCalled(); + }); +}); + +describe("OffAll", () => { + it("should cancel all event listeners", () => { + const cba = vi.fn(), cbb = vi.fn(), cbc = vi.fn(); + On('a', cba); + On('a', cba); + On('a', cba); + On('b', cbb); + On('c', cbc); + On('c', cbc); + OffAll(); + dispatchWailsEvent({ name: 'a' }); + dispatchWailsEvent({ name: 'b' }); + dispatchWailsEvent({ name: 'c' }); + expect(cba).not.toHaveBeenCalled(); + expect(cbb).not.toHaveBeenCalled(); + expect(cbc).not.toHaveBeenCalled(); + }); +}); diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/events.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/events.ts new file mode 100644 index 000000000..cf6a2581d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/events.ts @@ -0,0 +1,170 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { newRuntimeCaller, objectNames } from "./runtime.js"; +import { eventListeners, Listener, listenerOff } from "./listener.js"; +import { Events as Create } from "./create.js"; +import { Types } from "./event_types.js"; + +// Setup +window._wails = window._wails || {}; +window._wails.dispatchWailsEvent = dispatchWailsEvent; + +const call = newRuntimeCaller(objectNames.Events); +const EmitMethod = 0; + +export * from "./event_types.js"; + +/** + * A table of data types for all known events. + * Will be monkey-patched by the binding generator. + */ +export interface CustomEvents {} + +/** + * Either a known event name or an arbitrary string. + */ +export type WailsEventName = E | (string & {}); + +/** + * Union of all known system event names. + */ +type SystemEventName = { + [K in keyof (typeof Types)]: (typeof Types)[K][keyof ((typeof Types)[K])] +} extends (infer M) ? M[keyof M] : never; + +/** + * The data type associated to a given event. + */ +export type WailsEventData = + E extends keyof CustomEvents ? CustomEvents[E] : (E extends SystemEventName ? void : any); + +/** + * The type of handlers for a given event. + */ +export type WailsEventCallback = (ev: WailsEvent) => void; + +/** + * Represents a system event or a custom event emitted through wails-provided facilities. + */ +export class WailsEvent { + /** + * The name of the event. + */ + name: E; + + /** + * Optional data associated with the emitted event. + */ + data: WailsEventData; + + /** + * Name of the originating window. Omitted for application events. + * Will be overridden if set manually. + */ + sender?: string; + + constructor(name: E, data: WailsEventData); + constructor(name: WailsEventData extends null | void ? E : never) + constructor(name: E, data?: any) { + this.name = name; + this.data = data ?? null; + } +} + +function dispatchWailsEvent(event: any) { + let listeners = eventListeners.get(event.name); + if (!listeners) { + return; + } + + let wailsEvent = new WailsEvent( + event.name, + (event.name in Create) ? Create[event.name](event.data) : event.data + ); + if ('sender' in event) { + wailsEvent.sender = event.sender; + } + + listeners = listeners.filter(listener => !listener.dispatch(wailsEvent)); + if (listeners.length === 0) { + eventListeners.delete(event.name); + } else { + eventListeners.set(event.name, listeners); + } +} + +/** + * Register a callback function to be called multiple times for a specific event. + * + * @param eventName - The name of the event to register the callback for. + * @param callback - The callback function to be called when the event is triggered. + * @param maxCallbacks - The maximum number of times the callback can be called for the event. Once the maximum number is reached, the callback will no longer be called. + * @returns A function that, when called, will unregister the callback from the event. + */ +export function OnMultiple(eventName: E, callback: WailsEventCallback, maxCallbacks: number) { + let listeners = eventListeners.get(eventName) || []; + const thisListener = new Listener(eventName, callback, maxCallbacks); + listeners.push(thisListener); + eventListeners.set(eventName, listeners); + return () => listenerOff(thisListener); +} + +/** + * Registers a callback function to be executed when the specified event occurs. + * + * @param eventName - The name of the event to register the callback for. + * @param callback - The callback function to be called when the event is triggered. + * @returns A function that, when called, will unregister the callback from the event. + */ +export function On(eventName: E, callback: WailsEventCallback): () => void { + return OnMultiple(eventName, callback, -1); +} + +/** + * Registers a callback function to be executed only once for the specified event. + * + * @param eventName - The name of the event to register the callback for. + * @param callback - The callback function to be called when the event is triggered. + * @returns A function that, when called, will unregister the callback from the event. + */ +export function Once(eventName: E, callback: WailsEventCallback): () => void { + return OnMultiple(eventName, callback, 1); +} + +/** + * Removes event listeners for the specified event names. + * + * @param eventNames - The name of the events to remove listeners for. + */ +export function Off(...eventNames: [WailsEventName, ...WailsEventName[]]): void { + eventNames.forEach(eventName => eventListeners.delete(eventName)); +} + +/** + * Removes all event listeners. + */ +export function OffAll(): void { + eventListeners.clear(); +} + +/** + * Emits an event. + * + * @returns A promise that will be fulfilled once the event has been emitted. Resolves to true if the event was cancelled. + * @param name - The name of the event to emit + * @param data - The data that will be sent with the event + */ +export function Emit(name: E, data: WailsEventData): Promise +export function Emit(name: WailsEventData extends null | void ? E : never): Promise +export function Emit(name: WailsEventData, data?: any): Promise { + return call(EmitMethod, new WailsEvent(name, data)) +} + diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/flags.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/flags.ts new file mode 100644 index 000000000..9e4ad2427 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/flags.ts @@ -0,0 +1,23 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/** + * Retrieves the value associated with the specified key from the flag map. + * + * @param key - The key to retrieve the value for. + * @return The value associated with the specified key. + */ +export function GetFlag(key: string): any { + try { + return window._wails.flags[key]; + } catch (e) { + throw new Error("Unable to retrieve flag '" + key + "': " + e, { cause: e }); + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/global.d.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/global.d.ts new file mode 100644 index 000000000..231896ce1 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/global.d.ts @@ -0,0 +1,17 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +declare global { + interface Window { + _wails: Record; + } +} + +export {}; diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts new file mode 100644 index 000000000..1cdac37c4 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts @@ -0,0 +1,101 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +// Setup +window._wails = window._wails || {}; + +import "./contextmenu.js"; +import "./drag.js"; + +// Re-export public API +import * as Application from "./application.js"; +import * as Browser from "./browser.js"; +import * as Call from "./calls.js"; +import * as Clipboard from "./clipboard.js"; +import * as Create from "./create.js"; +import * as Dialogs from "./dialogs.js"; +import * as Events from "./events.js"; +import * as Flags from "./flags.js"; +import * as Screens from "./screens.js"; +import * as System from "./system.js"; +import * as IOS from "./ios.js"; +import Window, { handleDragEnter, handleDragLeave, handleDragOver } from "./window.js"; +import * as WML from "./wml.js"; + +export { + Application, + Browser, + Call, + Clipboard, + Dialogs, + Events, + Flags, + Screens, + System, + IOS, + Window, + WML +}; + +/** + * An internal utility consumed by the binding generator. + * + * @ignore + */ +export { Create }; + +export * from "./cancellable.js"; + +// Export transport interfaces and utilities +export { + setTransport, + getTransport, + type RuntimeTransport, + objectNames, + clientId, +} from "./runtime.js"; + +import { clientId } from "./runtime.js"; + +// Notify backend +window._wails.invoke = System.invoke; +window._wails.clientId = clientId; + +// Register platform handlers (internal API) +// Note: Window is the thisWindow instance (default export from window.ts) +// Binding ensures 'this' correctly refers to the current window instance +window._wails.handlePlatformFileDrop = Window.HandlePlatformFileDrop.bind(Window); + +// Linux-specific drag handlers (GTK intercepts DOM drag events) +window._wails.handleDragEnter = handleDragEnter; +window._wails.handleDragLeave = handleDragLeave; +window._wails.handleDragOver = handleDragOver; + +System.invoke("wails:runtime:ready"); + +/** + * Loads a script from the given URL if it exists. + * Uses HEAD request to check existence, then injects a script tag. + * Silently ignores if the script doesn't exist. + */ +export function loadOptionalScript(url: string): Promise { + return fetch(url, { method: 'HEAD' }) + .then(response => { + if (response.ok) { + const script = document.createElement('script'); + script.src = url; + document.head.appendChild(script); + } + }) + .catch(() => {}); // Silently ignore - script is optional +} + +// Load custom.js if available (used by server mode for WebSocket events, etc.) +loadOptionalScript('/wails/custom.js'); diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/ios.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/ios.ts new file mode 100644 index 000000000..33a428edf --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/ios.ts @@ -0,0 +1,36 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { newRuntimeCaller, objectNames } from "./runtime.js"; + +const call = newRuntimeCaller(objectNames.IOS); + +// Method IDs +const HapticsImpact = 0; +const DeviceInfo = 1; + +export namespace Haptics { + export type ImpactStyle = "light"|"medium"|"heavy"|"soft"|"rigid"; + export function Impact(style: ImpactStyle = "medium"): Promise { + return call(HapticsImpact, { style }); + } +} + +export namespace Device { + export interface Info { + model: string; + systemName: string; + systemVersion: string; + isSimulator: boolean; + } + export function Info(): Promise { + return call(DeviceInfo); + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/listener.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/listener.ts new file mode 100644 index 000000000..0d74debca --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/listener.ts @@ -0,0 +1,52 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +// The following utilities have been factored out of ./events.ts +// for testing purposes. + +export const eventListeners = new Map(); + +export class Listener { + eventName: string; + callback: (data: any) => void; + maxCallbacks: number; + + constructor(eventName: string, callback: (data: any) => void, maxCallbacks: number) { + this.eventName = eventName; + this.callback = callback; + this.maxCallbacks = maxCallbacks || -1; + } + + dispatch(data: any): boolean { + try { + this.callback(data); + } catch (err) { + console.error(err); + } + + if (this.maxCallbacks === -1) return false; + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + } +} + +export function listenerOff(listener: Listener): void { + let listeners = eventListeners.get(listener.eventName); + if (!listeners) { + return; + } + + listeners = listeners.filter(l => l !== listener); + if (listeners.length === 0) { + eventListeners.delete(listener.eventName); + } else { + eventListeners.set(listener.eventName, listeners); + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/nanoid.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/nanoid.ts new file mode 100644 index 000000000..bfe83048f --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/nanoid.ts @@ -0,0 +1,42 @@ +// Source: https://github.com/ai/nanoid + +// The MIT License (MIT) +// +// Copyright 2017 Andrey Sitnik +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This alphabet uses `A-Za-z0-9_-` symbols. +// The order of characters is optimized for better gzip and brotli compression. +// References to the same file (works both for gzip and brotli): +// `'use`, `andom`, and `rict'` +// References to the brotli default dictionary: +// `-26T`, `1983`, `40px`, `75px`, `bush`, `jack`, `mind`, `very`, and `wolf` +const urlAlphabet = + 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' + +export function nanoid(size: number = 21): string { + let id = '' + // A compact alternative for `for (var i = 0; i < step; i++)`. + let i = size | 0 + while (i--) { + // `| 0` is more compact and faster than `Math.floor()`. + id += urlAlphabet[(Math.random() * 64) | 0] + } + return id +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/plugins/vite.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/plugins/vite.ts new file mode 100644 index 000000000..b661dce82 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/plugins/vite.ts @@ -0,0 +1,76 @@ +import type {Plugin} from 'vite'; + +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +const TYPED_EVENTS_MODULE = "\0wailsio_runtime_events_typed"; + +/** + * A plugin that extends the wails runtime with locally generated code + * to provide support for typed custom events. + * With the plugin installed, vite will fail to build the project + * unless wails bindings have been generated first. + * + * @param {string} [bindingsRoot] - The root import path for generated bindings + */ +export default function WailsTypedEvents(bindingsRoot: string): Plugin { + let bindingsId: string, + runtimeId: string, + eventsId: string; + + return { + name: "wails-typed-events", + async buildStart() { + const bindingsPath = `${bindingsRoot}/github.com/wailsapp/wails/v3/internal/eventcreate`; + let resolution = await this.resolve(bindingsPath); + if (!resolution || resolution.external) { + this.error(`Event bindings module not found at import specifier '${bindingsPath}'. Please verify that the wails tool is up to date and the binding generator runs successfully. If you moved the bindings to a custom location, ensure you supplied the correct root path as the first argument to \`wailsTypedEventsPlugin\``); + return; + } + bindingsId = resolution.id; + + resolution = await this.resolve("@wailsio/runtime"); + if (!resolution || resolution.external) { return; } + runtimeId = resolution.id; + + resolution = await this.resolve("./events.js", runtimeId); + if (!resolution || resolution.external) { + this.error("Could not resolve events module within @wailsio/runtime package. Please verify that the module is correctly installed and up to date."); + return; + } + + eventsId = resolution.id; + }, + resolveId: { + order: 'pre', + handler(id, importer) { + if ( + bindingsId !== null + && runtimeId !== null + && eventsId !== null + && importer === runtimeId + && id === "./events.js" + ) { + return TYPED_EVENTS_MODULE; + } + return undefined; + } + }, + load(id) { + if (id === TYPED_EVENTS_MODULE) { + return ( + `import ${JSON.stringify(bindingsId)};\n` + + `export * from ${JSON.stringify(eventsId)};` + ); + } + return undefined; + } + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/promises_aplus.test.js b/v3/internal/runtime/desktop/@wailsio/runtime/src/promises_aplus.test.js new file mode 100644 index 000000000..baf51e3c0 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/promises_aplus.test.js @@ -0,0 +1,44 @@ +import * as util from "util"; +import * as V from "vitest"; +import { CancellablePromise } from "./cancellable"; + +// The Promises/A+ suite handles some errors late. +process.on('rejectionHandled', function () {}); + +// The Promises/A+ suite leaves some errors unhandled. +process.on('unhandledRejection', function (reason, promise) { + if (promise instanceof CancellablePromise && reason != null && typeof reason === 'object') { + for (const key of ['dummy', 'other', 'sentinel']) { + if (reason[key] === key) { + return; + } + } + } + throw new Error(`Unhandled rejection at: ${util.inspect(promise)}; reason: ${util.inspect(reason)}`, { cause: reason }); +}); + +// Emulate a minimal version of the mocha BDD API using vitest primitives. +global.context = global.describe = V.describe; +global.specify = global.it = function it(desc, fn) { + let viTestFn = fn; + if (fn?.length) { + viTestFn = () => new Promise((done) => fn(done)); + } + V.it(desc, viTestFn); +} +global.before = function(desc, fn) { V.beforeAll(typeof desc === 'function' ? desc : fn) }; +global.after = function(desc, fn) { V.afterAll(typeof desc === 'function' ? desc : fn) }; +global.beforeEach = function(desc, fn) { V.beforeEach(typeof desc === 'function' ? desc : fn) }; +global.afterEach = function(desc, fn) { V.afterEach(typeof desc === 'function' ? desc : fn) }; + +require('promises-aplus-tests').mocha({ + resolved(value) { + return CancellablePromise.resolve(value); + }, + rejected(reason) { + return CancellablePromise.reject(reason); + }, + deferred() { + return CancellablePromise.withResolvers(); + } +}); diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts new file mode 100644 index 000000000..eaa70d838 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts @@ -0,0 +1,141 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { nanoid } from "./nanoid.js"; + +const runtimeURL = window.location.origin + "/wails/runtime"; + +// Re-export nanoid for custom transport implementations +export { nanoid }; + +// Object Names +export const objectNames = Object.freeze({ + Call: 0, + Clipboard: 1, + Application: 2, + Events: 3, + ContextMenu: 4, + Dialog: 5, + Window: 6, + Screens: 7, + System: 8, + Browser: 9, + CancelCall: 10, + IOS: 11, +}); +export let clientId = nanoid(); + +/** + * RuntimeTransport defines the interface for custom IPC transport implementations. + * Implement this interface to use WebSockets, custom protocols, or any other + * transport mechanism instead of the default HTTP fetch. + */ +export interface RuntimeTransport { + /** + * Send a runtime call and return the response. + * + * @param objectID - The Wails object ID (0=Call, 1=Clipboard, etc.) + * @param method - The method ID to call + * @param windowName - Optional window name + * @param args - Arguments to pass (will be JSON stringified if present) + * @returns Promise that resolves with the response data + */ + call(objectID: number, method: number, windowName: string, args: any): Promise; +} + +/** + * Custom transport implementation (can be set by user) + */ +let customTransport: RuntimeTransport | null = null; + +/** + * Set a custom transport for all Wails runtime calls. + * This allows you to replace the default HTTP fetch transport with + * WebSockets, custom protocols, or any other mechanism. + * + * @param transport - Your custom transport implementation + * + * @example + * ```typescript + * import { setTransport } from '/wails/runtime.js'; + * + * const wsTransport = { + * call: async (objectID, method, windowName, args) => { + * // Your WebSocket implementation + * } + * }; + * + * setTransport(wsTransport); + * ``` + */ +export function setTransport(transport: RuntimeTransport | null): void { + customTransport = transport; +} + +/** + * Get the current transport (useful for extending/wrapping) + */ +export function getTransport(): RuntimeTransport | null { + return customTransport; +} + +/** + * Creates a new runtime caller with specified ID. + * + * @param object - The object to invoke the method on. + * @param windowName - The name of the window. + * @return The new runtime caller function. + */ +export function newRuntimeCaller(object: number, windowName: string = '') { + return function (method: number, args: any = null) { + return runtimeCallWithID(object, method, windowName, args); + }; +} + +async function runtimeCallWithID(objectID: number, method: number, windowName: string, args: any): Promise { + // Use custom transport if available + if (customTransport) { + return customTransport.call(objectID, method, windowName, args); + } + + // Default HTTP fetch transport + let url = new URL(runtimeURL); + + let body: { object: number; method: number, args?: any } = { + object: objectID, + method + } + if (args !== null && args !== undefined) { + body.args = args; + } + + let headers: Record = { + ["x-wails-client-id"]: clientId, + ["Content-Type"]: "application/json" + } + if (windowName) { + headers["x-wails-window-name"] = windowName; + } + + let response = await fetch(url, { + method: 'POST', + headers, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(await response.text()); + } + + if ((response.headers.get("Content-Type")?.indexOf("application/json") ?? -1) !== -1) { + return response.json(); + } else { + return response.text(); + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/screens.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/screens.ts new file mode 100644 index 000000000..c0ecfd7be --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/screens.ts @@ -0,0 +1,88 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +export interface Size { + /** The width of a rectangular area. */ + Width: number; + /** The height of a rectangular area. */ + Height: number; +} + +export interface Rect { + /** The X coordinate of the origin. */ + X: number; + /** The Y coordinate of the origin. */ + Y: number; + /** The width of the rectangle. */ + Width: number; + /** The height of the rectangle. */ + Height: number; +} + +export interface Screen { + /** Unique identifier for the screen. */ + ID: string; + /** Human-readable name of the screen. */ + Name: string; + /** The scale factor of the screen (DPI/96). 1 = standard DPI, 2 = HiDPI (Retina), etc. */ + ScaleFactor: number; + /** The X coordinate of the screen. */ + X: number; + /** The Y coordinate of the screen. */ + Y: number; + /** Contains the width and height of the screen. */ + Size: Size; + /** Contains the bounds of the screen in terms of X, Y, Width, and Height. */ + Bounds: Rect; + /** Contains the physical bounds of the screen in terms of X, Y, Width, and Height (before scaling). */ + PhysicalBounds: Rect; + /** Contains the area of the screen that is actually usable (excluding taskbar and other system UI). */ + WorkArea: Rect; + /** Contains the physical WorkArea of the screen (before scaling). */ + PhysicalWorkArea: Rect; + /** True if this is the primary monitor selected by the user in the operating system. */ + IsPrimary: boolean; + /** The rotation of the screen. */ + Rotation: number; +} + +import { newRuntimeCaller, objectNames } from "./runtime.js"; +const call = newRuntimeCaller(objectNames.Screens); + +const getAll = 0; +const getPrimary = 1; +const getCurrent = 2; + +/** + * Gets all screens. + * + * @returns A promise that resolves to an array of Screen objects. + */ +export function GetAll(): Promise { + return call(getAll); +} + +/** + * Gets the primary screen. + * + * @returns A promise that resolves to the primary screen. + */ +export function GetPrimary(): Promise { + return call(getPrimary); +} + +/** + * Gets the current active screen. + * + * @returns A promise that resolves with the current active screen. + */ +export function GetCurrent(): Promise { + return call(getCurrent); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/system.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/system.ts new file mode 100644 index 000000000..c4fdd6cfe --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/system.ts @@ -0,0 +1,159 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { newRuntimeCaller, objectNames } from "./runtime.js"; + +const call = newRuntimeCaller(objectNames.System); + +const SystemIsDarkMode = 0; +const SystemEnvironment = 1; +const SystemCapabilities = 2; + +const _invoke = (function () { + try { + // Windows WebView2 + if ((window as any).chrome?.webview?.postMessage) { + return (window as any).chrome.webview.postMessage.bind((window as any).chrome.webview); + } + // macOS/iOS WKWebView + else if ((window as any).webkit?.messageHandlers?.['external']?.postMessage) { + return (window as any).webkit.messageHandlers['external'].postMessage.bind((window as any).webkit.messageHandlers['external']); + } + // Android WebView - uses addJavascriptInterface which exposes window.wails.invoke + else if ((window as any).wails?.invoke) { + return (msg: any) => (window as any).wails.invoke(typeof msg === 'string' ? msg : JSON.stringify(msg)); + } + } catch(e) {} + + console.warn('\n%c⚠️ Browser Environment Detected %c\n\n%cOnly UI previews are available in the browser. For full functionality, please run the application in desktop mode.\nMore information at: https://v3.wails.io/learn/build/#using-a-browser-for-development\n', + 'background: #ffffff; color: #000000; font-weight: bold; padding: 4px 8px; border-radius: 4px; border: 2px solid #000000;', + 'background: transparent;', + 'color: #ffffff; font-style: italic; font-weight: bold;'); + return null; +})(); + +export function invoke(msg: any): void { + _invoke?.(msg); +} + +/** + * Retrieves the system dark mode status. + * + * @returns A promise that resolves to a boolean value indicating if the system is in dark mode. + */ +export function IsDarkMode(): Promise { + return call(SystemIsDarkMode); +} + +/** + * Fetches the capabilities of the application from the server. + * + * @returns A promise that resolves to an object containing the capabilities. + */ +export async function Capabilities(): Promise> { + return call(SystemCapabilities); +} + +export interface OSInfo { + /** The branding of the OS. */ + Branding: string; + /** The ID of the OS. */ + ID: string; + /** The name of the OS. */ + Name: string; + /** The version of the OS. */ + Version: string; +} + +export interface EnvironmentInfo { + /** The architecture of the system. */ + Arch: string; + /** True if the application is running in debug mode, otherwise false. */ + Debug: boolean; + /** The operating system in use. */ + OS: string; + /** Details of the operating system. */ + OSInfo: OSInfo; + /** Additional platform information. */ + PlatformInfo: Record; +} + +/** + * Retrieves environment details. + * + * @returns A promise that resolves to an object containing OS and system architecture. + */ +export function Environment(): Promise { + return call(SystemEnvironment); +} + +/** + * Checks if the current operating system is Windows. + * + * @return True if the operating system is Windows, otherwise false. + */ +export function IsWindows(): boolean { + return (window as any)._wails?.environment?.OS === "windows"; +} + +/** + * Checks if the current operating system is Linux. + * + * @returns Returns true if the current operating system is Linux, false otherwise. + */ +export function IsLinux(): boolean { + return (window as any)._wails?.environment?.OS === "linux"; +} + +/** + * Checks if the current environment is a macOS operating system. + * + * @returns True if the environment is macOS, false otherwise. + */ +export function IsMac(): boolean { + return (window as any)._wails?.environment?.OS === "darwin"; +} + +/** + * Checks if the current environment architecture is AMD64. + * + * @returns True if the current environment architecture is AMD64, false otherwise. + */ +export function IsAMD64(): boolean { + return (window as any)._wails?.environment?.Arch === "amd64"; +} + +/** + * Checks if the current architecture is ARM. + * + * @returns True if the current architecture is ARM, false otherwise. + */ +export function IsARM(): boolean { + return (window as any)._wails?.environment?.Arch === "arm"; +} + +/** + * Checks if the current environment is ARM64 architecture. + * + * @returns Returns true if the environment is ARM64 architecture, otherwise returns false. + */ +export function IsARM64(): boolean { + return (window as any)._wails?.environment?.Arch === "arm64"; +} + +/** + * Reports whether the app is being run in debug mode. + * + * @returns True if the app is being run in debug mode. + */ +export function IsDebug(): boolean { + return Boolean((window as any)._wails?.environment?.Debug); +} + diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/utils.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/utils.ts new file mode 100644 index 000000000..35b09463b --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/utils.ts @@ -0,0 +1,105 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/** + * Logs a message to the console with custom formatting. + * + * @param message - The message to be logged. + */ +export function debugLog(message: any) { + // eslint-disable-next-line + console.log( + '%c wails3 %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' + ); +} + +/** + * Checks whether the webview supports the {@link MouseEvent#buttons} property. + * Looking at you macOS High Sierra! + */ +export function canTrackButtons(): boolean { + return (new MouseEvent('mousedown')).buttons === 0; +} + +/** + * Checks whether the browser supports removing listeners by triggering an AbortSignal + * (see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal). + */ +export function canAbortListeners() { + if (!EventTarget || !AbortSignal || !AbortController) + return false; + + let result = true; + + const target = new EventTarget(); + const controller = new AbortController(); + target.addEventListener('test', () => { result = false; }, { signal: controller.signal }); + controller.abort(); + target.dispatchEvent(new CustomEvent('test')); + + return result; +} + +/** + * Resolves the closest HTMLElement ancestor of an event's target. + */ +export function eventTarget(event: Event): HTMLElement { + if (event.target instanceof HTMLElement) { + return event.target; + } else if (!(event.target instanceof HTMLElement) && event.target instanceof Node) { + return event.target.parentElement ?? document.body; + } else { + return document.body; + } +} + +/*** + This technique for proper load detection is taken from HTMX: + + BSD 2-Clause License + + Copyright (c) 2020, Big Sky Software + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ***/ + +let isReady = false; +document.addEventListener('DOMContentLoaded', () => { isReady = true }); + +export function whenReady(callback: () => void) { + if (isReady || document.readyState === 'complete') { + callback(); + } else { + document.addEventListener('DOMContentLoaded', callback); + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts new file mode 100644 index 000000000..7a80fc17a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts @@ -0,0 +1,836 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import {newRuntimeCaller, objectNames} from "./runtime.js"; +import type { Screen } from "./screens.js"; + +// Drop target constants +const DROP_TARGET_ATTRIBUTE = 'data-file-drop-target'; +const DROP_TARGET_ACTIVE_CLASS = 'file-drop-target-active'; +let currentDropTarget: Element | null = null; + +const PositionMethod = 0; +const CenterMethod = 1; +const CloseMethod = 2; +const DisableSizeConstraintsMethod = 3; +const EnableSizeConstraintsMethod = 4; +const FocusMethod = 5; +const ForceReloadMethod = 6; +const FullscreenMethod = 7; +const GetScreenMethod = 8; +const GetZoomMethod = 9; +const HeightMethod = 10; +const HideMethod = 11; +const IsFocusedMethod = 12; +const IsFullscreenMethod = 13; +const IsMaximisedMethod = 14; +const IsMinimisedMethod = 15; +const MaximiseMethod = 16; +const MinimiseMethod = 17; +const NameMethod = 18; +const OpenDevToolsMethod = 19; +const RelativePositionMethod = 20; +const ReloadMethod = 21; +const ResizableMethod = 22; +const RestoreMethod = 23; +const SetPositionMethod = 24; +const SetAlwaysOnTopMethod = 25; +const SetBackgroundColourMethod = 26; +const SetFramelessMethod = 27; +const SetFullscreenButtonEnabledMethod = 28; +const SetMaxSizeMethod = 29; +const SetMinSizeMethod = 30; +const SetRelativePositionMethod = 31; +const SetResizableMethod = 32; +const SetSizeMethod = 33; +const SetTitleMethod = 34; +const SetZoomMethod = 35; +const ShowMethod = 36; +const SizeMethod = 37; +const ToggleFullscreenMethod = 38; +const ToggleMaximiseMethod = 39; +const ToggleFramelessMethod = 40; +const UnFullscreenMethod = 41; +const UnMaximiseMethod = 42; +const UnMinimiseMethod = 43; +const WidthMethod = 44; +const ZoomMethod = 45; +const ZoomInMethod = 46; +const ZoomOutMethod = 47; +const ZoomResetMethod = 48; +const SnapAssistMethod = 49; +const FilesDropped = 50; +const PrintMethod = 51; + +/** + * Finds the nearest drop target element by walking up the DOM tree. + */ +function getDropTargetElement(element: Element | null): Element | null { + if (!element) { + return null; + } + return element.closest(`[${DROP_TARGET_ATTRIBUTE}]`); +} + +/** + * Check if we can use WebView2's postMessageWithAdditionalObjects (Windows) + * Also checks that EnableFileDrop is true for this window. + */ +function canResolveFilePaths(): boolean { + // Must have WebView2's postMessageWithAdditionalObjects API (Windows only) + if ((window as any).chrome?.webview?.postMessageWithAdditionalObjects == null) { + return false; + } + // Must have EnableFileDrop set to true for this window + // This flag is set by the Go backend during runtime initialization + return (window as any)._wails?.flags?.enableFileDrop === true; +} + +/** + * Send file drop to backend via WebView2 (Windows only) + */ +function resolveFilePaths(x: number, y: number, files: File[]): void { + if ((window as any).chrome?.webview?.postMessageWithAdditionalObjects) { + (window as any).chrome.webview.postMessageWithAdditionalObjects(`file:drop:${x}:${y}`, files); + } +} + +// Native drag state (Linux/macOS intercept DOM drag events) +let nativeDragActive = false; + +/** + * Cleans up native drag state and hover effects. + * Called on drop or when drag leaves the window. + */ +function cleanupNativeDrag(): void { + nativeDragActive = false; + if (currentDropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = null; + } +} + +/** + * Called from Go when a file drag enters the window on Linux/macOS. + */ +function handleDragEnter(): void { + // Check if file drops are enabled for this window + if ((window as any)._wails?.flags?.enableFileDrop === false) { + return; // File drops disabled, don't activate drag state + } + nativeDragActive = true; +} + +/** + * Called from Go when a file drag leaves the window on Linux/macOS. + */ +function handleDragLeave(): void { + cleanupNativeDrag(); +} + +/** + * Called from Go during file drag to update hover state on Linux/macOS. + * @param x - X coordinate in CSS pixels + * @param y - Y coordinate in CSS pixels + */ +function handleDragOver(x: number, y: number): void { + if (!nativeDragActive) return; + + // Check if file drops are enabled for this window + if ((window as any)._wails?.flags?.enableFileDrop === false) { + return; // File drops disabled, don't show hover effects + } + + const targetElement = document.elementFromPoint(x, y); + const dropTarget = getDropTargetElement(targetElement); + + if (currentDropTarget && currentDropTarget !== dropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + } + + if (dropTarget) { + dropTarget.classList.add(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = dropTarget; + } else { + currentDropTarget = null; + } +} + + + +// Export the handlers for use by Go via index.ts +export { handleDragEnter, handleDragLeave, handleDragOver }; + +/** + * A record describing the position of a window. + */ +interface Position { + /** The horizontal position of the window. */ + x: number; + /** The vertical position of the window. */ + y: number; +} + +/** + * A record describing the size of a window. + */ +interface Size { + /** The width of the window. */ + width: number; + /** The height of the window. */ + height: number; +} + +// Private field names. +const callerSym = Symbol("caller"); + +class Window { + // Private fields. + private [callerSym]: (message: number, args?: any) => Promise; + + /** + * Initialises a window object with the specified name. + * + * @private + * @param name - The name of the target window. + */ + constructor(name: string = '') { + this[callerSym] = newRuntimeCaller(objectNames.Window, name) + + // bind instance method to make them easily usable in event handlers + for (const method of Object.getOwnPropertyNames(Window.prototype)) { + if ( + method !== "constructor" + && typeof (this as any)[method] === "function" + ) { + (this as any)[method] = (this as any)[method].bind(this); + } + } + } + + /** + * Gets the specified window. + * + * @param name - The name of the window to get. + * @returns The corresponding window object. + */ + Get(name: string): Window { + return new Window(name); + } + + /** + * Returns the absolute position of the window. + * + * @returns The current absolute position of the window. + */ + Position(): Promise { + return this[callerSym](PositionMethod); + } + + /** + * Centers the window on the screen. + */ + Center(): Promise { + return this[callerSym](CenterMethod); + } + + /** + * Closes the window. + */ + Close(): Promise { + return this[callerSym](CloseMethod); + } + + /** + * Disables min/max size constraints. + */ + DisableSizeConstraints(): Promise { + return this[callerSym](DisableSizeConstraintsMethod); + } + + /** + * Enables min/max size constraints. + */ + EnableSizeConstraints(): Promise { + return this[callerSym](EnableSizeConstraintsMethod); + } + + /** + * Focuses the window. + */ + Focus(): Promise { + return this[callerSym](FocusMethod); + } + + /** + * Forces the window to reload the page assets. + */ + ForceReload(): Promise { + return this[callerSym](ForceReloadMethod); + } + + /** + * Switches the window to fullscreen mode. + */ + Fullscreen(): Promise { + return this[callerSym](FullscreenMethod); + } + + /** + * Returns the screen that the window is on. + * + * @returns The screen the window is currently on. + */ + GetScreen(): Promise { + return this[callerSym](GetScreenMethod); + } + + /** + * Returns the current zoom level of the window. + * + * @returns The current zoom level. + */ + GetZoom(): Promise { + return this[callerSym](GetZoomMethod); + } + + /** + * Returns the height of the window. + * + * @returns The current height of the window. + */ + Height(): Promise { + return this[callerSym](HeightMethod); + } + + /** + * Hides the window. + */ + Hide(): Promise { + return this[callerSym](HideMethod); + } + + /** + * Returns true if the window is focused. + * + * @returns Whether the window is currently focused. + */ + IsFocused(): Promise { + return this[callerSym](IsFocusedMethod); + } + + /** + * Returns true if the window is fullscreen. + * + * @returns Whether the window is currently fullscreen. + */ + IsFullscreen(): Promise { + return this[callerSym](IsFullscreenMethod); + } + + /** + * Returns true if the window is maximised. + * + * @returns Whether the window is currently maximised. + */ + IsMaximised(): Promise { + return this[callerSym](IsMaximisedMethod); + } + + /** + * Returns true if the window is minimised. + * + * @returns Whether the window is currently minimised. + */ + IsMinimised(): Promise { + return this[callerSym](IsMinimisedMethod); + } + + /** + * Maximises the window. + */ + Maximise(): Promise { + return this[callerSym](MaximiseMethod); + } + + /** + * Minimises the window. + */ + Minimise(): Promise { + return this[callerSym](MinimiseMethod); + } + + /** + * Returns the name of the window. + * + * @returns The name of the window. + */ + Name(): Promise { + return this[callerSym](NameMethod); + } + + /** + * Opens the development tools pane. + */ + OpenDevTools(): Promise { + return this[callerSym](OpenDevToolsMethod); + } + + /** + * Returns the relative position of the window to the screen. + * + * @returns The current relative position of the window. + */ + RelativePosition(): Promise { + return this[callerSym](RelativePositionMethod); + } + + /** + * Reloads the page assets. + */ + Reload(): Promise { + return this[callerSym](ReloadMethod); + } + + /** + * Returns true if the window is resizable. + * + * @returns Whether the window is currently resizable. + */ + Resizable(): Promise { + return this[callerSym](ResizableMethod); + } + + /** + * Restores the window to its previous state if it was previously minimised, maximised or fullscreen. + */ + Restore(): Promise { + return this[callerSym](RestoreMethod); + } + + /** + * Sets the absolute position of the window. + * + * @param x - The desired horizontal absolute position of the window. + * @param y - The desired vertical absolute position of the window. + */ + SetPosition(x: number, y: number): Promise { + return this[callerSym](SetPositionMethod, { x, y }); + } + + /** + * Sets the window to be always on top. + * + * @param alwaysOnTop - Whether the window should stay on top. + */ + SetAlwaysOnTop(alwaysOnTop: boolean): Promise { + return this[callerSym](SetAlwaysOnTopMethod, { alwaysOnTop }); + } + + /** + * Sets the background colour of the window. + * + * @param r - The desired red component of the window background. + * @param g - The desired green component of the window background. + * @param b - The desired blue component of the window background. + * @param a - The desired alpha component of the window background. + */ + SetBackgroundColour(r: number, g: number, b: number, a: number): Promise { + return this[callerSym](SetBackgroundColourMethod, { r, g, b, a }); + } + + /** + * Removes the window frame and title bar. + * + * @param frameless - Whether the window should be frameless. + */ + SetFrameless(frameless: boolean): Promise { + return this[callerSym](SetFramelessMethod, { frameless }); + } + + /** + * Disables the system fullscreen button. + * + * @param enabled - Whether the fullscreen button should be enabled. + */ + SetFullscreenButtonEnabled(enabled: boolean): Promise { + return this[callerSym](SetFullscreenButtonEnabledMethod, { enabled }); + } + + /** + * Sets the maximum size of the window. + * + * @param width - The desired maximum width of the window. + * @param height - The desired maximum height of the window. + */ + SetMaxSize(width: number, height: number): Promise { + return this[callerSym](SetMaxSizeMethod, { width, height }); + } + + /** + * Sets the minimum size of the window. + * + * @param width - The desired minimum width of the window. + * @param height - The desired minimum height of the window. + */ + SetMinSize(width: number, height: number): Promise { + return this[callerSym](SetMinSizeMethod, { width, height }); + } + + /** + * Sets the relative position of the window to the screen. + * + * @param x - The desired horizontal relative position of the window. + * @param y - The desired vertical relative position of the window. + */ + SetRelativePosition(x: number, y: number): Promise { + return this[callerSym](SetRelativePositionMethod, { x, y }); + } + + /** + * Sets whether the window is resizable. + * + * @param resizable - Whether the window should be resizable. + */ + SetResizable(resizable: boolean): Promise { + return this[callerSym](SetResizableMethod, { resizable }); + } + + /** + * Sets the size of the window. + * + * @param width - The desired width of the window. + * @param height - The desired height of the window. + */ + SetSize(width: number, height: number): Promise { + return this[callerSym](SetSizeMethod, { width, height }); + } + + /** + * Sets the title of the window. + * + * @param title - The desired title of the window. + */ + SetTitle(title: string): Promise { + return this[callerSym](SetTitleMethod, { title }); + } + + /** + * Sets the zoom level of the window. + * + * @param zoom - The desired zoom level. + */ + SetZoom(zoom: number): Promise { + return this[callerSym](SetZoomMethod, { zoom }); + } + + /** + * Shows the window. + */ + Show(): Promise { + return this[callerSym](ShowMethod); + } + + /** + * Returns the size of the window. + * + * @returns The current size of the window. + */ + Size(): Promise { + return this[callerSym](SizeMethod); + } + + /** + * Toggles the window between fullscreen and normal. + */ + ToggleFullscreen(): Promise { + return this[callerSym](ToggleFullscreenMethod); + } + + /** + * Toggles the window between maximised and normal. + */ + ToggleMaximise(): Promise { + return this[callerSym](ToggleMaximiseMethod); + } + + /** + * Toggles the window between frameless and normal. + */ + ToggleFrameless(): Promise { + return this[callerSym](ToggleFramelessMethod); + } + + /** + * Un-fullscreens the window. + */ + UnFullscreen(): Promise { + return this[callerSym](UnFullscreenMethod); + } + + /** + * Un-maximises the window. + */ + UnMaximise(): Promise { + return this[callerSym](UnMaximiseMethod); + } + + /** + * Un-minimises the window. + */ + UnMinimise(): Promise { + return this[callerSym](UnMinimiseMethod); + } + + /** + * Returns the width of the window. + * + * @returns The current width of the window. + */ + Width(): Promise { + return this[callerSym](WidthMethod); + } + + /** + * Zooms the window. + */ + Zoom(): Promise { + return this[callerSym](ZoomMethod); + } + + /** + * Increases the zoom level of the webview content. + */ + ZoomIn(): Promise { + return this[callerSym](ZoomInMethod); + } + + /** + * Decreases the zoom level of the webview content. + */ + ZoomOut(): Promise { + return this[callerSym](ZoomOutMethod); + } + + /** + * Resets the zoom level of the webview content. + */ + ZoomReset(): Promise { + return this[callerSym](ZoomResetMethod); + } + + /** + * Handles file drops originating from platform-specific code (e.g., macOS/Linux native drag-and-drop). + * Gathers information about the drop target element and sends it back to the Go backend. + * + * @param filenames - An array of file paths (strings) that were dropped. + * @param x - The x-coordinate of the drop event (CSS pixels). + * @param y - The y-coordinate of the drop event (CSS pixels). + */ + HandlePlatformFileDrop(filenames: string[], x: number, y: number): void { + // Check if file drops are enabled for this window + if ((window as any)._wails?.flags?.enableFileDrop === false) { + return; // File drops disabled, ignore the drop + } + + const element = document.elementFromPoint(x, y); + const dropTarget = getDropTargetElement(element); + + if (!dropTarget) { + // Drop was not on a designated drop target - ignore + return; + } + + const elementDetails = { + id: dropTarget.id, + classList: Array.from(dropTarget.classList), + attributes: {} as { [key: string]: string }, + }; + for (let i = 0; i < dropTarget.attributes.length; i++) { + const attr = dropTarget.attributes[i]; + elementDetails.attributes[attr.name] = attr.value; + } + + const payload = { + filenames, + x, + y, + elementDetails, + }; + + this[callerSym](FilesDropped, payload); + + // Clean up native drag state after drop + cleanupNativeDrag(); + } + + /* Triggers Windows 11 Snap Assist feature (Windows only). + * This is equivalent to pressing Win+Z and shows snap layout options. + */ + SnapAssist(): Promise { + return this[callerSym](SnapAssistMethod); + } + + /** + * Opens the print dialog for the window. + */ + Print(): Promise { + return this[callerSym](PrintMethod); + } +} + +/** + * The window within which the script is running. + */ +const thisWindow = new Window(''); + +/** + * Sets up global drag and drop event listeners for file drops. + * Handles visual feedback (hover state) and file drop processing. + */ +function setupDropTargetListeners() { + const docElement = document.documentElement; + let dragEnterCounter = 0; + + docElement.addEventListener('dragenter', (event) => { + if (!event.dataTransfer?.types.includes('Files')) { + return; // Only handle file drags, let other drags pass through + } + event.preventDefault(); // Always prevent default to stop browser navigation + // On Windows, check if file drops are enabled for this window + if ((window as any)._wails?.flags?.enableFileDrop === false) { + event.dataTransfer.dropEffect = 'none'; // Show "no drop" cursor + return; // File drops disabled, don't show hover effects + } + dragEnterCounter++; + + const targetElement = document.elementFromPoint(event.clientX, event.clientY); + const dropTarget = getDropTargetElement(targetElement); + + // Update hover state + if (currentDropTarget && currentDropTarget !== dropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + } + + if (dropTarget) { + dropTarget.classList.add(DROP_TARGET_ACTIVE_CLASS); + event.dataTransfer.dropEffect = 'copy'; + currentDropTarget = dropTarget; + } else { + event.dataTransfer.dropEffect = 'none'; + currentDropTarget = null; + } + }, false); + + docElement.addEventListener('dragover', (event) => { + if (!event.dataTransfer?.types.includes('Files')) { + return; // Only handle file drags + } + event.preventDefault(); // Always prevent default to stop browser navigation + // On Windows, check if file drops are enabled for this window + if ((window as any)._wails?.flags?.enableFileDrop === false) { + event.dataTransfer.dropEffect = 'none'; // Show "no drop" cursor + return; // File drops disabled, don't show hover effects + } + + // Update drop target as cursor moves + const targetElement = document.elementFromPoint(event.clientX, event.clientY); + const dropTarget = getDropTargetElement(targetElement); + + if (currentDropTarget && currentDropTarget !== dropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + } + + if (dropTarget) { + if (!dropTarget.classList.contains(DROP_TARGET_ACTIVE_CLASS)) { + dropTarget.classList.add(DROP_TARGET_ACTIVE_CLASS); + } + event.dataTransfer.dropEffect = 'copy'; + currentDropTarget = dropTarget; + } else { + event.dataTransfer.dropEffect = 'none'; + currentDropTarget = null; + } + }, false); + + docElement.addEventListener('dragleave', (event) => { + if (!event.dataTransfer?.types.includes('Files')) { + return; + } + event.preventDefault(); // Always prevent default to stop browser navigation + // On Windows, check if file drops are enabled for this window + if ((window as any)._wails?.flags?.enableFileDrop === false) { + return; + } + + // On Linux/WebKitGTK and macOS, dragleave fires immediately with relatedTarget=null when native + // drag handling is involved. Ignore these spurious events - we'll clean up on drop instead. + if (event.relatedTarget === null) { + return; + } + + dragEnterCounter--; + + if (dragEnterCounter === 0 || + (currentDropTarget && !currentDropTarget.contains(event.relatedTarget as Node))) { + if (currentDropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = null; + } + dragEnterCounter = 0; + } + }, false); + + docElement.addEventListener('drop', (event) => { + if (!event.dataTransfer?.types.includes('Files')) { + return; // Only handle file drops + } + event.preventDefault(); // Always prevent default to stop browser navigation + // On Windows, check if file drops are enabled for this window + if ((window as any)._wails?.flags?.enableFileDrop === false) { + return; + } + dragEnterCounter = 0; + + if (currentDropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = null; + } + + // On Windows, handle file drops via JavaScript + // On macOS/Linux, native code will call HandlePlatformFileDrop + if (canResolveFilePaths()) { + const files: File[] = []; + if (event.dataTransfer.items) { + for (const item of event.dataTransfer.items) { + if (item.kind === 'file') { + const file = item.getAsFile(); + if (file) files.push(file); + } + } + } else if (event.dataTransfer.files) { + for (const file of event.dataTransfer.files) { + files.push(file); + } + } + + if (files.length > 0) { + resolveFilePaths(event.clientX, event.clientY, files); + } + } + }, false); +} + +// Initialize listeners when the script loads +if (typeof window !== "undefined" && typeof document !== "undefined") { + setupDropTargetListeners(); +} + +export default thisWindow; diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/wml.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/wml.ts new file mode 100644 index 000000000..bdf7376cf --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/wml.ts @@ -0,0 +1,209 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { OpenURL } from "./browser.js"; +import { Question } from "./dialogs.js"; +import { Emit } from "./events.js"; +import { canAbortListeners, whenReady } from "./utils.js"; +import Window from "./window.js"; + +/** + * Sends an event with the given name and optional data. + * + * @param eventName - - The name of the event to send. + * @param [data=null] - - Optional data to send along with the event. + */ +function sendEvent(eventName: string, data: any = null): void { + Emit(eventName, data); +} + +/** + * Calls a method on a specified window. + * + * @param windowName - The name of the window to call the method on. + * @param methodName - The name of the method to call. + */ +function callWindowMethod(windowName: string, methodName: string) { + const targetWindow = Window.Get(windowName); + const method = (targetWindow as any)[methodName]; + + if (typeof method !== "function") { + console.error(`Window method '${methodName}' not found`); + return; + } + + try { + method.call(targetWindow); + } catch (e) { + console.error(`Error calling window method '${methodName}': `, e); + } +} + +/** + * Responds to a triggering event by running appropriate WML actions for the current target. + */ +function onWMLTriggered(ev: Event): void { + const element = ev.currentTarget as Element; + + function runEffect(choice = "Yes") { + if (choice !== "Yes") + return; + + const eventType = element.getAttribute('wml-event') || element.getAttribute('data-wml-event'); + const targetWindow = element.getAttribute('wml-target-window') || element.getAttribute('data-wml-target-window') || ""; + const windowMethod = element.getAttribute('wml-window') || element.getAttribute('data-wml-window'); + const url = element.getAttribute('wml-openurl') || element.getAttribute('data-wml-openurl'); + + if (eventType !== null) + sendEvent(eventType); + if (windowMethod !== null) + callWindowMethod(targetWindow, windowMethod); + if (url !== null) + void OpenURL(url); + } + + const confirm = element.getAttribute('wml-confirm') || element.getAttribute('data-wml-confirm'); + + if (confirm) { + Question({ + Title: "Confirm", + Message: confirm, + Detached: false, + Buttons: [ + { Label: "Yes" }, + { Label: "No", IsDefault: true } + ] + }).then(runEffect); + } else { + runEffect(); + } +} + +// Private field names. +const controllerSym = Symbol("controller"); +const triggerMapSym = Symbol("triggerMap"); +const elementCountSym = Symbol("elementCount"); + +/** + * AbortControllerRegistry does not actually remember active event listeners: instead + * it ties them to an AbortSignal and uses an AbortController to remove them all at once. + */ +class AbortControllerRegistry { + // Private fields. + [controllerSym]: AbortController; + + constructor() { + this[controllerSym] = new AbortController(); + } + + /** + * Returns an options object for addEventListener that ties the listener + * to the AbortSignal from the current AbortController. + * + * @param element - An HTML element + * @param triggers - The list of active WML trigger events for the specified elements + */ + set(element: Element, triggers: string[]): AddEventListenerOptions { + return { signal: this[controllerSym].signal }; + } + + /** + * Removes all registered event listeners and resets the registry. + */ + reset(): void { + this[controllerSym].abort(); + this[controllerSym] = new AbortController(); + } +} + +/** + * WeakMapRegistry maps active trigger events to each DOM element through a WeakMap. + * This ensures that the mapping remains private to this module, while still allowing garbage + * collection of the involved elements. + */ +class WeakMapRegistry { + /** Stores the current element-to-trigger mapping. */ + [triggerMapSym]: WeakMap; + /** Counts the number of elements with active WML triggers. */ + [elementCountSym]: number; + + constructor() { + this[triggerMapSym] = new WeakMap(); + this[elementCountSym] = 0; + } + + /** + * Sets active triggers for the specified element. + * + * @param element - An HTML element + * @param triggers - The list of active WML trigger events for the specified element + */ + set(element: Element, triggers: string[]): AddEventListenerOptions { + if (!this[triggerMapSym].has(element)) { this[elementCountSym]++; } + this[triggerMapSym].set(element, triggers); + return {}; + } + + /** + * Removes all registered event listeners. + */ + reset(): void { + if (this[elementCountSym] <= 0) + return; + + for (const element of document.body.querySelectorAll('*')) { + if (this[elementCountSym] <= 0) + break; + + const triggers = this[triggerMapSym].get(element); + if (triggers != null) { this[elementCountSym]--; } + + for (const trigger of triggers || []) + element.removeEventListener(trigger, onWMLTriggered); + } + + this[triggerMapSym] = new WeakMap(); + this[elementCountSym] = 0; + } +} + +const triggerRegistry = canAbortListeners() ? new AbortControllerRegistry() : new WeakMapRegistry(); + +/** + * Adds event listeners to the specified element. + */ +function addWMLListeners(element: Element): void { + const triggerRegExp = /\S+/g; + const triggerAttr = (element.getAttribute('wml-trigger') || element.getAttribute('data-wml-trigger') || "click"); + const triggers: string[] = []; + + let match; + while ((match = triggerRegExp.exec(triggerAttr)) !== null) + triggers.push(match[0]); + + const options = triggerRegistry.set(element, triggers); + for (const trigger of triggers) + element.addEventListener(trigger, onWMLTriggered, options); +} + +/** + * Schedules an automatic reload of WML to be performed as soon as the document is fully loaded. + */ +export function Enable(): void { + whenReady(Reload); +} + +/** + * Reloads the WML page by adding necessary event listeners and browser listeners. + */ +export function Reload(): void { + triggerRegistry.reset(); + document.body.querySelectorAll('[wml-event], [wml-window], [wml-openurl], [data-wml-event], [data-wml-window], [data-wml-openurl]').forEach(addWMLListeners); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.json b/v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.json new file mode 100644 index 000000000..75c3db8e4 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.json @@ -0,0 +1,37 @@ +{ + "include": ["src"], + "exclude": ["./src/**/*.test.*"], + "compilerOptions": { + "composite": true, + + "allowJs": false, + + "noEmitOnError": true, + "declaration": true, + "declarationMap": false, + "declarationDir": "types", + "outDir": "dist", + "rootDir": "src", + + "target": "ES2017", + "module": "ES2015", + "moduleResolution": "bundler", + "isolatedModules": true, + "verbatimModuleSyntax": true, + "stripInternal": true, + "skipLibCheck": true, + + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/vitest.config.ts b/v3/internal/runtime/desktop/@wailsio/runtime/vitest.config.ts new file mode 100644 index 000000000..efb60170a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: 'happy-dom', + testTimeout: 200 + }, +}); diff --git a/v3/internal/runtime/desktop/README.md b/v3/internal/runtime/desktop/README.md new file mode 100644 index 000000000..0ca8ed53e --- /dev/null +++ b/v3/internal/runtime/desktop/README.md @@ -0,0 +1,10 @@ +# README + +The `index.js` file in the `compiled` directory is the entrypoint for the `runtime.js` file that may be +loaded at runtime. This will add `window.wails` and `window._wails` to the global scope. + +NOTE: It is preferable to use the `@wailsio/runtime` package to use the runtime. + +⚠️ Do not rebuild the runtime manually after updating TS code: +the CI pipeline will take care of this. +PRs that touch build artifacts will be blocked from merging. \ No newline at end of file diff --git a/v3/internal/runtime/desktop/compiled/main.js b/v3/internal/runtime/desktop/compiled/main.js new file mode 100644 index 000000000..d2b21dca1 --- /dev/null +++ b/v3/internal/runtime/desktop/compiled/main.js @@ -0,0 +1,22 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import * as Runtime from "../@wailsio/runtime/src"; + +// NOTE: the following methods MUST be imported explicitly because of how esbuild injection works +import { Enable as EnableWML } from "../@wailsio/runtime/src/wml"; +import { debugLog } from "../@wailsio/runtime/src/utils"; + +window.wails = Runtime; +EnableWML(); + +if (DEBUG) { + debugLog("Wails Runtime Loaded") +} diff --git a/v3/internal/runtime/package-lock.json b/v3/internal/runtime/package-lock.json new file mode 100644 index 000000000..f75f8cf7b --- /dev/null +++ b/v3/internal/runtime/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "runtime", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/v3/internal/runtime/runtime.go b/v3/internal/runtime/runtime.go new file mode 100644 index 000000000..d35ff0552 --- /dev/null +++ b/v3/internal/runtime/runtime.go @@ -0,0 +1,21 @@ +package runtime + +import ( + "fmt" + + "encoding/json" +) + +var runtimeInit = `window._wails=window._wails||{};window._wails.flags=window._wails.flags||{};window.wails=window.wails||{};` + +func Core(flags map[string]any) string { + flagsStr := "" + if len(flags) > 0 { + f, err := json.Marshal(flags) + if err == nil { + flagsStr += fmt.Sprintf("window._wails.flags=%s;", f) + } + } + + return runtimeInit + flagsStr + invoke + environment +} diff --git a/v3/internal/runtime/runtime_android.go b/v3/internal/runtime/runtime_android.go new file mode 100644 index 000000000..ab212a891 --- /dev/null +++ b/v3/internal/runtime/runtime_android.go @@ -0,0 +1,16 @@ +//go:build android + +package runtime + +// Android uses window.wails.invoke which is set up via addJavascriptInterface in WailsJSBridge +// We need to log the state to debug why it's not being detected +var invoke = ` +console.log('[Wails Android Runtime] Injecting runtime, window.wails exists:', !!window.wails); +console.log('[Wails Android Runtime] window.wails.invoke exists:', !!(window.wails && window.wails.invoke)); +window._wails.invoke=function(m){ + console.log('[Wails Android Runtime] _wails.invoke called:', m); + return window.wails.invoke(typeof m==='string'?m:JSON.stringify(m)); +}; +console.log('[Wails Android Runtime] Runtime injection complete'); +` +var flags = "" diff --git a/v3/internal/runtime/runtime_darwin.go b/v3/internal/runtime/runtime_darwin.go new file mode 100644 index 000000000..6b2333690 --- /dev/null +++ b/v3/internal/runtime/runtime_darwin.go @@ -0,0 +1,5 @@ +//go:build darwin + +package runtime + +var invoke = "window._wails.invoke=function(msg){window.webkit.messageHandlers.external.postMessage(msg);};" diff --git a/v3/internal/runtime/runtime_dev.go b/v3/internal/runtime/runtime_dev.go new file mode 100644 index 000000000..bb52628cf --- /dev/null +++ b/v3/internal/runtime/runtime_dev.go @@ -0,0 +1,10 @@ +//go:build !production + +package runtime + +import ( + "fmt" + "runtime" +) + +var environment = fmt.Sprintf(`window._wails.environment={"OS":"%s","Arch":"%s","Debug":true};`, runtime.GOOS, runtime.GOARCH) diff --git a/v3/internal/runtime/runtime_linux.go b/v3/internal/runtime/runtime_linux.go new file mode 100644 index 000000000..a1e0418bd --- /dev/null +++ b/v3/internal/runtime/runtime_linux.go @@ -0,0 +1,5 @@ +//go:build linux && !android + +package runtime + +var invoke = "window._wails.invoke=window.webkit.messageHandlers.external.postMessage;" diff --git a/v3/internal/runtime/runtime_prod.go b/v3/internal/runtime/runtime_prod.go new file mode 100644 index 000000000..ec3613551 --- /dev/null +++ b/v3/internal/runtime/runtime_prod.go @@ -0,0 +1,8 @@ +//go:build production + +package runtime + +import "fmt" +import goruntime "runtime" + +var environment = fmt.Sprintf(`window._wails.environment={"OS":"%s","Arch":"%s","Debug":false};`, goruntime.GOOS, goruntime.GOARCH) diff --git a/v3/internal/runtime/runtime_windows.go b/v3/internal/runtime/runtime_windows.go new file mode 100644 index 000000000..59fb6a5a6 --- /dev/null +++ b/v3/internal/runtime/runtime_windows.go @@ -0,0 +1,5 @@ +//go:build windows + +package runtime + +var invoke = `window._wails.invoke=window.chrome.webview.postMessage;` diff --git a/v3/internal/s/s.go b/v3/internal/s/s.go new file mode 100644 index 000000000..1ce187e07 --- /dev/null +++ b/v3/internal/s/s.go @@ -0,0 +1,456 @@ +package s + +import ( + "crypto/md5" + "fmt" + "github.com/google/shlex" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" +) + +var ( + Output io.Writer = io.Discard + IndentSize int + originalOutput io.Writer + currentIndent int + dryRun bool + deferred []func() +) + +func checkError(err error) { + if err != nil { + println("\nERROR:", err.Error()) + os.Exit(1) + } +} + +func mute() { + originalOutput = Output + Output = io.Discard +} + +func unmute() { + Output = originalOutput +} + +func indent() { + currentIndent += IndentSize +} + +func unindent() { + currentIndent -= IndentSize +} + +func log(message string, args ...interface{}) { + indent := strings.Repeat(" ", currentIndent) + _, err := fmt.Fprintf(Output, indent+message+"\n", args...) + checkError(err) +} + +// RENAME a file or directory +func RENAME(source string, target string) { + log("RENAME %s -> %s", source, target) + err := os.Rename(source, target) + checkError(err) +} + +// MUSTDELETE a file. +func MUSTDELETE(filename string) { + log("DELETE %s", filename) + err := os.Remove(filepath.Join(CWD(), filename)) + checkError(err) +} + +// DELETE a file. +func DELETE(filename string) { + log("DELETE %s", filename) + _ = os.Remove(filepath.Join(CWD(), filename)) +} + +func CONTAINS(list string, item string) bool { + result := strings.Contains(list, item) + listTrimmed := list + if len(listTrimmed) > 30 { + listTrimmed = listTrimmed[:30] + "..." + } + log("CONTAINS %s in %s: %t", item, listTrimmed, result) + return result +} + +func SETENV(key string, value string) { + log("SETENV %s=%s", key, value) + err := os.Setenv(key, value) + checkError(err) +} + +func CD(dir string) { + err := os.Chdir(dir) + checkError(err) + log("CD %s", dir) +} +func MKDIR(path string, mode ...os.FileMode) { + var perms os.FileMode + perms = 0755 + if len(mode) == 1 { + perms = mode[0] + } + log("MKDIR %s (perms: %v)", path, perms) + err := os.MkdirAll(path, perms) + checkError(err) +} + +// ENDIR ensures that the path gets created if it doesn't exist +func ENDIR(path string, mode ...os.FileMode) { + var perms os.FileMode + perms = 0755 + if len(mode) == 1 { + perms = mode[0] + } + _ = os.MkdirAll(path, perms) +} + +// COPYDIR recursively copies a directory tree, attempting to preserve permissions. +// Source directory must exist, destination directory must *not* exist. +// Symlinks are ignored and skipped. +// Credit: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04 +func COPYDIR(src string, dst string) { + log("COPYDIR %s -> %s", src, dst) + src = filepath.Clean(src) + dst = filepath.Clean(dst) + + si, err := os.Stat(src) + checkError(err) + if !si.IsDir() { + checkError(fmt.Errorf("source is not a directory")) + } + + _, err = os.Stat(dst) + if err != nil && !os.IsNotExist(err) { + checkError(err) + } + if err == nil { + checkError(fmt.Errorf("destination already exists")) + } + + indent() + MKDIR(dst) + + entries, err := os.ReadDir(src) + checkError(err) + + for _, entry := range entries { + srcPath := filepath.Join(src, entry.Name()) + dstPath := filepath.Join(dst, entry.Name()) + + if entry.IsDir() { + COPYDIR(srcPath, dstPath) + } else { + // Skip symlinks. + if entry.Type()&os.ModeSymlink != 0 { + continue + } + + COPY(srcPath, dstPath) + } + } + unindent() +} + +// COPYDIR2 recursively copies a directory tree, attempting to preserve permissions. +// Source directory must exist, destination directory can exist. +// Symlinks are ignored and skipped. +// Credit: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04 +func COPYDIR2(src string, dst string) { + log("COPYDIR %s -> %s", src, dst) + src = filepath.Clean(src) + dst = filepath.Clean(dst) + + si, err := os.Stat(src) + checkError(err) + if !si.IsDir() { + checkError(fmt.Errorf("source is not a directory")) + } + + indent() + MKDIR(dst) + + entries, err := os.ReadDir(src) + checkError(err) + + for _, entry := range entries { + srcPath := filepath.Join(src, entry.Name()) + dstPath := filepath.Join(dst, entry.Name()) + + if entry.IsDir() { + COPYDIR(srcPath, dstPath) + } else { + // Skip symlinks. + if entry.Type()&os.ModeSymlink != 0 { + continue + } + + COPY(srcPath, dstPath) + } + } + unindent() +} + +func SYMLINK(source string, target string) { + // trim string to first 30 chars + var trimTarget = target + if len(trimTarget) > 30 { + trimTarget = trimTarget[:30] + "..." + } + log("SYMLINK %s -> %s", source, trimTarget) + err := os.Symlink(source, target) + checkError(err) +} + +// COPY file from source to target +func COPY(source string, target string) { + log("COPY %s -> %s", source, target) + src, err := os.Open(source) + checkError(err) + defer closefile(src) + if ISDIR(target) { + target = filepath.Join(target, filepath.Base(source)) + } + d, err := os.Create(target) + checkError(err) + _, err = io.Copy(d, src) + checkError(err) +} + +// Move file from source to target +func MOVE(source string, target string) { + // If target is a directory, append the source filename + if ISDIR(target) { + target = filepath.Join(target, filepath.Base(source)) + } + log("MOVE %s -> %s", source, target) + err := os.Rename(source, target) + checkError(err) +} + +func CWD() string { + result, err := os.Getwd() + checkError(err) + log("CWD %s", result) + return result +} + +func RMDIR(target string) { + log("RMDIR %s", target) + err := os.RemoveAll(target) + checkError(err) +} + +func RM(target string) { + log("RM %s", target) + err := os.Remove(target) + checkError(err) +} + +func ECHO(message string) { + println(message) +} + +func TOUCH(filepath string) { + log("TOUCH %s", filepath) + f, err := os.Create(filepath) + checkError(err) + closefile(f) +} + +func EXEC(command string) ([]byte, error) { + log("EXEC %s", command) + + // Split input using shlex + args, err := shlex.Split(command) + checkError(err) + // Execute command + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = CWD() + cmd.Env = os.Environ() + return cmd.CombinedOutput() +} + +func CHMOD(path string, mode os.FileMode) { + log("CHMOD %s %v", path, mode) + err := os.Chmod(path, mode) + checkError(err) +} + +// EXISTS - Returns true if the given path exists +func EXISTS(path string) bool { + _, err := os.Lstat(path) + log("EXISTS %s -> %t", path, err == nil) + return err == nil +} + +// ISDIR returns true if the given directory exists +func ISDIR(path string) bool { + fi, err := os.Lstat(path) + if err != nil { + return false + } + + return fi.Mode().IsDir() +} + +// ISDIREMPTY returns true if the given directory is empty +func ISDIREMPTY(dir string) bool { + + // CREDIT: https://stackoverflow.com/a/30708914/8325411 + f, err := os.Open(dir) + checkError(err) + defer closefile(f) + + _, err = f.Readdirnames(1) // Or f.Readdir(1) + if err == io.EOF { + return true + } + return false +} + +// ISFILE returns true if the given file exists +func ISFILE(path string) bool { + fi, err := os.Lstat(path) + if err != nil { + return false + } + + return fi.Mode().IsRegular() +} + +// SUBDIRS returns a list of subdirectories for the given directory +func SUBDIRS(rootDir string) []string { + var result []string + + // Iterate root dir + err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error { + checkError(err) + // If we have a directory, save it + if info.IsDir() { + result = append(result, path) + } + return nil + }) + checkError(err) + return result +} + +// SAVESTRING will create a file with the given string +func SAVESTRING(filename string, data string) { + log("SAVESTRING %s", filename) + mute() + SAVEBYTES(filename, []byte(data)) + unmute() +} + +// LOADSTRING returns the contents of the given filename as a string +func LOADSTRING(filename string) string { + log("LOADSTRING %s", filename) + mute() + data := LOADBYTES(filename) + unmute() + return string(data) +} + +// SAVEBYTES will create a file with the given string +func SAVEBYTES(filename string, data []byte) { + log("SAVEBYTES %s", filename) + err := os.WriteFile(filename, data, 0755) + checkError(err) +} + +// LOADBYTES returns the contents of the given filename as a string +func LOADBYTES(filename string) []byte { + log("LOADBYTES %s", filename) + data, err := os.ReadFile(filename) + checkError(err) + return data +} + +func closefile(f *os.File) { + err := f.Close() + checkError(err) +} + +// MD5FILE returns the md5sum of the given file +func MD5FILE(filename string) string { + f, err := os.Open(filename) + checkError(err) + defer closefile(f) + + h := md5.New() + _, err = io.Copy(h, f) + checkError(err) + + return fmt.Sprintf("%x", h.Sum(nil)) +} + +// Sub is the substitution type +type Sub map[string]string + +// REPLACEALL replaces all substitution keys with associated values in the given file +func REPLACEALL(filename string, substitutions Sub) { + log("REPLACEALL %s (%v)", filename, substitutions) + data := LOADSTRING(filename) + for old, newText := range substitutions { + data = strings.ReplaceAll(data, old, newText) + } + SAVESTRING(filename, data) +} + +func DOWNLOAD(url string, target string) { + log("DOWNLOAD %s -> %s", url, target) + // create HTTP client + resp, err := http.Get(url) + checkError(err) + defer resp.Body.Close() + + out, err := os.Create(target) + checkError(err) + defer out.Close() + + // Write the body to file + _, err = io.Copy(out, resp.Body) + checkError(err) +} + +func FINDFILES(root string, filenames ...string) []string { + var result []string + // Walk the root directory trying to find all the files + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + checkError(err) + // If we have a file, check if it is in the list + if info.Mode().IsRegular() { + for _, filename := range filenames { + if info.Name() == filename { + result = append(result, path) + } + } + } + return nil + }) + checkError(err) + log("FINDFILES in %s -> [%v]", root, strings.Join(result, ", ")) + return result +} + +func DEFER(fn func()) { + log("DEFER") + deferred = append(deferred, fn) +} + +func CALLDEFER() { + log("CALLDEFER") + for _, fn := range deferred { + fn() + } +} diff --git a/v3/internal/service/service.go b/v3/internal/service/service.go new file mode 100644 index 000000000..76c1c2560 --- /dev/null +++ b/v3/internal/service/service.go @@ -0,0 +1,36 @@ +package service + +import ( + "embed" + "fmt" + "io/fs" + "os" + "path/filepath" + + "github.com/wailsapp/wails/v3/internal/flags" + + "github.com/leaanthony/gosod" + + "github.com/samber/lo" +) + +//go:embed template +var serviceTemplate embed.FS + +type TemplateOptions struct { + *flags.ServiceInit +} + +func Install(options *flags.ServiceInit) error { + + if options.OutputDir == "." || options.OutputDir == "" { + options.OutputDir = filepath.Join(lo.Must(os.Getwd()), options.Name) + } + fmt.Printf("Generating service '%s' into '%s'\n", options.Name, options.OutputDir) + tfs, err := fs.Sub(serviceTemplate, "template") + if err != nil { + return err + } + + return gosod.New(tfs).Extract(options.OutputDir, options) +} diff --git a/v3/internal/service/template/README.tmpl.md b/v3/internal/service/template/README.tmpl.md new file mode 100644 index 000000000..065021b6a --- /dev/null +++ b/v3/internal/service/template/README.tmpl.md @@ -0,0 +1,129 @@ +# Wails v3 Service Template + +This README provides an overview of the Wails v3 service template and explains how to adapt it to create your own custom service. + +## Overview + +The service template provides a basic structure for creating a Wails v3 service. A service in Wails v3 is a Go package that can be integrated into your Wails application to provide specific functionality, handle HTTP requests, and interact with the frontend. + +## Template Structure + +The template defines a `MyService` struct and several methods: + +### MyService Struct + +```go +type MyService struct { + ctx context.Context + options application.ServiceOptions +} +``` + +This is the main service struct. You can rename it to better reflect your service's purpose. The struct holds a context and service options, which are set during startup. + +### ServiceName Method + +```go +func (p *MyService) ServiceName() string +``` + +This method returns the name of the service. It's used to identify the service within the Wails application. + +### ServiceStartup Method + +```go +func (p *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error +``` + +This method is called when the app is starting up. Use it to initialize resources, set up connections, or perform any necessary setup tasks. +It receives a context and service options, which are stored in the service struct. + +### ServiceShutdown Method + +```go +func (p *MyService) ServiceShutdown() error +``` + +This method is called when the app is shutting down. Use it to clean up resources, close connections, or perform any necessary cleanup tasks. + +### ServeHTTP Method + +```go +func (p *MyService) ServeHTTP(w http.ResponseWriter, r *http.Request) +``` + +This method handles HTTP requests to the service. It's called when the frontend makes an HTTP request to the backend +at the path specified in the `Route` field of the service options. + +### Service Methods + +```go +func (p *MyService) Greet(name string) string +``` + +This is an example of a service method. You can add as many methods as you need. These methods can be called from the frontend. + +## Adapting the Template + +To create your own service: + +1. Rename the `MyService` struct to reflect your service's purpose (e.g., `DatabaseService`, `AuthService`). +2. Update the `ServiceName` method to return your service's unique identifier. +3. Implement the `ServiceStartup` method to initialize your service. This might include setting up database connections, loading configuration, etc. +4. If needed, implement the `ServiceShutdown` method to properly clean up resources when the application closes. +5. If your service needs to handle HTTP requests, implement the `ServeHTTP` method. Use this to create API endpoints, serve files, or handle any HTTP interactions. +6. Add your own methods to the service. These can include database operations, business logic, or any functionality your service needs to provide. +7. If your service requires configuration, consider adding a `Config` struct and a `New` function to create and configure your service. + +## Example: Database Service + +Here's how you might adapt the template for a database service: + +```go +type DatabaseService struct { + ctx context.Context + options application.ServiceOptions + db *sql.DB +} + +func (s *DatabaseService) Name() string { + return "github.com/myname/DatabaseService" +} + +func (s *DatabaseService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + s.ctx = ctx + s.options = options + // Initialize database connection + var err error + s.db, err = sql.Open("mysql", "user:password@/dbname") + return err +} + +func (s *DatabaseService) ServiceShutdown() error { + return s.db.Close() +} + +func (s *DatabaseService) GetUser(id int) (User, error) { + // Implement database query +} + +// Add more methods as needed +``` + +## Long-running tasks + +If your service needs to perform long-running tasks, consider using goroutines and channels to manage these tasks. +You can use the `context.Context` to listen for when the application shuts down: + +```go +func (s *DatabaseService) longRunningTask() { + for { + select { + case <-s.ctx.Done(): + // Cleanup and exit + return + // Perform long-running task + } + } +} +``` diff --git a/v3/internal/service/template/go.mod.tmpl b/v3/internal/service/template/go.mod.tmpl new file mode 100644 index 000000000..dc8719753 --- /dev/null +++ b/v3/internal/service/template/go.mod.tmpl @@ -0,0 +1,12 @@ +module {{.Name}} + +go 1.23 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.7 + +require ( + github.com/imdario/mergo v0.3.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect +) diff --git a/v3/internal/service/template/go.sum b/v3/internal/service/template/go.sum new file mode 100644 index 000000000..991eadf04 --- /dev/null +++ b/v3/internal/service/template/go.sum @@ -0,0 +1,20 @@ +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/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY= +github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +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/wails/v3 v3.0.0-alpha.7 h1:LNX2EnbxTEYJYICJT8UkuzoGVNalRizTNGBY47endmk= +github.com/wailsapp/wails/v3 v3.0.0-alpha.7/go.mod h1:lBz4zedFxreJBoVpMe9u89oo4IE3IlyHJg5rOWnGNR0= +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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/v3/internal/service/template/service.go.tmpl b/v3/internal/service/template/service.go.tmpl new file mode 100644 index 000000000..781e9efff --- /dev/null +++ b/v3/internal/service/template/service.go.tmpl @@ -0,0 +1,60 @@ +package {{.Name}} + +import ( + "context" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// ---------------- Service Setup ---------------- +// This is the main service struct. It can be named anything you like. +// Both the ServiceStartup() and ServiceShutdown() methods are called synchronously when the app starts and stops. +// Changing the name of this struct will change the name of the services class in the frontend +// Bound methods will exist inside frontend/bindings/github.com/user/{{.Name}} under the name of the struct +type MyService struct{ + ctx context.Context + options application.ServiceOptions +} + +// ServiceName is the name of the service +func (p *MyService) ServiceName() string { + return "{{.Name}}" +} + +// ServiceStartup is called when the app is starting up. You can use this to +// initialise any resources you need. You can also access the application +// instance via the app property. +// OPTIONAL: This method is optional. +func (p *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + p.ctx = ctx + p.options = options + return nil +} + +// ServiceShutdown is called when the app is shutting down via runtime.Quit() call +// You can use this to clean up any resources you have allocated +// OPTIONAL: This method is optional. +func (p *MyService) ServiceShutdown() error { + return nil +} + +// ServeHTTP is called when the app is running and the frontend makes an HTTP request to the backend at the path +// specified in the `Route` field of the service Options. +// OPTIONAL: This method is optional. +func (p *MyService) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // You can use the request to get the path, query parameters, headers, etc. + // You can also use the response to set the status code, headers, body etc. + // Consult the net/http documentation for more information: https://pkg.go.dev/net/http + + // Log the request to the console + log.Printf("Received request: %s %s", r.Method, r.URL.Path) +} + +// ---------------- Service Methods ---------------- +// Service methods are just normal Go methods. You can add as many as you like. +// The only requirement is that they are exported (start with a capital letter). +// You can also return any type that is JSON serializable. +// See https://golang.org/pkg/encoding/json/#Marshal for more information. + +func (p *MyService) Greet(name string) string { + return "Hello " + name +} diff --git a/v3/internal/service/template/service.tmpl.yml b/v3/internal/service/template/service.tmpl.yml new file mode 100644 index 000000000..bd018461e --- /dev/null +++ b/v3/internal/service/template/service.tmpl.yml @@ -0,0 +1,8 @@ +# This is the plugin definition file for the "{{.Name}}" plugin. +Name: "{{.Name}}" +Description: "{{.Description}}" +Author: "{{.Author}}" +Version: "{{.Version}}" +Website: "{{.Website}}" +Repository: "{{.Repository}}" +License: "{{.License}}" diff --git a/v3/internal/setupwizard/defaults.go b/v3/internal/setupwizard/defaults.go new file mode 100644 index 000000000..a5847cb68 --- /dev/null +++ b/v3/internal/setupwizard/defaults.go @@ -0,0 +1,30 @@ +package setupwizard + +import ( + "github.com/wailsapp/wails/v3/internal/defaults" +) + +// Re-export types for convenience +type GlobalDefaults = defaults.GlobalDefaults +type AuthorDefaults = defaults.AuthorDefaults +type ProjectDefaults = defaults.ProjectDefaults + +// DefaultGlobalDefaults returns sensible defaults for first-time users +func DefaultGlobalDefaults() GlobalDefaults { + return defaults.Default() +} + +// GetDefaultsPath returns the path to the defaults.yaml file +func GetDefaultsPath() (string, error) { + return defaults.GetDefaultsPath() +} + +// LoadGlobalDefaults loads the global defaults from the config file +func LoadGlobalDefaults() (GlobalDefaults, error) { + return defaults.Load() +} + +// SaveGlobalDefaults saves the global defaults to the config file +func SaveGlobalDefaults(d GlobalDefaults) error { + return defaults.Save(d) +} diff --git a/v3/internal/setupwizard/frontend/dist/assets/index-C9VCVRfM.js b/v3/internal/setupwizard/frontend/dist/assets/index-C9VCVRfM.js new file mode 100644 index 000000000..3a5ce1f7a --- /dev/null +++ b/v3/internal/setupwizard/frontend/dist/assets/index-C9VCVRfM.js @@ -0,0 +1,48 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const s of i)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(i){if(i.ep)return;i.ep=!0;const s=n(i);fetch(i.href,s)}})();function zm(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Gd={exports:{}},Ts={},Qd={exports:{}},I={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var qr=Symbol.for("react.element"),Bm=Symbol.for("react.portal"),Um=Symbol.for("react.fragment"),Wm=Symbol.for("react.strict_mode"),bm=Symbol.for("react.profiler"),$m=Symbol.for("react.provider"),Hm=Symbol.for("react.context"),Km=Symbol.for("react.forward_ref"),Gm=Symbol.for("react.suspense"),Qm=Symbol.for("react.memo"),Ym=Symbol.for("react.lazy"),Tu=Symbol.iterator;function Xm(e){return e===null||typeof e!="object"?null:(e=Tu&&e[Tu]||e["@@iterator"],typeof e=="function"?e:null)}var Yd={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Xd=Object.assign,Zd={};function Xn(e,t,n){this.props=e,this.context=t,this.refs=Zd,this.updater=n||Yd}Xn.prototype.isReactComponent={};Xn.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};Xn.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function qd(){}qd.prototype=Xn.prototype;function Ya(e,t,n){this.props=e,this.context=t,this.refs=Zd,this.updater=n||Yd}var Xa=Ya.prototype=new qd;Xa.constructor=Ya;Xd(Xa,Xn.prototype);Xa.isPureReactComponent=!0;var Cu=Array.isArray,Jd=Object.prototype.hasOwnProperty,Za={current:null},ef={key:!0,ref:!0,__self:!0,__source:!0};function tf(e,t,n){var r,i={},s=null,o=null;if(t!=null)for(r in t.ref!==void 0&&(o=t.ref),t.key!==void 0&&(s=""+t.key),t)Jd.call(t,r)&&!ef.hasOwnProperty(r)&&(i[r]=t[r]);var a=arguments.length-2;if(a===1)i.children=n;else if(1>>1,oe=E[H];if(0>>1;Hi($s,V))Qti(ui,$s)?(E[H]=ui,E[Qt]=V,H=Qt):(E[H]=$s,E[Gt]=V,H=Gt);else if(Qti(ui,V))E[H]=ui,E[Qt]=V,H=Qt;else break e}}return L}function i(E,L){var V=E.sortIndex-L.sortIndex;return V!==0?V:E.id-L.id}if(typeof performance=="object"&&typeof performance.now=="function"){var s=performance;e.unstable_now=function(){return s.now()}}else{var o=Date,a=o.now();e.unstable_now=function(){return o.now()-a}}var l=[],u=[],c=1,d=null,f=3,y=!1,v=!1,x=!1,S=typeof setTimeout=="function"?setTimeout:null,m=typeof clearTimeout=="function"?clearTimeout:null,p=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function g(E){for(var L=n(u);L!==null;){if(L.callback===null)r(u);else if(L.startTime<=E)r(u),L.sortIndex=L.expirationTime,t(l,L);else break;L=n(u)}}function w(E){if(x=!1,g(E),!v)if(n(l)!==null)v=!0,se(k);else{var L=n(u);L!==null&&B(w,L.startTime-E)}}function k(E,L){v=!1,x&&(x=!1,m(C),C=-1),y=!0;var V=f;try{for(g(L),d=n(l);d!==null&&(!(d.expirationTime>L)||E&&!X());){var H=d.callback;if(typeof H=="function"){d.callback=null,f=d.priorityLevel;var oe=H(d.expirationTime<=L);L=e.unstable_now(),typeof oe=="function"?d.callback=oe:d===n(l)&&r(l),g(L)}else r(l);d=n(l)}if(d!==null)var li=!0;else{var Gt=n(u);Gt!==null&&B(w,Gt.startTime-L),li=!1}return li}finally{d=null,f=V,y=!1}}var T=!1,P=null,C=-1,R=5,M=-1;function X(){return!(e.unstable_now()-ME||125H?(E.sortIndex=V,t(u,E),n(l)===null&&E===n(u)&&(x?(m(C),C=-1):x=!0,B(w,V-H))):(E.sortIndex=oe,t(l,E),v||y||(v=!0,se(k))),E},e.unstable_shouldYield=X,e.unstable_wrapCallback=function(E){var L=f;return function(){var V=f;f=L;try{return E.apply(this,arguments)}finally{f=V}}}})(af);of.exports=af;var l0=of.exports;/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var u0=N,Ae=l0;function j(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),_o=Object.prototype.hasOwnProperty,c0=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,ju={},Eu={};function d0(e){return _o.call(Eu,e)?!0:_o.call(ju,e)?!1:c0.test(e)?Eu[e]=!0:(ju[e]=!0,!1)}function f0(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function h0(e,t,n,r){if(t===null||typeof t>"u"||f0(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function Se(e,t,n,r,i,s,o){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=i,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=s,this.removeEmptyString=o}var fe={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){fe[e]=new Se(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];fe[t]=new Se(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){fe[e]=new Se(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){fe[e]=new Se(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){fe[e]=new Se(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){fe[e]=new Se(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){fe[e]=new Se(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){fe[e]=new Se(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){fe[e]=new Se(e,5,!1,e.toLowerCase(),null,!1,!1)});var Ja=/[\-:]([a-z])/g;function el(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(Ja,el);fe[t]=new Se(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(Ja,el);fe[t]=new Se(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(Ja,el);fe[t]=new Se(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){fe[e]=new Se(e,1,!1,e.toLowerCase(),null,!1,!1)});fe.xlinkHref=new Se("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){fe[e]=new Se(e,1,!1,e.toLowerCase(),null,!0,!0)});function tl(e,t,n,r){var i=fe.hasOwnProperty(t)?fe[t]:null;(i!==null?i.type!==0:r||!(2a||i[o]!==s[a]){var l=` +`+i[o].replace(" at new "," at ");return e.displayName&&l.includes("")&&(l=l.replace("",e.displayName)),l}while(1<=o&&0<=a);break}}}finally{Gs=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?dr(e):""}function p0(e){switch(e.tag){case 5:return dr(e.type);case 16:return dr("Lazy");case 13:return dr("Suspense");case 19:return dr("SuspenseList");case 0:case 2:case 15:return e=Qs(e.type,!1),e;case 11:return e=Qs(e.type.render,!1),e;case 1:return e=Qs(e.type,!0),e;default:return""}}function Bo(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case vn:return"Fragment";case yn:return"Portal";case Oo:return"Profiler";case nl:return"StrictMode";case Fo:return"Suspense";case zo:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case cf:return(e.displayName||"Context")+".Consumer";case uf:return(e._context.displayName||"Context")+".Provider";case rl:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case il:return t=e.displayName||null,t!==null?t:Bo(e.type)||"Memo";case Ct:t=e._payload,e=e._init;try{return Bo(e(t))}catch{}}return null}function m0(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Bo(t);case 8:return t===nl?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function Ft(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function ff(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function g0(e){var t=ff(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var i=n.get,s=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return i.call(this)},set:function(o){r=""+o,s.call(this,o)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(o){r=""+o},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function fi(e){e._valueTracker||(e._valueTracker=g0(e))}function hf(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=ff(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Gi(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Uo(e,t){var n=t.checked;return Y({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Du(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=Ft(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function pf(e,t){t=t.checked,t!=null&&tl(e,"checked",t,!1)}function Wo(e,t){pf(e,t);var n=Ft(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?bo(e,t.type,n):t.hasOwnProperty("defaultValue")&&bo(e,t.type,Ft(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Mu(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function bo(e,t,n){(t!=="number"||Gi(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var fr=Array.isArray;function Rn(e,t,n,r){if(e=e.options,t){t={};for(var i=0;i"+t.valueOf().toString()+"",t=hi.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Mr(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var gr={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},y0=["Webkit","ms","Moz","O"];Object.keys(gr).forEach(function(e){y0.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),gr[t]=gr[e]})});function vf(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||gr.hasOwnProperty(e)&&gr[e]?(""+t).trim():t+"px"}function xf(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,i=vf(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,i):e[n]=i}}var v0=Y({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Ko(e,t){if(t){if(v0[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(j(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(j(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(j(61))}if(t.style!=null&&typeof t.style!="object")throw Error(j(62))}}function Go(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Qo=null;function sl(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Yo=null,In=null,_n=null;function Vu(e){if(e=ti(e)){if(typeof Yo!="function")throw Error(j(280));var t=e.stateNode;t&&(t=Ns(t),Yo(e.stateNode,e.type,t))}}function wf(e){In?_n?_n.push(e):_n=[e]:In=e}function kf(){if(In){var e=In,t=_n;if(_n=In=null,Vu(e),t)for(e=0;e>>=0,e===0?32:31-(D0(e)/M0|0)|0}var pi=64,mi=4194304;function hr(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Zi(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,i=e.suspendedLanes,s=e.pingedLanes,o=n&268435455;if(o!==0){var a=o&~i;a!==0?r=hr(a):(s&=o,s!==0&&(r=hr(s)))}else o=n&~i,o!==0?r=hr(o):s!==0&&(r=hr(s));if(r===0)return 0;if(t!==0&&t!==r&&!(t&i)&&(i=r&-r,s=t&-t,i>=s||i===16&&(s&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function Jr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Je(t),e[t]=n}function R0(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=vr),Wu=" ",bu=!1;function Uf(e,t){switch(e){case"keyup":return lg.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Wf(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var xn=!1;function cg(e,t){switch(e){case"compositionend":return Wf(t);case"keypress":return t.which!==32?null:(bu=!0,Wu);case"textInput":return e=t.data,e===Wu&&bu?null:e;default:return null}}function dg(e,t){if(xn)return e==="compositionend"||!hl&&Uf(e,t)?(e=zf(),Ri=cl=Nt=null,xn=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=Gu(n)}}function Kf(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Kf(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Gf(){for(var e=window,t=Gi();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Gi(e.document)}return t}function pl(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function wg(e){var t=Gf(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Kf(n.ownerDocument.documentElement,n)){if(r!==null&&pl(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var i=n.textContent.length,s=Math.min(r.start,i);r=r.end===void 0?s:Math.min(r.end,i),!e.extend&&s>r&&(i=r,r=s,s=i),i=Qu(n,s);var o=Qu(n,r);i&&o&&(e.rangeCount!==1||e.anchorNode!==i.node||e.anchorOffset!==i.offset||e.focusNode!==o.node||e.focusOffset!==o.offset)&&(t=t.createRange(),t.setStart(i.node,i.offset),e.removeAllRanges(),s>r?(e.addRange(t),e.extend(o.node,o.offset)):(t.setEnd(o.node,o.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,wn=null,ta=null,wr=null,na=!1;function Yu(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;na||wn==null||wn!==Gi(r)||(r=wn,"selectionStart"in r&&pl(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),wr&&_r(wr,r)||(wr=r,r=es(ta,"onSelect"),0Tn||(e.current=la[Tn],la[Tn]=null,Tn--)}function z(e,t){Tn++,la[Tn]=e.current,e.current=t}var zt={},ye=bt(zt),je=bt(!1),un=zt;function Wn(e,t){var n=e.type.contextTypes;if(!n)return zt;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var i={},s;for(s in n)i[s]=t[s];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=i),i}function Ee(e){return e=e.childContextTypes,e!=null}function ns(){W(je),W(ye)}function nc(e,t,n){if(ye.current!==zt)throw Error(j(168));z(ye,t),z(je,n)}function nh(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var i in r)if(!(i in t))throw Error(j(108,m0(e)||"Unknown",i));return Y({},n,r)}function rs(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||zt,un=ye.current,z(ye,e),z(je,je.current),!0}function rc(e,t,n){var r=e.stateNode;if(!r)throw Error(j(169));n?(e=nh(e,t,un),r.__reactInternalMemoizedMergedChildContext=e,W(je),W(ye),z(ye,e)):W(je),z(je,n)}var ct=null,Ds=!1,lo=!1;function rh(e){ct===null?ct=[e]:ct.push(e)}function Ag(e){Ds=!0,rh(e)}function $t(){if(!lo&&ct!==null){lo=!0;var e=0,t=O;try{var n=ct;for(O=1;e>=o,i-=o,dt=1<<32-Je(t)+i|n<C?(R=P,P=null):R=P.sibling;var M=f(m,P,g[C],w);if(M===null){P===null&&(P=R);break}e&&P&&M.alternate===null&&t(m,P),p=s(M,p,C),T===null?k=M:T.sibling=M,T=M,P=R}if(C===g.length)return n(m,P),b&&Xt(m,C),k;if(P===null){for(;CC?(R=P,P=null):R=P.sibling;var X=f(m,P,M.value,w);if(X===null){P===null&&(P=R);break}e&&P&&X.alternate===null&&t(m,P),p=s(X,p,C),T===null?k=X:T.sibling=X,T=X,P=R}if(M.done)return n(m,P),b&&Xt(m,C),k;if(P===null){for(;!M.done;C++,M=g.next())M=d(m,M.value,w),M!==null&&(p=s(M,p,C),T===null?k=M:T.sibling=M,T=M);return b&&Xt(m,C),k}for(P=r(m,P);!M.done;C++,M=g.next())M=y(P,m,C,M.value,w),M!==null&&(e&&M.alternate!==null&&P.delete(M.key===null?C:M.key),p=s(M,p,C),T===null?k=M:T.sibling=M,T=M);return e&&P.forEach(function(Ke){return t(m,Ke)}),b&&Xt(m,C),k}function S(m,p,g,w){if(typeof g=="object"&&g!==null&&g.type===vn&&g.key===null&&(g=g.props.children),typeof g=="object"&&g!==null){switch(g.$$typeof){case di:e:{for(var k=g.key,T=p;T!==null;){if(T.key===k){if(k=g.type,k===vn){if(T.tag===7){n(m,T.sibling),p=i(T,g.props.children),p.return=m,m=p;break e}}else if(T.elementType===k||typeof k=="object"&&k!==null&&k.$$typeof===Ct&&oc(k)===T.type){n(m,T.sibling),p=i(T,g.props),p.ref=ar(m,T,g),p.return=m,m=p;break e}n(m,T);break}else t(m,T);T=T.sibling}g.type===vn?(p=on(g.props.children,m.mode,w,g.key),p.return=m,m=p):(w=Wi(g.type,g.key,g.props,null,m.mode,w),w.ref=ar(m,p,g),w.return=m,m=w)}return o(m);case yn:e:{for(T=g.key;p!==null;){if(p.key===T)if(p.tag===4&&p.stateNode.containerInfo===g.containerInfo&&p.stateNode.implementation===g.implementation){n(m,p.sibling),p=i(p,g.children||[]),p.return=m,m=p;break e}else{n(m,p);break}else t(m,p);p=p.sibling}p=yo(g,m.mode,w),p.return=m,m=p}return o(m);case Ct:return T=g._init,S(m,p,T(g._payload),w)}if(fr(g))return v(m,p,g,w);if(nr(g))return x(m,p,g,w);Si(m,g)}return typeof g=="string"&&g!==""||typeof g=="number"?(g=""+g,p!==null&&p.tag===6?(n(m,p.sibling),p=i(p,g),p.return=m,m=p):(n(m,p),p=go(g,m.mode,w),p.return=m,m=p),o(m)):n(m,p)}return S}var $n=ah(!0),lh=ah(!1),os=bt(null),as=null,jn=null,vl=null;function xl(){vl=jn=as=null}function wl(e){var t=os.current;W(os),e._currentValue=t}function da(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Fn(e,t){as=e,vl=jn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(Ce=!0),e.firstContext=null)}function $e(e){var t=e._currentValue;if(vl!==e)if(e={context:e,memoizedValue:t,next:null},jn===null){if(as===null)throw Error(j(308));jn=e,as.dependencies={lanes:0,firstContext:e}}else jn=jn.next=e;return t}var en=null;function kl(e){en===null?en=[e]:en.push(e)}function uh(e,t,n,r){var i=t.interleaved;return i===null?(n.next=n,kl(t)):(n.next=i.next,i.next=n),t.interleaved=n,gt(e,r)}function gt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var Pt=!1;function Sl(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function ch(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function ht(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function Rt(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,_&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,gt(e,n)}return i=r.interleaved,i===null?(t.next=t,kl(r)):(t.next=i.next,i.next=t),r.interleaved=t,gt(e,n)}function _i(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,al(e,n)}}function ac(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,s=null;if(n=n.firstBaseUpdate,n!==null){do{var o={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};s===null?i=s=o:s=s.next=o,n=n.next}while(n!==null);s===null?i=s=t:s=s.next=t}else i=s=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:s,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function ls(e,t,n,r){var i=e.updateQueue;Pt=!1;var s=i.firstBaseUpdate,o=i.lastBaseUpdate,a=i.shared.pending;if(a!==null){i.shared.pending=null;var l=a,u=l.next;l.next=null,o===null?s=u:o.next=u,o=l;var c=e.alternate;c!==null&&(c=c.updateQueue,a=c.lastBaseUpdate,a!==o&&(a===null?c.firstBaseUpdate=u:a.next=u,c.lastBaseUpdate=l))}if(s!==null){var d=i.baseState;o=0,c=u=l=null,a=s;do{var f=a.lane,y=a.eventTime;if((r&f)===f){c!==null&&(c=c.next={eventTime:y,lane:0,tag:a.tag,payload:a.payload,callback:a.callback,next:null});e:{var v=e,x=a;switch(f=t,y=n,x.tag){case 1:if(v=x.payload,typeof v=="function"){d=v.call(y,d,f);break e}d=v;break e;case 3:v.flags=v.flags&-65537|128;case 0:if(v=x.payload,f=typeof v=="function"?v.call(y,d,f):v,f==null)break e;d=Y({},d,f);break e;case 2:Pt=!0}}a.callback!==null&&a.lane!==0&&(e.flags|=64,f=i.effects,f===null?i.effects=[a]:f.push(a))}else y={eventTime:y,lane:f,tag:a.tag,payload:a.payload,callback:a.callback,next:null},c===null?(u=c=y,l=d):c=c.next=y,o|=f;if(a=a.next,a===null){if(a=i.shared.pending,a===null)break;f=a,a=f.next,f.next=null,i.lastBaseUpdate=f,i.shared.pending=null}}while(!0);if(c===null&&(l=d),i.baseState=l,i.firstBaseUpdate=u,i.lastBaseUpdate=c,t=i.shared.interleaved,t!==null){i=t;do o|=i.lane,i=i.next;while(i!==t)}else s===null&&(i.shared.lanes=0);fn|=o,e.lanes=o,e.memoizedState=d}}function lc(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=co.transition;co.transition={};try{e(!1),t()}finally{O=n,co.transition=r}}function Eh(){return He().memoizedState}function _g(e,t,n){var r=_t(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},Nh(e))Dh(t,n);else if(n=uh(e,t,n,r),n!==null){var i=we();et(n,e,r,i),Mh(n,t,r)}}function Og(e,t,n){var r=_t(e),i={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(Nh(e))Dh(t,i);else{var s=e.alternate;if(e.lanes===0&&(s===null||s.lanes===0)&&(s=t.lastRenderedReducer,s!==null))try{var o=t.lastRenderedState,a=s(o,n);if(i.hasEagerState=!0,i.eagerState=a,tt(a,o)){var l=t.interleaved;l===null?(i.next=i,kl(t)):(i.next=l.next,l.next=i),t.interleaved=i;return}}catch{}finally{}n=uh(e,t,i,r),n!==null&&(i=we(),et(n,e,r,i),Mh(n,t,r))}}function Nh(e){var t=e.alternate;return e===Q||t!==null&&t===Q}function Dh(e,t){kr=cs=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Mh(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,al(e,n)}}var ds={readContext:$e,useCallback:he,useContext:he,useEffect:he,useImperativeHandle:he,useInsertionEffect:he,useLayoutEffect:he,useMemo:he,useReducer:he,useRef:he,useState:he,useDebugValue:he,useDeferredValue:he,useTransition:he,useMutableSource:he,useSyncExternalStore:he,useId:he,unstable_isNewReconciler:!1},Fg={readContext:$e,useCallback:function(e,t){return rt().memoizedState=[e,t===void 0?null:t],e},useContext:$e,useEffect:cc,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,Fi(4194308,4,Sh.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Fi(4194308,4,e,t)},useInsertionEffect:function(e,t){return Fi(4,2,e,t)},useMemo:function(e,t){var n=rt();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=rt();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=_g.bind(null,Q,e),[r.memoizedState,e]},useRef:function(e){var t=rt();return e={current:e},t.memoizedState=e},useState:uc,useDebugValue:Ml,useDeferredValue:function(e){return rt().memoizedState=e},useTransition:function(){var e=uc(!1),t=e[0];return e=Ig.bind(null,e[1]),rt().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=Q,i=rt();if(b){if(n===void 0)throw Error(j(407));n=n()}else{if(n=t(),le===null)throw Error(j(349));dn&30||ph(r,t,n)}i.memoizedState=n;var s={value:n,getSnapshot:t};return i.queue=s,cc(gh.bind(null,r,s,e),[e]),r.flags|=2048,$r(9,mh.bind(null,r,s,n,t),void 0,null),n},useId:function(){var e=rt(),t=le.identifierPrefix;if(b){var n=ft,r=dt;n=(r&~(1<<32-Je(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Wr++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=o.createElement(n,{is:r.is}):(e=o.createElement(n),n==="select"&&(o=e,r.multiple?o.multiple=!0:r.size&&(o.size=r.size))):e=o.createElementNS(e,n),e[it]=t,e[zr]=r,Bh(e,t,!1,!1),t.stateNode=e;e:{switch(o=Go(n,r),n){case"dialog":U("cancel",e),U("close",e),i=r;break;case"iframe":case"object":case"embed":U("load",e),i=r;break;case"video":case"audio":for(i=0;iGn&&(t.flags|=128,r=!0,lr(s,!1),t.lanes=4194304)}else{if(!r)if(e=us(o),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),lr(s,!0),s.tail===null&&s.tailMode==="hidden"&&!o.alternate&&!b)return pe(t),null}else 2*J()-s.renderingStartTime>Gn&&n!==1073741824&&(t.flags|=128,r=!0,lr(s,!1),t.lanes=4194304);s.isBackwards?(o.sibling=t.child,t.child=o):(n=s.last,n!==null?n.sibling=o:t.child=o,s.last=o)}return s.tail!==null?(t=s.tail,s.rendering=t,s.tail=t.sibling,s.renderingStartTime=J(),t.sibling=null,n=K.current,z(K,r?n&1|2:n&1),t):(pe(t),null);case 22:case 23:return _l(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?De&1073741824&&(pe(t),t.subtreeFlags&6&&(t.flags|=8192)):pe(t),null;case 24:return null;case 25:return null}throw Error(j(156,t.tag))}function Kg(e,t){switch(gl(t),t.tag){case 1:return Ee(t.type)&&ns(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Hn(),W(je),W(ye),Pl(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return Cl(t),null;case 13:if(W(K),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(j(340));bn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return W(K),null;case 4:return Hn(),null;case 10:return wl(t.type._context),null;case 22:case 23:return _l(),null;case 24:return null;default:return null}}var Ci=!1,me=!1,Gg=typeof WeakSet=="function"?WeakSet:Set,D=null;function En(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){q(e,t,r)}else n.current=null}function wa(e,t,n){try{n()}catch(r){q(e,t,r)}}var kc=!1;function Qg(e,t){if(ra=qi,e=Gf(),pl(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var i=r.anchorOffset,s=r.focusNode;r=r.focusOffset;try{n.nodeType,s.nodeType}catch{n=null;break e}var o=0,a=-1,l=-1,u=0,c=0,d=e,f=null;t:for(;;){for(var y;d!==n||i!==0&&d.nodeType!==3||(a=o+i),d!==s||r!==0&&d.nodeType!==3||(l=o+r),d.nodeType===3&&(o+=d.nodeValue.length),(y=d.firstChild)!==null;)f=d,d=y;for(;;){if(d===e)break t;if(f===n&&++u===i&&(a=o),f===s&&++c===r&&(l=o),(y=d.nextSibling)!==null)break;d=f,f=d.parentNode}d=y}n=a===-1||l===-1?null:{start:a,end:l}}else n=null}n=n||{start:0,end:0}}else n=null;for(ia={focusedElem:e,selectionRange:n},qi=!1,D=t;D!==null;)if(t=D,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,D=e;else for(;D!==null;){t=D;try{var v=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(v!==null){var x=v.memoizedProps,S=v.memoizedState,m=t.stateNode,p=m.getSnapshotBeforeUpdate(t.elementType===t.type?x:Xe(t.type,x),S);m.__reactInternalSnapshotBeforeUpdate=p}break;case 3:var g=t.stateNode.containerInfo;g.nodeType===1?g.textContent="":g.nodeType===9&&g.documentElement&&g.removeChild(g.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(j(163))}}catch(w){q(t,t.return,w)}if(e=t.sibling,e!==null){e.return=t.return,D=e;break}D=t.return}return v=kc,kc=!1,v}function Sr(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var i=r=r.next;do{if((i.tag&e)===e){var s=i.destroy;i.destroy=void 0,s!==void 0&&wa(t,n,s)}i=i.next}while(i!==r)}}function As(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function ka(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function bh(e){var t=e.alternate;t!==null&&(e.alternate=null,bh(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[it],delete t[zr],delete t[aa],delete t[Mg],delete t[Lg])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function $h(e){return e.tag===5||e.tag===3||e.tag===4}function Sc(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||$h(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Sa(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=ts));else if(r!==4&&(e=e.child,e!==null))for(Sa(e,t,n),e=e.sibling;e!==null;)Sa(e,t,n),e=e.sibling}function Ta(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(Ta(e,t,n),e=e.sibling;e!==null;)Ta(e,t,n),e=e.sibling}var ue=null,Ze=!1;function St(e,t,n){for(n=n.child;n!==null;)Hh(e,t,n),n=n.sibling}function Hh(e,t,n){if(st&&typeof st.onCommitFiberUnmount=="function")try{st.onCommitFiberUnmount(Cs,n)}catch{}switch(n.tag){case 5:me||En(n,t);case 6:var r=ue,i=Ze;ue=null,St(e,t,n),ue=r,Ze=i,ue!==null&&(Ze?(e=ue,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):ue.removeChild(n.stateNode));break;case 18:ue!==null&&(Ze?(e=ue,n=n.stateNode,e.nodeType===8?ao(e.parentNode,n):e.nodeType===1&&ao(e,n),Rr(e)):ao(ue,n.stateNode));break;case 4:r=ue,i=Ze,ue=n.stateNode.containerInfo,Ze=!0,St(e,t,n),ue=r,Ze=i;break;case 0:case 11:case 14:case 15:if(!me&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){i=r=r.next;do{var s=i,o=s.destroy;s=s.tag,o!==void 0&&(s&2||s&4)&&wa(n,t,o),i=i.next}while(i!==r)}St(e,t,n);break;case 1:if(!me&&(En(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(a){q(n,t,a)}St(e,t,n);break;case 21:St(e,t,n);break;case 22:n.mode&1?(me=(r=me)||n.memoizedState!==null,St(e,t,n),me=r):St(e,t,n);break;default:St(e,t,n)}}function Tc(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Gg),t.forEach(function(r){var i=ry.bind(null,e,r);n.has(r)||(n.add(r),r.then(i,i))})}}function Ge(e,t){var n=t.deletions;if(n!==null)for(var r=0;ri&&(i=o),r&=~s}if(r=i,r=J()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Xg(r/1960))-r,10e?16:e,Dt===null)var r=!1;else{if(e=Dt,Dt=null,ps=0,_&6)throw Error(j(331));var i=_;for(_|=4,D=e.current;D!==null;){var s=D,o=s.child;if(D.flags&16){var a=s.deletions;if(a!==null){for(var l=0;lJ()-Rl?sn(e,0):Vl|=n),Ne(e,t)}function Jh(e,t){t===0&&(e.mode&1?(t=mi,mi<<=1,!(mi&130023424)&&(mi=4194304)):t=1);var n=we();e=gt(e,t),e!==null&&(Jr(e,t,n),Ne(e,n))}function ny(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Jh(e,n)}function ry(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,i=e.memoizedState;i!==null&&(n=i.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(j(314))}r!==null&&r.delete(t),Jh(e,n)}var ep;ep=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||je.current)Ce=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return Ce=!1,$g(e,t,n);Ce=!!(e.flags&131072)}else Ce=!1,b&&t.flags&1048576&&ih(t,ss,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;zi(e,t),e=t.pendingProps;var i=Wn(t,ye.current);Fn(t,n),i=El(null,t,r,e,i,n);var s=Nl();return t.flags|=1,typeof i=="object"&&i!==null&&typeof i.render=="function"&&i.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Ee(r)?(s=!0,rs(t)):s=!1,t.memoizedState=i.state!==null&&i.state!==void 0?i.state:null,Sl(t),i.updater=Ls,t.stateNode=i,i._reactInternals=t,ha(t,r,e,n),t=ga(null,t,r,!0,s,n)):(t.tag=0,b&&s&&ml(t),ve(null,t,i,n),t=t.child),t;case 16:r=t.elementType;e:{switch(zi(e,t),e=t.pendingProps,i=r._init,r=i(r._payload),t.type=r,i=t.tag=sy(r),e=Xe(r,e),i){case 0:t=ma(null,t,r,e,n);break e;case 1:t=vc(null,t,r,e,n);break e;case 11:t=gc(null,t,r,e,n);break e;case 14:t=yc(null,t,r,Xe(r.type,e),n);break e}throw Error(j(306,r,""))}return t;case 0:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:Xe(r,i),ma(e,t,r,i,n);case 1:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:Xe(r,i),vc(e,t,r,i,n);case 3:e:{if(Oh(t),e===null)throw Error(j(387));r=t.pendingProps,s=t.memoizedState,i=s.element,ch(e,t),ls(t,r,null,n);var o=t.memoizedState;if(r=o.element,s.isDehydrated)if(s={element:r,isDehydrated:!1,cache:o.cache,pendingSuspenseBoundaries:o.pendingSuspenseBoundaries,transitions:o.transitions},t.updateQueue.baseState=s,t.memoizedState=s,t.flags&256){i=Kn(Error(j(423)),t),t=xc(e,t,r,n,i);break e}else if(r!==i){i=Kn(Error(j(424)),t),t=xc(e,t,r,n,i);break e}else for(Me=Vt(t.stateNode.containerInfo.firstChild),Le=t,b=!0,qe=null,n=lh(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(bn(),r===i){t=yt(e,t,n);break e}ve(e,t,r,n)}t=t.child}return t;case 5:return dh(t),e===null&&ca(t),r=t.type,i=t.pendingProps,s=e!==null?e.memoizedProps:null,o=i.children,sa(r,i)?o=null:s!==null&&sa(r,s)&&(t.flags|=32),_h(e,t),ve(e,t,o,n),t.child;case 6:return e===null&&ca(t),null;case 13:return Fh(e,t,n);case 4:return Tl(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=$n(t,null,r,n):ve(e,t,r,n),t.child;case 11:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:Xe(r,i),gc(e,t,r,i,n);case 7:return ve(e,t,t.pendingProps,n),t.child;case 8:return ve(e,t,t.pendingProps.children,n),t.child;case 12:return ve(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,i=t.pendingProps,s=t.memoizedProps,o=i.value,z(os,r._currentValue),r._currentValue=o,s!==null)if(tt(s.value,o)){if(s.children===i.children&&!je.current){t=yt(e,t,n);break e}}else for(s=t.child,s!==null&&(s.return=t);s!==null;){var a=s.dependencies;if(a!==null){o=s.child;for(var l=a.firstContext;l!==null;){if(l.context===r){if(s.tag===1){l=ht(-1,n&-n),l.tag=2;var u=s.updateQueue;if(u!==null){u=u.shared;var c=u.pending;c===null?l.next=l:(l.next=c.next,c.next=l),u.pending=l}}s.lanes|=n,l=s.alternate,l!==null&&(l.lanes|=n),da(s.return,n,t),a.lanes|=n;break}l=l.next}}else if(s.tag===10)o=s.type===t.type?null:s.child;else if(s.tag===18){if(o=s.return,o===null)throw Error(j(341));o.lanes|=n,a=o.alternate,a!==null&&(a.lanes|=n),da(o,n,t),o=s.sibling}else o=s.child;if(o!==null)o.return=s;else for(o=s;o!==null;){if(o===t){o=null;break}if(s=o.sibling,s!==null){s.return=o.return,o=s;break}o=o.return}s=o}ve(e,t,i.children,n),t=t.child}return t;case 9:return i=t.type,r=t.pendingProps.children,Fn(t,n),i=$e(i),r=r(i),t.flags|=1,ve(e,t,r,n),t.child;case 14:return r=t.type,i=Xe(r,t.pendingProps),i=Xe(r.type,i),yc(e,t,r,i,n);case 15:return Rh(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:Xe(r,i),zi(e,t),t.tag=1,Ee(r)?(e=!0,rs(t)):e=!1,Fn(t,n),Lh(t,r,i),ha(t,r,i,n),ga(null,t,r,!0,e,n);case 19:return zh(e,t,n);case 22:return Ih(e,t,n)}throw Error(j(156,t.tag))};function tp(e,t){return Nf(e,t)}function iy(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function ze(e,t,n,r){return new iy(e,t,n,r)}function Fl(e){return e=e.prototype,!(!e||!e.isReactComponent)}function sy(e){if(typeof e=="function")return Fl(e)?1:0;if(e!=null){if(e=e.$$typeof,e===rl)return 11;if(e===il)return 14}return 2}function Ot(e,t){var n=e.alternate;return n===null?(n=ze(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Wi(e,t,n,r,i,s){var o=2;if(r=e,typeof e=="function")Fl(e)&&(o=1);else if(typeof e=="string")o=5;else e:switch(e){case vn:return on(n.children,i,s,t);case nl:o=8,i|=8;break;case Oo:return e=ze(12,n,t,i|2),e.elementType=Oo,e.lanes=s,e;case Fo:return e=ze(13,n,t,i),e.elementType=Fo,e.lanes=s,e;case zo:return e=ze(19,n,t,i),e.elementType=zo,e.lanes=s,e;case df:return Rs(n,i,s,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case uf:o=10;break e;case cf:o=9;break e;case rl:o=11;break e;case il:o=14;break e;case Ct:o=16,r=null;break e}throw Error(j(130,e==null?e:typeof e,""))}return t=ze(o,n,t,i),t.elementType=e,t.type=r,t.lanes=s,t}function on(e,t,n,r){return e=ze(7,e,r,t),e.lanes=n,e}function Rs(e,t,n,r){return e=ze(22,e,r,t),e.elementType=df,e.lanes=n,e.stateNode={isHidden:!1},e}function go(e,t,n){return e=ze(6,e,null,t),e.lanes=n,e}function yo(e,t,n){return t=ze(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function oy(e,t,n,r,i){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Xs(0),this.expirationTimes=Xs(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Xs(0),this.identifierPrefix=r,this.onRecoverableError=i,this.mutableSourceEagerHydrationData=null}function zl(e,t,n,r,i,s,o,a,l){return e=new oy(e,t,n,a,l),t===1?(t=1,s===!0&&(t|=8)):t=0,s=ze(3,null,null,t),e.current=s,s.stateNode=e,s.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Sl(s),e}function ay(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(sp)}catch(e){console.error(e)}}sp(),sf.exports=Ve;var fy=sf.exports,Lc=fy;Io.createRoot=Lc.createRoot,Io.hydrateRoot=Lc.hydrateRoot;const bl=N.createContext({});function $l(e){const t=N.useRef(null);return t.current===null&&(t.current=e()),t.current}const Hl=typeof window<"u",op=Hl?N.useLayoutEffect:N.useEffect,zs=N.createContext(null);function Kl(e,t){e.indexOf(t)===-1&&e.push(t)}function Gl(e,t){const n=e.indexOf(t);n>-1&&e.splice(n,1)}const vt=(e,t,n)=>n>t?t:n{};const xt={},ap=e=>/^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(e);function lp(e){return typeof e=="object"&&e!==null}const up=e=>/^0[^.\s]+$/u.test(e);function Yl(e){let t;return()=>(t===void 0&&(t=e()),t)}const We=e=>e,hy=(e,t)=>n=>t(e(n)),ri=(...e)=>e.reduce(hy),Kr=(e,t,n)=>{const r=t-e;return r===0?1:(n-e)/r};class Xl{constructor(){this.subscriptions=[]}add(t){return Kl(this.subscriptions,t),()=>Gl(this.subscriptions,t)}notify(t,n,r){const i=this.subscriptions.length;if(i)if(i===1)this.subscriptions[0](t,n,r);else for(let s=0;se*1e3,Be=e=>e/1e3;function cp(e,t){return t?e*(1e3/t):0}const dp=(e,t,n)=>(((1-3*n+3*t)*e+(3*n-6*t))*e+3*t)*e,py=1e-7,my=12;function gy(e,t,n,r,i){let s,o,a=0;do o=t+(n-t)/2,s=dp(o,r,i)-e,s>0?n=o:t=o;while(Math.abs(s)>py&&++agy(s,0,1,e,n);return s=>s===0||s===1?s:dp(i(s),t,r)}const fp=e=>t=>t<=.5?e(2*t)/2:(2-e(2*(1-t)))/2,hp=e=>t=>1-e(1-t),pp=ii(.33,1.53,.69,.99),Zl=hp(pp),mp=fp(Zl),gp=e=>(e*=2)<1?.5*Zl(e):.5*(2-Math.pow(2,-10*(e-1))),ql=e=>1-Math.sin(Math.acos(e)),yp=hp(ql),vp=fp(ql),yy=ii(.42,0,1,1),vy=ii(0,0,.58,1),xp=ii(.42,0,.58,1),xy=e=>Array.isArray(e)&&typeof e[0]!="number",wp=e=>Array.isArray(e)&&typeof e[0]=="number",wy={linear:We,easeIn:yy,easeInOut:xp,easeOut:vy,circIn:ql,circInOut:vp,circOut:yp,backIn:Zl,backInOut:mp,backOut:pp,anticipate:gp},ky=e=>typeof e=="string",Ac=e=>{if(wp(e)){Ql(e.length===4);const[t,n,r,i]=e;return ii(t,n,r,i)}else if(ky(e))return wy[e];return e},Ei=["setup","read","resolveKeyframes","preUpdate","update","preRender","render","postRender"];function Sy(e,t){let n=new Set,r=new Set,i=!1,s=!1;const o=new WeakSet;let a={delta:0,timestamp:0,isProcessing:!1};function l(c){o.has(c)&&(u.schedule(c),e()),c(a)}const u={schedule:(c,d=!1,f=!1)=>{const v=f&&i?n:r;return d&&o.add(c),v.has(c)||v.add(c),c},cancel:c=>{r.delete(c),o.delete(c)},process:c=>{if(a=c,i){s=!0;return}i=!0,[n,r]=[r,n],n.forEach(l),n.clear(),i=!1,s&&(s=!1,u.process(c))}};return u}const Ty=40;function kp(e,t){let n=!1,r=!0;const i={delta:0,timestamp:0,isProcessing:!1},s=()=>n=!0,o=Ei.reduce((g,w)=>(g[w]=Sy(s),g),{}),{setup:a,read:l,resolveKeyframes:u,preUpdate:c,update:d,preRender:f,render:y,postRender:v}=o,x=()=>{const g=xt.useManualTiming?i.timestamp:performance.now();n=!1,xt.useManualTiming||(i.delta=r?1e3/60:Math.max(Math.min(g-i.timestamp,Ty),1)),i.timestamp=g,i.isProcessing=!0,a.process(i),l.process(i),u.process(i),c.process(i),d.process(i),f.process(i),y.process(i),v.process(i),i.isProcessing=!1,n&&t&&(r=!1,e(x))},S=()=>{n=!0,r=!0,i.isProcessing||e(x)};return{schedule:Ei.reduce((g,w)=>{const k=o[w];return g[w]=(T,P=!1,C=!1)=>(n||S(),k.schedule(T,P,C)),g},{}),cancel:g=>{for(let w=0;w(bi===void 0&&Pe.set(ce.isProcessing||xt.useManualTiming?ce.timestamp:performance.now()),bi),set:e=>{bi=e,queueMicrotask(Cy)}},Sp=e=>t=>typeof t=="string"&&t.startsWith(e),Jl=Sp("--"),Py=Sp("var(--"),eu=e=>Py(e)?jy.test(e.split("/*")[0].trim()):!1,jy=/var\(--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)$/iu,Jn={test:e=>typeof e=="number",parse:parseFloat,transform:e=>e},Gr={...Jn,transform:e=>vt(0,1,e)},Ni={...Jn,default:1},Pr=e=>Math.round(e*1e5)/1e5,tu=/-?(?:\d+(?:\.\d+)?|\.\d+)/gu;function Ey(e){return e==null}const Ny=/^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))$/iu,nu=(e,t)=>n=>!!(typeof n=="string"&&Ny.test(n)&&n.startsWith(e)||t&&!Ey(n)&&Object.prototype.hasOwnProperty.call(n,t)),Tp=(e,t,n)=>r=>{if(typeof r!="string")return r;const[i,s,o,a]=r.match(tu);return{[e]:parseFloat(i),[t]:parseFloat(s),[n]:parseFloat(o),alpha:a!==void 0?parseFloat(a):1}},Dy=e=>vt(0,255,e),xo={...Jn,transform:e=>Math.round(Dy(e))},nn={test:nu("rgb","red"),parse:Tp("red","green","blue"),transform:({red:e,green:t,blue:n,alpha:r=1})=>"rgba("+xo.transform(e)+", "+xo.transform(t)+", "+xo.transform(n)+", "+Pr(Gr.transform(r))+")"};function My(e){let t="",n="",r="",i="";return e.length>5?(t=e.substring(1,3),n=e.substring(3,5),r=e.substring(5,7),i=e.substring(7,9)):(t=e.substring(1,2),n=e.substring(2,3),r=e.substring(3,4),i=e.substring(4,5),t+=t,n+=n,r+=r,i+=i),{red:parseInt(t,16),green:parseInt(n,16),blue:parseInt(r,16),alpha:i?parseInt(i,16)/255:1}}const Na={test:nu("#"),parse:My,transform:nn.transform},si=e=>({test:t=>typeof t=="string"&&t.endsWith(e)&&t.split(" ").length===1,parse:parseFloat,transform:t=>`${t}${e}`}),Tt=si("deg"),lt=si("%"),A=si("px"),Ly=si("vh"),Ay=si("vw"),Vc={...lt,parse:e=>lt.parse(e)/100,transform:e=>lt.transform(e*100)},Dn={test:nu("hsl","hue"),parse:Tp("hue","saturation","lightness"),transform:({hue:e,saturation:t,lightness:n,alpha:r=1})=>"hsla("+Math.round(e)+", "+lt.transform(Pr(t))+", "+lt.transform(Pr(n))+", "+Pr(Gr.transform(r))+")"},ee={test:e=>nn.test(e)||Na.test(e)||Dn.test(e),parse:e=>nn.test(e)?nn.parse(e):Dn.test(e)?Dn.parse(e):Na.parse(e),transform:e=>typeof e=="string"?e:e.hasOwnProperty("red")?nn.transform(e):Dn.transform(e),getAnimatableNone:e=>{const t=ee.parse(e);return t.alpha=0,ee.transform(t)}},Vy=/(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))/giu;function Ry(e){var t,n;return isNaN(e)&&typeof e=="string"&&(((t=e.match(tu))==null?void 0:t.length)||0)+(((n=e.match(Vy))==null?void 0:n.length)||0)>0}const Cp="number",Pp="color",Iy="var",_y="var(",Rc="${}",Oy=/var\s*\(\s*--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)|#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\)|-?(?:\d+(?:\.\d+)?|\.\d+)/giu;function Qr(e){const t=e.toString(),n=[],r={color:[],number:[],var:[]},i=[];let s=0;const a=t.replace(Oy,l=>(ee.test(l)?(r.color.push(s),i.push(Pp),n.push(ee.parse(l))):l.startsWith(_y)?(r.var.push(s),i.push(Iy),n.push(l)):(r.number.push(s),i.push(Cp),n.push(parseFloat(l))),++s,Rc)).split(Rc);return{values:n,split:a,indexes:r,types:i}}function jp(e){return Qr(e).values}function Ep(e){const{split:t,types:n}=Qr(e),r=t.length;return i=>{let s="";for(let o=0;otypeof e=="number"?0:ee.test(e)?ee.getAnimatableNone(e):e;function zy(e){const t=jp(e);return Ep(e)(t.map(Fy))}const Ut={test:Ry,parse:jp,createTransformer:Ep,getAnimatableNone:zy};function wo(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+(t-e)*6*n:n<1/2?t:n<2/3?e+(t-e)*(2/3-n)*6:e}function By({hue:e,saturation:t,lightness:n,alpha:r}){e/=360,t/=100,n/=100;let i=0,s=0,o=0;if(!t)i=s=o=n;else{const a=n<.5?n*(1+t):n+t-n*t,l=2*n-a;i=wo(l,a,e+1/3),s=wo(l,a,e),o=wo(l,a,e-1/3)}return{red:Math.round(i*255),green:Math.round(s*255),blue:Math.round(o*255),alpha:r}}function ys(e,t){return n=>n>0?t:e}const G=(e,t,n)=>e+(t-e)*n,ko=(e,t,n)=>{const r=e*e,i=n*(t*t-r)+r;return i<0?0:Math.sqrt(i)},Uy=[Na,nn,Dn],Wy=e=>Uy.find(t=>t.test(e));function Ic(e){const t=Wy(e);if(!t)return!1;let n=t.parse(e);return t===Dn&&(n=By(n)),n}const _c=(e,t)=>{const n=Ic(e),r=Ic(t);if(!n||!r)return ys(e,t);const i={...n};return s=>(i.red=ko(n.red,r.red,s),i.green=ko(n.green,r.green,s),i.blue=ko(n.blue,r.blue,s),i.alpha=G(n.alpha,r.alpha,s),nn.transform(i))},Da=new Set(["none","hidden"]);function by(e,t){return Da.has(e)?n=>n<=0?e:t:n=>n>=1?t:e}function $y(e,t){return n=>G(e,t,n)}function ru(e){return typeof e=="number"?$y:typeof e=="string"?eu(e)?ys:ee.test(e)?_c:Gy:Array.isArray(e)?Np:typeof e=="object"?ee.test(e)?_c:Hy:ys}function Np(e,t){const n=[...e],r=n.length,i=e.map((s,o)=>ru(s)(s,t[o]));return s=>{for(let o=0;o{for(const s in r)n[s]=r[s](i);return n}}function Ky(e,t){const n=[],r={color:0,var:0,number:0};for(let i=0;i{const n=Ut.createTransformer(t),r=Qr(e),i=Qr(t);return r.indexes.var.length===i.indexes.var.length&&r.indexes.color.length===i.indexes.color.length&&r.indexes.number.length>=i.indexes.number.length?Da.has(e)&&!i.values.length||Da.has(t)&&!r.values.length?by(e,t):ri(Np(Ky(r,i),i.values),n):ys(e,t)};function Dp(e,t,n){return typeof e=="number"&&typeof t=="number"&&typeof n=="number"?G(e,t,n):ru(e)(e,t)}const Qy=e=>{const t=({timestamp:n})=>e(n);return{start:(n=!0)=>$.update(t,n),stop:()=>Bt(t),now:()=>ce.isProcessing?ce.timestamp:Pe.now()}},Mp=(e,t,n=10)=>{let r="";const i=Math.max(Math.round(t/n),2);for(let s=0;s=vs?1/0:t}function Yy(e,t=100,n){const r=n({...e,keyframes:[0,t]}),i=Math.min(iu(r),vs);return{type:"keyframes",ease:s=>r.next(i*s).value/t,duration:Be(i)}}const Xy=5;function Lp(e,t,n){const r=Math.max(t-Xy,0);return cp(n-e(r),t-r)}const Z={stiffness:100,damping:10,mass:1,velocity:0,duration:800,bounce:.3,visualDuration:.3,restSpeed:{granular:.01,default:2},restDelta:{granular:.005,default:.5},minDuration:.01,maxDuration:10,minDamping:.05,maxDamping:1},So=.001;function Zy({duration:e=Z.duration,bounce:t=Z.bounce,velocity:n=Z.velocity,mass:r=Z.mass}){let i,s,o=1-t;o=vt(Z.minDamping,Z.maxDamping,o),e=vt(Z.minDuration,Z.maxDuration,Be(e)),o<1?(i=u=>{const c=u*o,d=c*e,f=c-n,y=Ma(u,o),v=Math.exp(-d);return So-f/y*v},s=u=>{const d=u*o*e,f=d*n+n,y=Math.pow(o,2)*Math.pow(u,2)*e,v=Math.exp(-d),x=Ma(Math.pow(u,2),o);return(-i(u)+So>0?-1:1)*((f-y)*v)/x}):(i=u=>{const c=Math.exp(-u*e),d=(u-n)*e+1;return-So+c*d},s=u=>{const c=Math.exp(-u*e),d=(n-u)*(e*e);return c*d});const a=5/e,l=Jy(i,s,a);if(e=at(e),isNaN(l))return{stiffness:Z.stiffness,damping:Z.damping,duration:e};{const u=Math.pow(l,2)*r;return{stiffness:u,damping:o*2*Math.sqrt(r*u),duration:e}}}const qy=12;function Jy(e,t,n){let r=n;for(let i=1;ie[n]!==void 0)}function n1(e){let t={velocity:Z.velocity,stiffness:Z.stiffness,damping:Z.damping,mass:Z.mass,isResolvedFromDuration:!1,...e};if(!Oc(e,t1)&&Oc(e,e1))if(e.visualDuration){const n=e.visualDuration,r=2*Math.PI/(n*1.2),i=r*r,s=2*vt(.05,1,1-(e.bounce||0))*Math.sqrt(i);t={...t,mass:Z.mass,stiffness:i,damping:s}}else{const n=Zy(e);t={...t,...n,mass:Z.mass},t.isResolvedFromDuration=!0}return t}function xs(e=Z.visualDuration,t=Z.bounce){const n=typeof e!="object"?{visualDuration:e,keyframes:[0,1],bounce:t}:e;let{restSpeed:r,restDelta:i}=n;const s=n.keyframes[0],o=n.keyframes[n.keyframes.length-1],a={done:!1,value:s},{stiffness:l,damping:u,mass:c,duration:d,velocity:f,isResolvedFromDuration:y}=n1({...n,velocity:-Be(n.velocity||0)}),v=f||0,x=u/(2*Math.sqrt(l*c)),S=o-s,m=Be(Math.sqrt(l/c)),p=Math.abs(S)<5;r||(r=p?Z.restSpeed.granular:Z.restSpeed.default),i||(i=p?Z.restDelta.granular:Z.restDelta.default);let g;if(x<1){const k=Ma(m,x);g=T=>{const P=Math.exp(-x*m*T);return o-P*((v+x*m*S)/k*Math.sin(k*T)+S*Math.cos(k*T))}}else if(x===1)g=k=>o-Math.exp(-m*k)*(S+(v+m*S)*k);else{const k=m*Math.sqrt(x*x-1);g=T=>{const P=Math.exp(-x*m*T),C=Math.min(k*T,300);return o-P*((v+x*m*S)*Math.sinh(C)+k*S*Math.cosh(C))/k}}const w={calculatedDuration:y&&d||null,next:k=>{const T=g(k);if(y)a.done=k>=d;else{let P=k===0?v:0;x<1&&(P=k===0?at(v):Lp(g,k,T));const C=Math.abs(P)<=r,R=Math.abs(o-T)<=i;a.done=C&&R}return a.value=a.done?o:T,a},toString:()=>{const k=Math.min(iu(w),vs),T=Mp(P=>w.next(k*P).value,k,30);return k+"ms "+T},toTransition:()=>{}};return w}xs.applyToOptions=e=>{const t=Yy(e,100,xs);return e.ease=t.ease,e.duration=at(t.duration),e.type="keyframes",e};function La({keyframes:e,velocity:t=0,power:n=.8,timeConstant:r=325,bounceDamping:i=10,bounceStiffness:s=500,modifyTarget:o,min:a,max:l,restDelta:u=.5,restSpeed:c}){const d=e[0],f={done:!1,value:d},y=C=>a!==void 0&&Cl,v=C=>a===void 0?l:l===void 0||Math.abs(a-C)-x*Math.exp(-C/r),g=C=>m+p(C),w=C=>{const R=p(C),M=g(C);f.done=Math.abs(R)<=u,f.value=f.done?m:M};let k,T;const P=C=>{y(f.value)&&(k=C,T=xs({keyframes:[f.value,v(f.value)],velocity:Lp(g,C,f.value),damping:i,stiffness:s,restDelta:u,restSpeed:c}))};return P(0),{calculatedDuration:null,next:C=>{let R=!1;return!T&&k===void 0&&(R=!0,w(C),P(C)),k!==void 0&&C>=k?T.next(C-k):(!R&&w(C),f)}}}function r1(e,t,n){const r=[],i=n||xt.mix||Dp,s=e.length-1;for(let o=0;ot[0];if(s===2&&t[0]===t[1])return()=>t[1];const o=e[0]===e[1];e[0]>e[s-1]&&(e=[...e].reverse(),t=[...t].reverse());const a=r1(t,r,i),l=a.length,u=c=>{if(o&&c1)for(;du(vt(e[0],e[s-1],c)):u}function s1(e,t){const n=e[e.length-1];for(let r=1;r<=t;r++){const i=Kr(0,t,r);e.push(G(n,1,i))}}function o1(e){const t=[0];return s1(t,e.length-1),t}function a1(e,t){return e.map(n=>n*t)}function l1(e,t){return e.map(()=>t||xp).splice(0,e.length-1)}function jr({duration:e=300,keyframes:t,times:n,ease:r="easeInOut"}){const i=xy(r)?r.map(Ac):Ac(r),s={done:!1,value:t[0]},o=a1(n&&n.length===t.length?n:o1(t),e),a=i1(o,t,{ease:Array.isArray(i)?i:l1(t,i)});return{calculatedDuration:e,next:l=>(s.value=a(l),s.done=l>=e,s)}}const u1=e=>e!==null;function su(e,{repeat:t,repeatType:n="loop"},r,i=1){const s=e.filter(u1),a=i<0||t&&n!=="loop"&&t%2===1?0:s.length-1;return!a||r===void 0?s[a]:r}const c1={decay:La,inertia:La,tween:jr,keyframes:jr,spring:xs};function Ap(e){typeof e.type=="string"&&(e.type=c1[e.type])}class ou{constructor(){this.updateFinished()}get finished(){return this._finished}updateFinished(){this._finished=new Promise(t=>{this.resolve=t})}notifyFinished(){this.resolve()}then(t,n){return this.finished.then(t,n)}}const d1=e=>e/100;class au extends ou{constructor(t){super(),this.state="idle",this.startTime=null,this.isStopped=!1,this.currentTime=0,this.holdTime=null,this.playbackSpeed=1,this.stop=()=>{var r,i;const{motionValue:n}=this.options;n&&n.updatedAt!==Pe.now()&&this.tick(Pe.now()),this.isStopped=!0,this.state!=="idle"&&(this.teardown(),(i=(r=this.options).onStop)==null||i.call(r))},this.options=t,this.initAnimation(),this.play(),t.autoplay===!1&&this.pause()}initAnimation(){const{options:t}=this;Ap(t);const{type:n=jr,repeat:r=0,repeatDelay:i=0,repeatType:s,velocity:o=0}=t;let{keyframes:a}=t;const l=n||jr;l!==jr&&typeof a[0]!="number"&&(this.mixKeyframes=ri(d1,Dp(a[0],a[1])),a=[0,100]);const u=l({...t,keyframes:a});s==="mirror"&&(this.mirroredGenerator=l({...t,keyframes:[...a].reverse(),velocity:-o})),u.calculatedDuration===null&&(u.calculatedDuration=iu(u));const{calculatedDuration:c}=u;this.calculatedDuration=c,this.resolvedDuration=c+i,this.totalDuration=this.resolvedDuration*(r+1)-i,this.generator=u}updateTime(t){const n=Math.round(t-this.startTime)*this.playbackSpeed;this.holdTime!==null?this.currentTime=this.holdTime:this.currentTime=n}tick(t,n=!1){const{generator:r,totalDuration:i,mixKeyframes:s,mirroredGenerator:o,resolvedDuration:a,calculatedDuration:l}=this;if(this.startTime===null)return r.next(0);const{delay:u=0,keyframes:c,repeat:d,repeatType:f,repeatDelay:y,type:v,onUpdate:x,finalKeyframe:S}=this.options;this.speed>0?this.startTime=Math.min(this.startTime,t):this.speed<0&&(this.startTime=Math.min(t-i/this.speed,this.startTime)),n?this.currentTime=t:this.updateTime(t);const m=this.currentTime-u*(this.playbackSpeed>=0?1:-1),p=this.playbackSpeed>=0?m<0:m>i;this.currentTime=Math.max(m,0),this.state==="finished"&&this.holdTime===null&&(this.currentTime=i);let g=this.currentTime,w=r;if(d){const C=Math.min(this.currentTime,i)/a;let R=Math.floor(C),M=C%1;!M&&C>=1&&(M=1),M===1&&R--,R=Math.min(R,d+1),!!(R%2)&&(f==="reverse"?(M=1-M,y&&(M-=y/a)):f==="mirror"&&(w=o)),g=vt(0,1,M)*a}const k=p?{done:!1,value:c[0]}:w.next(g);s&&(k.value=s(k.value));let{done:T}=k;!p&&l!==null&&(T=this.playbackSpeed>=0?this.currentTime>=i:this.currentTime<=0);const P=this.holdTime===null&&(this.state==="finished"||this.state==="running"&&T);return P&&v!==La&&(k.value=su(c,this.options,S,this.speed)),x&&x(k.value),P&&this.finish(),k}then(t,n){return this.finished.then(t,n)}get duration(){return Be(this.calculatedDuration)}get iterationDuration(){const{delay:t=0}=this.options||{};return this.duration+Be(t)}get time(){return Be(this.currentTime)}set time(t){var n;t=at(t),this.currentTime=t,this.startTime===null||this.holdTime!==null||this.playbackSpeed===0?this.holdTime=t:this.driver&&(this.startTime=this.driver.now()-t/this.playbackSpeed),(n=this.driver)==null||n.start(!1)}get speed(){return this.playbackSpeed}set speed(t){this.updateTime(Pe.now());const n=this.playbackSpeed!==t;this.playbackSpeed=t,n&&(this.time=Be(this.currentTime))}play(){var i,s;if(this.isStopped)return;const{driver:t=Qy,startTime:n}=this.options;this.driver||(this.driver=t(o=>this.tick(o))),(s=(i=this.options).onPlay)==null||s.call(i);const r=this.driver.now();this.state==="finished"?(this.updateFinished(),this.startTime=r):this.holdTime!==null?this.startTime=r-this.holdTime:this.startTime||(this.startTime=n??r),this.state==="finished"&&this.speed<0&&(this.startTime+=this.calculatedDuration),this.holdTime=null,this.state="running",this.driver.start()}pause(){this.state="paused",this.updateTime(Pe.now()),this.holdTime=this.currentTime}complete(){this.state!=="running"&&this.play(),this.state="finished",this.holdTime=null}finish(){var t,n;this.notifyFinished(),this.teardown(),this.state="finished",(n=(t=this.options).onComplete)==null||n.call(t)}cancel(){var t,n;this.holdTime=null,this.startTime=0,this.tick(0),this.teardown(),(n=(t=this.options).onCancel)==null||n.call(t)}teardown(){this.state="idle",this.stopDriver(),this.startTime=this.holdTime=null}stopDriver(){this.driver&&(this.driver.stop(),this.driver=void 0)}sample(t){return this.startTime=0,this.tick(t,!0)}attachTimeline(t){var n;return this.options.allowFlatten&&(this.options.type="keyframes",this.options.ease="linear",this.initAnimation()),(n=this.driver)==null||n.stop(),t.observe(this)}}function f1(e){for(let t=1;te*180/Math.PI,Aa=e=>{const t=rn(Math.atan2(e[1],e[0]));return Va(t)},h1={x:4,y:5,translateX:4,translateY:5,scaleX:0,scaleY:3,scale:e=>(Math.abs(e[0])+Math.abs(e[3]))/2,rotate:Aa,rotateZ:Aa,skewX:e=>rn(Math.atan(e[1])),skewY:e=>rn(Math.atan(e[2])),skew:e=>(Math.abs(e[1])+Math.abs(e[2]))/2},Va=e=>(e=e%360,e<0&&(e+=360),e),Fc=Aa,zc=e=>Math.sqrt(e[0]*e[0]+e[1]*e[1]),Bc=e=>Math.sqrt(e[4]*e[4]+e[5]*e[5]),p1={x:12,y:13,z:14,translateX:12,translateY:13,translateZ:14,scaleX:zc,scaleY:Bc,scale:e=>(zc(e)+Bc(e))/2,rotateX:e=>Va(rn(Math.atan2(e[6],e[5]))),rotateY:e=>Va(rn(Math.atan2(-e[2],e[0]))),rotateZ:Fc,rotate:Fc,skewX:e=>rn(Math.atan(e[4])),skewY:e=>rn(Math.atan(e[1])),skew:e=>(Math.abs(e[1])+Math.abs(e[4]))/2};function Ra(e){return e.includes("scale")?1:0}function Ia(e,t){if(!e||e==="none")return Ra(t);const n=e.match(/^matrix3d\(([-\d.e\s,]+)\)$/u);let r,i;if(n)r=p1,i=n;else{const a=e.match(/^matrix\(([-\d.e\s,]+)\)$/u);r=h1,i=a}if(!i)return Ra(t);const s=r[t],o=i[1].split(",").map(g1);return typeof s=="function"?s(o):o[s]}const m1=(e,t)=>{const{transform:n="none"}=getComputedStyle(e);return Ia(n,t)};function g1(e){return parseFloat(e.trim())}const er=["transformPerspective","x","y","z","translateX","translateY","translateZ","scale","scaleX","scaleY","rotate","rotateX","rotateY","rotateZ","skew","skewX","skewY"],tr=new Set(er),Uc=e=>e===Jn||e===A,y1=new Set(["x","y","z"]),v1=er.filter(e=>!y1.has(e));function x1(e){const t=[];return v1.forEach(n=>{const r=e.getValue(n);r!==void 0&&(t.push([n,r.get()]),r.set(n.startsWith("scale")?1:0))}),t}const an={width:({x:e},{paddingLeft:t="0",paddingRight:n="0"})=>e.max-e.min-parseFloat(t)-parseFloat(n),height:({y:e},{paddingTop:t="0",paddingBottom:n="0"})=>e.max-e.min-parseFloat(t)-parseFloat(n),top:(e,{top:t})=>parseFloat(t),left:(e,{left:t})=>parseFloat(t),bottom:({y:e},{top:t})=>parseFloat(t)+(e.max-e.min),right:({x:e},{left:t})=>parseFloat(t)+(e.max-e.min),x:(e,{transform:t})=>Ia(t,"x"),y:(e,{transform:t})=>Ia(t,"y")};an.translateX=an.x;an.translateY=an.y;const ln=new Set;let _a=!1,Oa=!1,Fa=!1;function Vp(){if(Oa){const e=Array.from(ln).filter(r=>r.needsMeasurement),t=new Set(e.map(r=>r.element)),n=new Map;t.forEach(r=>{const i=x1(r);i.length&&(n.set(r,i),r.render())}),e.forEach(r=>r.measureInitialState()),t.forEach(r=>{r.render();const i=n.get(r);i&&i.forEach(([s,o])=>{var a;(a=r.getValue(s))==null||a.set(o)})}),e.forEach(r=>r.measureEndState()),e.forEach(r=>{r.suspendedScrollY!==void 0&&window.scrollTo(0,r.suspendedScrollY)})}Oa=!1,_a=!1,ln.forEach(e=>e.complete(Fa)),ln.clear()}function Rp(){ln.forEach(e=>{e.readKeyframes(),e.needsMeasurement&&(Oa=!0)})}function w1(){Fa=!0,Rp(),Vp(),Fa=!1}class lu{constructor(t,n,r,i,s,o=!1){this.state="pending",this.isAsync=!1,this.needsMeasurement=!1,this.unresolvedKeyframes=[...t],this.onComplete=n,this.name=r,this.motionValue=i,this.element=s,this.isAsync=o}scheduleResolve(){this.state="scheduled",this.isAsync?(ln.add(this),_a||(_a=!0,$.read(Rp),$.resolveKeyframes(Vp))):(this.readKeyframes(),this.complete())}readKeyframes(){const{unresolvedKeyframes:t,name:n,element:r,motionValue:i}=this;if(t[0]===null){const s=i==null?void 0:i.get(),o=t[t.length-1];if(s!==void 0)t[0]=s;else if(r&&n){const a=r.readValue(n,o);a!=null&&(t[0]=a)}t[0]===void 0&&(t[0]=o),i&&s===void 0&&i.set(t[0])}f1(t)}setFinalKeyframe(){}measureInitialState(){}renderEndStyles(){}measureEndState(){}complete(t=!1){this.state="complete",this.onComplete(this.unresolvedKeyframes,this.finalKeyframe,t),ln.delete(this)}cancel(){this.state==="scheduled"&&(ln.delete(this),this.state="pending")}resume(){this.state==="pending"&&this.scheduleResolve()}}const k1=e=>e.startsWith("--");function S1(e,t,n){k1(t)?e.style.setProperty(t,n):e.style[t]=n}const T1=Yl(()=>window.ScrollTimeline!==void 0),C1={};function P1(e,t){const n=Yl(e);return()=>C1[t]??n()}const Ip=P1(()=>{try{document.createElement("div").animate({opacity:0},{easing:"linear(0, 1)"})}catch{return!1}return!0},"linearEasing"),mr=([e,t,n,r])=>`cubic-bezier(${e}, ${t}, ${n}, ${r})`,Wc={linear:"linear",ease:"ease",easeIn:"ease-in",easeOut:"ease-out",easeInOut:"ease-in-out",circIn:mr([0,.65,.55,1]),circOut:mr([.55,0,1,.45]),backIn:mr([.31,.01,.66,-.59]),backOut:mr([.33,1.53,.69,.99])};function _p(e,t){if(e)return typeof e=="function"?Ip()?Mp(e,t):"ease-out":wp(e)?mr(e):Array.isArray(e)?e.map(n=>_p(n,t)||Wc.easeOut):Wc[e]}function j1(e,t,n,{delay:r=0,duration:i=300,repeat:s=0,repeatType:o="loop",ease:a="easeOut",times:l}={},u=void 0){const c={[t]:n};l&&(c.offset=l);const d=_p(a,i);Array.isArray(d)&&(c.easing=d);const f={delay:r,duration:i,easing:Array.isArray(d)?"linear":d,fill:"both",iterations:s+1,direction:o==="reverse"?"alternate":"normal"};return u&&(f.pseudoElement=u),e.animate(c,f)}function Op(e){return typeof e=="function"&&"applyToOptions"in e}function E1({type:e,...t}){return Op(e)&&Ip()?e.applyToOptions(t):(t.duration??(t.duration=300),t.ease??(t.ease="easeOut"),t)}class N1 extends ou{constructor(t){if(super(),this.finishedTime=null,this.isStopped=!1,!t)return;const{element:n,name:r,keyframes:i,pseudoElement:s,allowFlatten:o=!1,finalKeyframe:a,onComplete:l}=t;this.isPseudoElement=!!s,this.allowFlatten=o,this.options=t,Ql(typeof t.type!="string");const u=E1(t);this.animation=j1(n,r,i,u,s),u.autoplay===!1&&this.animation.pause(),this.animation.onfinish=()=>{if(this.finishedTime=this.time,!s){const c=su(i,this.options,a,this.speed);this.updateMotionValue?this.updateMotionValue(c):S1(n,r,c),this.animation.cancel()}l==null||l(),this.notifyFinished()}}play(){this.isStopped||(this.animation.play(),this.state==="finished"&&this.updateFinished())}pause(){this.animation.pause()}complete(){var t,n;(n=(t=this.animation).finish)==null||n.call(t)}cancel(){try{this.animation.cancel()}catch{}}stop(){if(this.isStopped)return;this.isStopped=!0;const{state:t}=this;t==="idle"||t==="finished"||(this.updateMotionValue?this.updateMotionValue():this.commitStyles(),this.isPseudoElement||this.cancel())}commitStyles(){var t,n;this.isPseudoElement||(n=(t=this.animation).commitStyles)==null||n.call(t)}get duration(){var n,r;const t=((r=(n=this.animation.effect)==null?void 0:n.getComputedTiming)==null?void 0:r.call(n).duration)||0;return Be(Number(t))}get iterationDuration(){const{delay:t=0}=this.options||{};return this.duration+Be(t)}get time(){return Be(Number(this.animation.currentTime)||0)}set time(t){this.finishedTime=null,this.animation.currentTime=at(t)}get speed(){return this.animation.playbackRate}set speed(t){t<0&&(this.finishedTime=null),this.animation.playbackRate=t}get state(){return this.finishedTime!==null?"finished":this.animation.playState}get startTime(){return Number(this.animation.startTime)}set startTime(t){this.animation.startTime=t}attachTimeline({timeline:t,observe:n}){var r;return this.allowFlatten&&((r=this.animation.effect)==null||r.updateTiming({easing:"linear"})),this.animation.onfinish=null,t&&T1()?(this.animation.timeline=t,We):n(this)}}const Fp={anticipate:gp,backInOut:mp,circInOut:vp};function D1(e){return e in Fp}function M1(e){typeof e.ease=="string"&&D1(e.ease)&&(e.ease=Fp[e.ease])}const bc=10;class L1 extends N1{constructor(t){M1(t),Ap(t),super(t),t.startTime&&(this.startTime=t.startTime),this.options=t}updateMotionValue(t){const{motionValue:n,onUpdate:r,onComplete:i,element:s,...o}=this.options;if(!n)return;if(t!==void 0){n.set(t);return}const a=new au({...o,autoplay:!1}),l=at(this.finishedTime??this.time);n.setWithVelocity(a.sample(l-bc).value,a.sample(l).value,bc),a.stop()}}const $c=(e,t)=>t==="zIndex"?!1:!!(typeof e=="number"||Array.isArray(e)||typeof e=="string"&&(Ut.test(e)||e==="0")&&!e.startsWith("url("));function A1(e){const t=e[0];if(e.length===1)return!0;for(let n=0;nObject.hasOwnProperty.call(Element.prototype,"animate"));function _1(e){var c;const{motionValue:t,name:n,repeatDelay:r,repeatType:i,damping:s,type:o}=e;if(!(((c=t==null?void 0:t.owner)==null?void 0:c.current)instanceof HTMLElement))return!1;const{onUpdate:l,transformTemplate:u}=t.owner.getProps();return I1()&&n&&R1.has(n)&&(n!=="transform"||!u)&&!l&&!r&&i!=="mirror"&&s!==0&&o!=="inertia"}const O1=40;class F1 extends ou{constructor({autoplay:t=!0,delay:n=0,type:r="keyframes",repeat:i=0,repeatDelay:s=0,repeatType:o="loop",keyframes:a,name:l,motionValue:u,element:c,...d}){var v;super(),this.stop=()=>{var x,S;this._animation&&(this._animation.stop(),(x=this.stopTimeline)==null||x.call(this)),(S=this.keyframeResolver)==null||S.cancel()},this.createdAt=Pe.now();const f={autoplay:t,delay:n,type:r,repeat:i,repeatDelay:s,repeatType:o,name:l,motionValue:u,element:c,...d},y=(c==null?void 0:c.KeyframeResolver)||lu;this.keyframeResolver=new y(a,(x,S,m)=>this.onKeyframesResolved(x,S,f,!m),l,u,c),(v=this.keyframeResolver)==null||v.scheduleResolve()}onKeyframesResolved(t,n,r,i){this.keyframeResolver=void 0;const{name:s,type:o,velocity:a,delay:l,isHandoff:u,onUpdate:c}=r;this.resolvedAt=Pe.now(),V1(t,s,o,a)||((xt.instantAnimations||!l)&&(c==null||c(su(t,r,n))),t[0]=t[t.length-1],za(r),r.repeat=0);const f={startTime:i?this.resolvedAt?this.resolvedAt-this.createdAt>O1?this.resolvedAt:this.createdAt:this.createdAt:void 0,finalKeyframe:n,...r,keyframes:t},y=!u&&_1(f)?new L1({...f,element:f.motionValue.owner.current}):new au(f);y.finished.then(()=>this.notifyFinished()).catch(We),this.pendingTimeline&&(this.stopTimeline=y.attachTimeline(this.pendingTimeline),this.pendingTimeline=void 0),this._animation=y}get finished(){return this._animation?this.animation.finished:this._finished}then(t,n){return this.finished.finally(t).then(()=>{})}get animation(){var t;return this._animation||((t=this.keyframeResolver)==null||t.resume(),w1()),this._animation}get duration(){return this.animation.duration}get iterationDuration(){return this.animation.iterationDuration}get time(){return this.animation.time}set time(t){this.animation.time=t}get speed(){return this.animation.speed}get state(){return this.animation.state}set speed(t){this.animation.speed=t}get startTime(){return this.animation.startTime}attachTimeline(t){return this._animation?this.stopTimeline=this.animation.attachTimeline(t):this.pendingTimeline=t,()=>this.stop()}play(){this.animation.play()}pause(){this.animation.pause()}complete(){this.animation.complete()}cancel(){var t;this._animation&&this.animation.cancel(),(t=this.keyframeResolver)==null||t.cancel()}}const z1=/^var\(--(?:([\w-]+)|([\w-]+), ?([a-zA-Z\d ()%#.,-]+))\)/u;function B1(e){const t=z1.exec(e);if(!t)return[,];const[,n,r,i]=t;return[`--${n??r}`,i]}function zp(e,t,n=1){const[r,i]=B1(e);if(!r)return;const s=window.getComputedStyle(t).getPropertyValue(r);if(s){const o=s.trim();return ap(o)?parseFloat(o):o}return eu(i)?zp(i,t,n+1):i}function uu(e,t){return(e==null?void 0:e[t])??(e==null?void 0:e.default)??e}const Bp=new Set(["width","height","top","left","right","bottom",...er]),U1={test:e=>e==="auto",parse:e=>e},Up=e=>t=>t.test(e),Wp=[Jn,A,lt,Tt,Ay,Ly,U1],Hc=e=>Wp.find(Up(e));function W1(e){return typeof e=="number"?e===0:e!==null?e==="none"||e==="0"||up(e):!0}const b1=new Set(["brightness","contrast","saturate","opacity"]);function $1(e){const[t,n]=e.slice(0,-1).split("(");if(t==="drop-shadow")return e;const[r]=n.match(tu)||[];if(!r)return e;const i=n.replace(r,"");let s=b1.has(t)?1:0;return r!==n&&(s*=100),t+"("+s+i+")"}const H1=/\b([a-z-]*)\(.*?\)/gu,Ba={...Ut,getAnimatableNone:e=>{const t=e.match(H1);return t?t.map($1).join(" "):e}},Kc={...Jn,transform:Math.round},K1={rotate:Tt,rotateX:Tt,rotateY:Tt,rotateZ:Tt,scale:Ni,scaleX:Ni,scaleY:Ni,scaleZ:Ni,skew:Tt,skewX:Tt,skewY:Tt,distance:A,translateX:A,translateY:A,translateZ:A,x:A,y:A,z:A,perspective:A,transformPerspective:A,opacity:Gr,originX:Vc,originY:Vc,originZ:A},cu={borderWidth:A,borderTopWidth:A,borderRightWidth:A,borderBottomWidth:A,borderLeftWidth:A,borderRadius:A,radius:A,borderTopLeftRadius:A,borderTopRightRadius:A,borderBottomRightRadius:A,borderBottomLeftRadius:A,width:A,maxWidth:A,height:A,maxHeight:A,top:A,right:A,bottom:A,left:A,padding:A,paddingTop:A,paddingRight:A,paddingBottom:A,paddingLeft:A,margin:A,marginTop:A,marginRight:A,marginBottom:A,marginLeft:A,backgroundPositionX:A,backgroundPositionY:A,...K1,zIndex:Kc,fillOpacity:Gr,strokeOpacity:Gr,numOctaves:Kc},G1={...cu,color:ee,backgroundColor:ee,outlineColor:ee,fill:ee,stroke:ee,borderColor:ee,borderTopColor:ee,borderRightColor:ee,borderBottomColor:ee,borderLeftColor:ee,filter:Ba,WebkitFilter:Ba},bp=e=>G1[e];function $p(e,t){let n=bp(e);return n!==Ba&&(n=Ut),n.getAnimatableNone?n.getAnimatableNone(t):void 0}const Q1=new Set(["auto","none","0"]);function Y1(e,t,n){let r=0,i;for(;r{t.getValue(l).set(u)}),this.resolveNoneKeyframes()}}function Z1(e,t,n){if(e instanceof EventTarget)return[e];if(typeof e=="string"){let r=document;const i=(n==null?void 0:n[e])??r.querySelectorAll(e);return i?Array.from(i):[]}return Array.from(e)}const Hp=(e,t)=>t&&typeof e=="number"?t.transform(e):e;function Kp(e){return lp(e)&&"offsetHeight"in e}const Gc=30,q1=e=>!isNaN(parseFloat(e));class J1{constructor(t,n={}){this.canTrackVelocity=null,this.events={},this.updateAndNotify=r=>{var s;const i=Pe.now();if(this.updatedAt!==i&&this.setPrevFrameValue(),this.prev=this.current,this.setCurrent(r),this.current!==this.prev&&((s=this.events.change)==null||s.notify(this.current),this.dependents))for(const o of this.dependents)o.dirty()},this.hasAnimated=!1,this.setCurrent(t),this.owner=n.owner}setCurrent(t){this.current=t,this.updatedAt=Pe.now(),this.canTrackVelocity===null&&t!==void 0&&(this.canTrackVelocity=q1(this.current))}setPrevFrameValue(t=this.current){this.prevFrameValue=t,this.prevUpdatedAt=this.updatedAt}onChange(t){return this.on("change",t)}on(t,n){this.events[t]||(this.events[t]=new Xl);const r=this.events[t].add(n);return t==="change"?()=>{r(),$.read(()=>{this.events.change.getSize()||this.stop()})}:r}clearListeners(){for(const t in this.events)this.events[t].clear()}attach(t,n){this.passiveEffect=t,this.stopPassiveEffect=n}set(t){this.passiveEffect?this.passiveEffect(t,this.updateAndNotify):this.updateAndNotify(t)}setWithVelocity(t,n,r){this.set(n),this.prev=void 0,this.prevFrameValue=t,this.prevUpdatedAt=this.updatedAt-r}jump(t,n=!0){this.updateAndNotify(t),this.prev=t,this.prevUpdatedAt=this.prevFrameValue=void 0,n&&this.stop(),this.stopPassiveEffect&&this.stopPassiveEffect()}dirty(){var t;(t=this.events.change)==null||t.notify(this.current)}addDependent(t){this.dependents||(this.dependents=new Set),this.dependents.add(t)}removeDependent(t){this.dependents&&this.dependents.delete(t)}get(){return this.current}getPrevious(){return this.prev}getVelocity(){const t=Pe.now();if(!this.canTrackVelocity||this.prevFrameValue===void 0||t-this.updatedAt>Gc)return 0;const n=Math.min(this.updatedAt-this.prevUpdatedAt,Gc);return cp(parseFloat(this.current)-parseFloat(this.prevFrameValue),n)}start(t){return this.stop(),new Promise(n=>{this.hasAnimated=!0,this.animation=t(n),this.events.animationStart&&this.events.animationStart.notify()}).then(()=>{this.events.animationComplete&&this.events.animationComplete.notify(),this.clearAnimation()})}stop(){this.animation&&(this.animation.stop(),this.events.animationCancel&&this.events.animationCancel.notify()),this.clearAnimation()}isAnimating(){return!!this.animation}clearAnimation(){delete this.animation}destroy(){var t,n;(t=this.dependents)==null||t.clear(),(n=this.events.destroy)==null||n.notify(),this.clearListeners(),this.stop(),this.stopPassiveEffect&&this.stopPassiveEffect()}}function Qn(e,t){return new J1(e,t)}const{schedule:du}=kp(queueMicrotask,!1),Ye={x:!1,y:!1};function Gp(){return Ye.x||Ye.y}function ev(e){return e==="x"||e==="y"?Ye[e]?null:(Ye[e]=!0,()=>{Ye[e]=!1}):Ye.x||Ye.y?null:(Ye.x=Ye.y=!0,()=>{Ye.x=Ye.y=!1})}function Qp(e,t){const n=Z1(e),r=new AbortController,i={passive:!0,...t,signal:r.signal};return[n,i,()=>r.abort()]}function Qc(e){return!(e.pointerType==="touch"||Gp())}function tv(e,t,n={}){const[r,i,s]=Qp(e,n),o=a=>{if(!Qc(a))return;const{target:l}=a,u=t(l,a);if(typeof u!="function"||!l)return;const c=d=>{Qc(d)&&(u(d),l.removeEventListener("pointerleave",c))};l.addEventListener("pointerleave",c,i)};return r.forEach(a=>{a.addEventListener("pointerenter",o,i)}),s}const Yp=(e,t)=>t?e===t?!0:Yp(e,t.parentElement):!1,fu=e=>e.pointerType==="mouse"?typeof e.button!="number"||e.button<=0:e.isPrimary!==!1,nv=new Set(["BUTTON","INPUT","SELECT","TEXTAREA","A"]);function rv(e){return nv.has(e.tagName)||e.tabIndex!==-1}const $i=new WeakSet;function Yc(e){return t=>{t.key==="Enter"&&e(t)}}function To(e,t){e.dispatchEvent(new PointerEvent("pointer"+t,{isPrimary:!0,bubbles:!0}))}const iv=(e,t)=>{const n=e.currentTarget;if(!n)return;const r=Yc(()=>{if($i.has(n))return;To(n,"down");const i=Yc(()=>{To(n,"up")}),s=()=>To(n,"cancel");n.addEventListener("keyup",i,t),n.addEventListener("blur",s,t)});n.addEventListener("keydown",r,t),n.addEventListener("blur",()=>n.removeEventListener("keydown",r),t)};function Xc(e){return fu(e)&&!Gp()}function sv(e,t,n={}){const[r,i,s]=Qp(e,n),o=a=>{const l=a.currentTarget;if(!Xc(a))return;$i.add(l);const u=t(l,a),c=(y,v)=>{window.removeEventListener("pointerup",d),window.removeEventListener("pointercancel",f),$i.has(l)&&$i.delete(l),Xc(y)&&typeof u=="function"&&u(y,{success:v})},d=y=>{c(y,l===window||l===document||n.useGlobalTarget||Yp(l,y.target))},f=y=>{c(y,!1)};window.addEventListener("pointerup",d,i),window.addEventListener("pointercancel",f,i)};return r.forEach(a=>{(n.useGlobalTarget?window:a).addEventListener("pointerdown",o,i),Kp(a)&&(a.addEventListener("focus",u=>iv(u,i)),!rv(a)&&!a.hasAttribute("tabindex")&&(a.tabIndex=0))}),s}function Xp(e){return lp(e)&&"ownerSVGElement"in e}function ov(e){return Xp(e)&&e.tagName==="svg"}const ge=e=>!!(e&&e.getVelocity),av=[...Wp,ee,Ut],lv=e=>av.find(Up(e)),hu=N.createContext({transformPagePoint:e=>e,isStatic:!1,reducedMotion:"never"});function Zc(e,t){if(typeof e=="function")return e(t);e!=null&&(e.current=t)}function uv(...e){return t=>{let n=!1;const r=e.map(i=>{const s=Zc(i,t);return!n&&typeof s=="function"&&(n=!0),s});if(n)return()=>{for(let i=0;i{const{width:u,height:c,top:d,left:f,right:y}=o.current;if(t||!s.current||!u||!c)return;const v=n==="left"?`left: ${f}`:`right: ${y}`;s.current.dataset.motionPopId=i;const x=document.createElement("style");a&&(x.nonce=a);const S=r??document.head;return S.appendChild(x),x.sheet&&x.sheet.insertRule(` + [data-motion-pop-id="${i}"] { + position: absolute !important; + width: ${u}px !important; + height: ${c}px !important; + ${v}px !important; + top: ${d}px !important; + } + `),()=>{S.contains(x)&&S.removeChild(x)}},[t]),h.jsx(dv,{isPresent:t,childRef:s,sizeRef:o,children:N.cloneElement(e,{ref:l})})}const hv=({children:e,initial:t,isPresent:n,onExitComplete:r,custom:i,presenceAffectsLayout:s,mode:o,anchorX:a,root:l})=>{const u=$l(pv),c=N.useId();let d=!0,f=N.useMemo(()=>(d=!1,{id:c,initial:t,isPresent:n,custom:i,onExitComplete:y=>{u.set(y,!0);for(const v of u.values())if(!v)return;r&&r()},register:y=>(u.set(y,!1),()=>u.delete(y))}),[n,u,r]);return s&&d&&(f={...f}),N.useMemo(()=>{u.forEach((y,v)=>u.set(v,!1))},[n]),N.useEffect(()=>{!n&&!u.size&&r&&r()},[n]),o==="popLayout"&&(e=h.jsx(fv,{isPresent:n,anchorX:a,root:l,children:e})),h.jsx(zs.Provider,{value:f,children:e})};function pv(){return new Map}function Zp(e=!0){const t=N.useContext(zs);if(t===null)return[!0,null];const{isPresent:n,onExitComplete:r,register:i}=t,s=N.useId();N.useEffect(()=>{if(e)return i(s)},[e]);const o=N.useCallback(()=>e&&r&&r(s),[s,r,e]);return!n&&r?[!1,o]:[!0]}const Di=e=>e.key||"";function qc(e){const t=[];return N.Children.forEach(e,n=>{N.isValidElement(n)&&t.push(n)}),t}const Jc=({children:e,custom:t,initial:n=!0,onExitComplete:r,presenceAffectsLayout:i=!0,mode:s="sync",propagate:o=!1,anchorX:a="left",root:l})=>{const[u,c]=Zp(o),d=N.useMemo(()=>qc(e),[e]),f=o&&!u?[]:d.map(Di),y=N.useRef(!0),v=N.useRef(d),x=$l(()=>new Map),[S,m]=N.useState(d),[p,g]=N.useState(d);op(()=>{y.current=!1,v.current=d;for(let T=0;T{const P=Di(T),C=o&&!u?!1:d===p||f.includes(P),R=()=>{if(x.has(P))x.set(P,!0);else return;let M=!0;x.forEach(X=>{X||(M=!1)}),M&&(k==null||k(),g(v.current),o&&(c==null||c()),r&&r())};return h.jsx(hv,{isPresent:C,initial:!y.current||n?void 0:!1,custom:t,presenceAffectsLayout:i,mode:s,root:l,onExitComplete:C?void 0:R,anchorX:a,children:T},P)})})},qp=N.createContext({strict:!1}),ed={animation:["animate","variants","whileHover","whileTap","exit","whileInView","whileFocus","whileDrag"],exit:["exit"],drag:["drag","dragControls"],focus:["whileFocus"],hover:["whileHover","onHoverStart","onHoverEnd"],tap:["whileTap","onTap","onTapStart","onTapCancel"],pan:["onPan","onPanStart","onPanSessionStart","onPanEnd"],inView:["whileInView","onViewportEnter","onViewportLeave"],layout:["layout","layoutId"]},Yn={};for(const e in ed)Yn[e]={isEnabled:t=>ed[e].some(n=>!!t[n])};function mv(e){for(const t in e)Yn[t]={...Yn[t],...e[t]}}const gv=new Set(["animate","exit","variants","initial","style","values","variants","transition","transformTemplate","custom","inherit","onBeforeLayoutMeasure","onAnimationStart","onAnimationComplete","onUpdate","onDragStart","onDrag","onDragEnd","onMeasureDragConstraints","onDirectionLock","onDragTransitionEnd","_dragX","_dragY","onHoverStart","onHoverEnd","onViewportEnter","onViewportLeave","globalTapTarget","ignoreStrict","viewport"]);function ws(e){return e.startsWith("while")||e.startsWith("drag")&&e!=="draggable"||e.startsWith("layout")||e.startsWith("onTap")||e.startsWith("onPan")||e.startsWith("onLayout")||gv.has(e)}let Jp=e=>!ws(e);function yv(e){typeof e=="function"&&(Jp=t=>t.startsWith("on")?!ws(t):e(t))}try{yv(require("@emotion/is-prop-valid").default)}catch{}function vv(e,t,n){const r={};for(const i in e)i==="values"&&typeof e.values=="object"||(Jp(i)||n===!0&&ws(i)||!t&&!ws(i)||e.draggable&&i.startsWith("onDrag"))&&(r[i]=e[i]);return r}const Bs=N.createContext({});function Us(e){return e!==null&&typeof e=="object"&&typeof e.start=="function"}function Yr(e){return typeof e=="string"||Array.isArray(e)}const pu=["animate","whileInView","whileFocus","whileHover","whileTap","whileDrag","exit"],mu=["initial",...pu];function Ws(e){return Us(e.animate)||mu.some(t=>Yr(e[t]))}function em(e){return!!(Ws(e)||e.variants)}function xv(e,t){if(Ws(e)){const{initial:n,animate:r}=e;return{initial:n===!1||Yr(n)?n:void 0,animate:Yr(r)?r:void 0}}return e.inherit!==!1?t:{}}function wv(e){const{initial:t,animate:n}=xv(e,N.useContext(Bs));return N.useMemo(()=>({initial:t,animate:n}),[td(t),td(n)])}function td(e){return Array.isArray(e)?e.join(" "):e}const Xr={};function kv(e){for(const t in e)Xr[t]=e[t],Jl(t)&&(Xr[t].isCSSVariable=!0)}function tm(e,{layout:t,layoutId:n}){return tr.has(e)||e.startsWith("origin")||(t||n!==void 0)&&(!!Xr[e]||e==="opacity")}const Sv={x:"translateX",y:"translateY",z:"translateZ",transformPerspective:"perspective"},Tv=er.length;function Cv(e,t,n){let r="",i=!0;for(let s=0;s({style:{},transform:{},transformOrigin:{},vars:{}});function nm(e,t,n){for(const r in t)!ge(t[r])&&!tm(r,n)&&(e[r]=t[r])}function Pv({transformTemplate:e},t){return N.useMemo(()=>{const n=yu();return gu(n,t,e),Object.assign({},n.vars,n.style)},[t])}function jv(e,t){const n=e.style||{},r={};return nm(r,n,e),Object.assign(r,Pv(e,t)),r}function Ev(e,t){const n={},r=jv(e,t);return e.drag&&e.dragListener!==!1&&(n.draggable=!1,r.userSelect=r.WebkitUserSelect=r.WebkitTouchCallout="none",r.touchAction=e.drag===!0?"none":`pan-${e.drag==="x"?"y":"x"}`),e.tabIndex===void 0&&(e.onTap||e.onTapStart||e.whileTap)&&(n.tabIndex=0),n.style=r,n}const Nv={offset:"stroke-dashoffset",array:"stroke-dasharray"},Dv={offset:"strokeDashoffset",array:"strokeDasharray"};function Mv(e,t,n=1,r=0,i=!0){e.pathLength=1;const s=i?Nv:Dv;e[s.offset]=A.transform(-r);const o=A.transform(t),a=A.transform(n);e[s.array]=`${o} ${a}`}function rm(e,{attrX:t,attrY:n,attrScale:r,pathLength:i,pathSpacing:s=1,pathOffset:o=0,...a},l,u,c){if(gu(e,a,u),l){e.style.viewBox&&(e.attrs.viewBox=e.style.viewBox);return}e.attrs=e.style,e.style={};const{attrs:d,style:f}=e;d.transform&&(f.transform=d.transform,delete d.transform),(f.transform||d.transformOrigin)&&(f.transformOrigin=d.transformOrigin??"50% 50%",delete d.transformOrigin),f.transform&&(f.transformBox=(c==null?void 0:c.transformBox)??"fill-box",delete d.transformBox),t!==void 0&&(d.x=t),n!==void 0&&(d.y=n),r!==void 0&&(d.scale=r),i!==void 0&&Mv(d,i,s,o,!1)}const im=()=>({...yu(),attrs:{}}),sm=e=>typeof e=="string"&&e.toLowerCase()==="svg";function Lv(e,t,n,r){const i=N.useMemo(()=>{const s=im();return rm(s,t,sm(r),e.transformTemplate,e.style),{...s.attrs,style:{...s.style}}},[t]);if(e.style){const s={};nm(s,e.style,e),i.style={...s,...i.style}}return i}const Av=["animate","circle","defs","desc","ellipse","g","image","line","filter","marker","mask","metadata","path","pattern","polygon","polyline","rect","stop","switch","symbol","svg","text","tspan","use","view"];function vu(e){return typeof e!="string"||e.includes("-")?!1:!!(Av.indexOf(e)>-1||/[A-Z]/u.test(e))}function Vv(e,t,n,{latestValues:r},i,s=!1){const a=(vu(e)?Lv:Ev)(t,r,i,e),l=vv(t,typeof e=="string",s),u=e!==N.Fragment?{...l,...a,ref:n}:{},{children:c}=t,d=N.useMemo(()=>ge(c)?c.get():c,[c]);return N.createElement(e,{...u,children:d})}function nd(e){const t=[{},{}];return e==null||e.values.forEach((n,r)=>{t[0][r]=n.get(),t[1][r]=n.getVelocity()}),t}function xu(e,t,n,r){if(typeof t=="function"){const[i,s]=nd(r);t=t(n!==void 0?n:e.custom,i,s)}if(typeof t=="string"&&(t=e.variants&&e.variants[t]),typeof t=="function"){const[i,s]=nd(r);t=t(n!==void 0?n:e.custom,i,s)}return t}function Hi(e){return ge(e)?e.get():e}function Rv({scrapeMotionValuesFromProps:e,createRenderState:t},n,r,i){return{latestValues:Iv(n,r,i,e),renderState:t()}}function Iv(e,t,n,r){const i={},s=r(e,{});for(const f in s)i[f]=Hi(s[f]);let{initial:o,animate:a}=e;const l=Ws(e),u=em(e);t&&u&&!l&&e.inherit!==!1&&(o===void 0&&(o=t.initial),a===void 0&&(a=t.animate));let c=n?n.initial===!1:!1;c=c||o===!1;const d=c?a:o;if(d&&typeof d!="boolean"&&!Us(d)){const f=Array.isArray(d)?d:[d];for(let y=0;y(t,n)=>{const r=N.useContext(Bs),i=N.useContext(zs),s=()=>Rv(e,t,r,i);return n?s():$l(s)};function wu(e,t,n){var s;const{style:r}=e,i={};for(const o in r)(ge(r[o])||t.style&&ge(t.style[o])||tm(o,e)||((s=n==null?void 0:n.getValue(o))==null?void 0:s.liveStyle)!==void 0)&&(i[o]=r[o]);return i}const _v=om({scrapeMotionValuesFromProps:wu,createRenderState:yu});function am(e,t,n){const r=wu(e,t,n);for(const i in e)if(ge(e[i])||ge(t[i])){const s=er.indexOf(i)!==-1?"attr"+i.charAt(0).toUpperCase()+i.substring(1):i;r[s]=e[i]}return r}const Ov=om({scrapeMotionValuesFromProps:am,createRenderState:im}),Fv=Symbol.for("motionComponentSymbol");function Mn(e){return e&&typeof e=="object"&&Object.prototype.hasOwnProperty.call(e,"current")}function zv(e,t,n){return N.useCallback(r=>{r&&e.onMount&&e.onMount(r),t&&(r?t.mount(r):t.unmount()),n&&(typeof n=="function"?n(r):Mn(n)&&(n.current=r))},[t])}const ku=e=>e.replace(/([a-z])([A-Z])/gu,"$1-$2").toLowerCase(),Bv="framerAppearId",lm="data-"+ku(Bv),um=N.createContext({});function Uv(e,t,n,r,i){var x,S;const{visualElement:s}=N.useContext(Bs),o=N.useContext(qp),a=N.useContext(zs),l=N.useContext(hu).reducedMotion,u=N.useRef(null);r=r||o.renderer,!u.current&&r&&(u.current=r(e,{visualState:t,parent:s,props:n,presenceContext:a,blockInitialAnimation:a?a.initial===!1:!1,reducedMotionConfig:l}));const c=u.current,d=N.useContext(um);c&&!c.projection&&i&&(c.type==="html"||c.type==="svg")&&Wv(u.current,n,i,d);const f=N.useRef(!1);N.useInsertionEffect(()=>{c&&f.current&&c.update(n,a)});const y=n[lm],v=N.useRef(!!y&&!((x=window.MotionHandoffIsComplete)!=null&&x.call(window,y))&&((S=window.MotionHasOptimisedAnimation)==null?void 0:S.call(window,y)));return op(()=>{c&&(f.current=!0,window.MotionIsMounted=!0,c.updateFeatures(),c.scheduleRenderMicrotask(),v.current&&c.animationState&&c.animationState.animateChanges())}),N.useEffect(()=>{c&&(!v.current&&c.animationState&&c.animationState.animateChanges(),v.current&&(queueMicrotask(()=>{var m;(m=window.MotionHandoffMarkAsComplete)==null||m.call(window,y)}),v.current=!1),c.enteringChildren=void 0)}),c}function Wv(e,t,n,r){const{layoutId:i,layout:s,drag:o,dragConstraints:a,layoutScroll:l,layoutRoot:u,layoutCrossfade:c}=t;e.projection=new n(e.latestValues,t["data-framer-portal-id"]?void 0:cm(e.parent)),e.projection.setOptions({layoutId:i,layout:s,alwaysMeasureLayout:!!o||a&&Mn(a),visualElement:e,animationType:typeof s=="string"?s:"both",initialPromotionConfig:r,crossfade:c,layoutScroll:l,layoutRoot:u})}function cm(e){if(e)return e.options.allowProjection!==!1?e.projection:cm(e.parent)}function Co(e,{forwardMotionProps:t=!1}={},n,r){n&&mv(n);const i=vu(e)?Ov:_v;function s(a,l){let u;const c={...N.useContext(hu),...a,layoutId:bv(a)},{isStatic:d}=c,f=wv(a),y=i(a,d);if(!d&&Hl){$v();const v=Hv(c);u=v.MeasureLayout,f.visualElement=Uv(e,y,c,r,v.ProjectionNode)}return h.jsxs(Bs.Provider,{value:f,children:[u&&f.visualElement?h.jsx(u,{visualElement:f.visualElement,...c}):null,Vv(e,a,zv(y,f.visualElement,l),y,d,t)]})}s.displayName=`motion.${typeof e=="string"?e:`create(${e.displayName??e.name??""})`}`;const o=N.forwardRef(s);return o[Fv]=e,o}function bv({layoutId:e}){const t=N.useContext(bl).id;return t&&e!==void 0?t+"-"+e:e}function $v(e,t){N.useContext(qp).strict}function Hv(e){const{drag:t,layout:n}=Yn;if(!t&&!n)return{};const r={...t,...n};return{MeasureLayout:t!=null&&t.isEnabled(e)||n!=null&&n.isEnabled(e)?r.MeasureLayout:void 0,ProjectionNode:r.ProjectionNode}}function Kv(e,t){if(typeof Proxy>"u")return Co;const n=new Map,r=(s,o)=>Co(s,o,e,t),i=(s,o)=>r(s,o);return new Proxy(i,{get:(s,o)=>o==="create"?r:(n.has(o)||n.set(o,Co(o,void 0,e,t)),n.get(o))})}function dm({top:e,left:t,right:n,bottom:r}){return{x:{min:t,max:n},y:{min:e,max:r}}}function Gv({x:e,y:t}){return{top:t.min,right:e.max,bottom:t.max,left:e.min}}function Qv(e,t){if(!t)return e;const n=t({x:e.left,y:e.top}),r=t({x:e.right,y:e.bottom});return{top:n.y,left:n.x,bottom:r.y,right:r.x}}function Po(e){return e===void 0||e===1}function Ua({scale:e,scaleX:t,scaleY:n}){return!Po(e)||!Po(t)||!Po(n)}function qt(e){return Ua(e)||fm(e)||e.z||e.rotate||e.rotateX||e.rotateY||e.skewX||e.skewY}function fm(e){return rd(e.x)||rd(e.y)}function rd(e){return e&&e!=="0%"}function ks(e,t,n){const r=e-n,i=t*r;return n+i}function id(e,t,n,r,i){return i!==void 0&&(e=ks(e,i,r)),ks(e,n,r)+t}function Wa(e,t=0,n=1,r,i){e.min=id(e.min,t,n,r,i),e.max=id(e.max,t,n,r,i)}function hm(e,{x:t,y:n}){Wa(e.x,t.translate,t.scale,t.originPoint),Wa(e.y,n.translate,n.scale,n.originPoint)}const sd=.999999999999,od=1.0000000000001;function Yv(e,t,n,r=!1){const i=n.length;if(!i)return;t.x=t.y=1;let s,o;for(let a=0;asd&&(t.x=1),t.ysd&&(t.y=1)}function Ln(e,t){e.min=e.min+t,e.max=e.max+t}function ad(e,t,n,r,i=.5){const s=G(e.min,e.max,i);Wa(e,t,n,s,r)}function An(e,t){ad(e.x,t.x,t.scaleX,t.scale,t.originX),ad(e.y,t.y,t.scaleY,t.scale,t.originY)}function pm(e,t){return dm(Qv(e.getBoundingClientRect(),t))}function Xv(e,t,n){const r=pm(e,n),{scroll:i}=t;return i&&(Ln(r.x,i.offset.x),Ln(r.y,i.offset.y)),r}const ld=()=>({translate:0,scale:1,origin:0,originPoint:0}),Vn=()=>({x:ld(),y:ld()}),ud=()=>({min:0,max:0}),ne=()=>({x:ud(),y:ud()}),ba={current:null},mm={current:!1};function Zv(){if(mm.current=!0,!!Hl)if(window.matchMedia){const e=window.matchMedia("(prefers-reduced-motion)"),t=()=>ba.current=e.matches;e.addEventListener("change",t),t()}else ba.current=!1}const qv=new WeakMap;function Jv(e,t,n){for(const r in t){const i=t[r],s=n[r];if(ge(i))e.addValue(r,i);else if(ge(s))e.addValue(r,Qn(i,{owner:e}));else if(s!==i)if(e.hasValue(r)){const o=e.getValue(r);o.liveStyle===!0?o.jump(i):o.hasAnimated||o.set(i)}else{const o=e.getStaticValue(r);e.addValue(r,Qn(o!==void 0?o:i,{owner:e}))}}for(const r in n)t[r]===void 0&&e.removeValue(r);return t}const cd=["AnimationStart","AnimationComplete","Update","BeforeLayoutMeasure","LayoutMeasure","LayoutAnimationStart","LayoutAnimationComplete"];class ex{scrapeMotionValuesFromProps(t,n,r){return{}}constructor({parent:t,props:n,presenceContext:r,reducedMotionConfig:i,blockInitialAnimation:s,visualState:o},a={}){this.current=null,this.children=new Set,this.isVariantNode=!1,this.isControllingVariants=!1,this.shouldReduceMotion=null,this.values=new Map,this.KeyframeResolver=lu,this.features={},this.valueSubscriptions=new Map,this.prevMotionValues={},this.events={},this.propEventSubscriptions={},this.notifyUpdate=()=>this.notify("Update",this.latestValues),this.render=()=>{this.current&&(this.triggerBuild(),this.renderInstance(this.current,this.renderState,this.props.style,this.projection))},this.renderScheduledAt=0,this.scheduleRender=()=>{const f=Pe.now();this.renderScheduledAtthis.bindToMotionValue(i,r)),mm.current||Zv(),this.shouldReduceMotion=this.reducedMotionConfig==="never"?!1:this.reducedMotionConfig==="always"?!0:ba.current,(n=this.parent)==null||n.addChild(this),this.update(this.props,this.presenceContext)}unmount(){var t;this.projection&&this.projection.unmount(),Bt(this.notifyUpdate),Bt(this.render),this.valueSubscriptions.forEach(n=>n()),this.valueSubscriptions.clear(),this.removeFromVariantTree&&this.removeFromVariantTree(),(t=this.parent)==null||t.removeChild(this);for(const n in this.events)this.events[n].clear();for(const n in this.features){const r=this.features[n];r&&(r.unmount(),r.isMounted=!1)}this.current=null}addChild(t){this.children.add(t),this.enteringChildren??(this.enteringChildren=new Set),this.enteringChildren.add(t)}removeChild(t){this.children.delete(t),this.enteringChildren&&this.enteringChildren.delete(t)}bindToMotionValue(t,n){this.valueSubscriptions.has(t)&&this.valueSubscriptions.get(t)();const r=tr.has(t);r&&this.onBindTransform&&this.onBindTransform();const i=n.on("change",o=>{this.latestValues[t]=o,this.props.onUpdate&&$.preRender(this.notifyUpdate),r&&this.projection&&(this.projection.isTransformDirty=!0),this.scheduleRender()});let s;window.MotionCheckAppearSync&&(s=window.MotionCheckAppearSync(this,t,n)),this.valueSubscriptions.set(t,()=>{i(),s&&s(),n.owner&&n.stop()})}sortNodePosition(t){return!this.current||!this.sortInstanceNodePosition||this.type!==t.type?0:this.sortInstanceNodePosition(this.current,t.current)}updateFeatures(){let t="animation";for(t in Yn){const n=Yn[t];if(!n)continue;const{isEnabled:r,Feature:i}=n;if(!this.features[t]&&i&&r(this.props)&&(this.features[t]=new i(this)),this.features[t]){const s=this.features[t];s.isMounted?s.update():(s.mount(),s.isMounted=!0)}}}triggerBuild(){this.build(this.renderState,this.latestValues,this.props)}measureViewportBox(){return this.current?this.measureInstanceViewportBox(this.current,this.props):ne()}getStaticValue(t){return this.latestValues[t]}setStaticValue(t,n){this.latestValues[t]=n}update(t,n){(t.transformTemplate||this.props.transformTemplate)&&this.scheduleRender(),this.prevProps=this.props,this.props=t,this.prevPresenceContext=this.presenceContext,this.presenceContext=n;for(let r=0;rn.variantChildren.delete(t)}addValue(t,n){const r=this.values.get(t);n!==r&&(r&&this.removeValue(t),this.bindToMotionValue(t,n),this.values.set(t,n),this.latestValues[t]=n.get())}removeValue(t){this.values.delete(t);const n=this.valueSubscriptions.get(t);n&&(n(),this.valueSubscriptions.delete(t)),delete this.latestValues[t],this.removeValueFromRenderState(t,this.renderState)}hasValue(t){return this.values.has(t)}getValue(t,n){if(this.props.values&&this.props.values[t])return this.props.values[t];let r=this.values.get(t);return r===void 0&&n!==void 0&&(r=Qn(n===null?void 0:n,{owner:this}),this.addValue(t,r)),r}readValue(t,n){let r=this.latestValues[t]!==void 0||!this.current?this.latestValues[t]:this.getBaseTargetFromProps(this.props,t)??this.readValueFromInstance(this.current,t,this.options);return r!=null&&(typeof r=="string"&&(ap(r)||up(r))?r=parseFloat(r):!lv(r)&&Ut.test(n)&&(r=$p(t,n)),this.setBaseTarget(t,ge(r)?r.get():r)),ge(r)?r.get():r}setBaseTarget(t,n){this.baseTarget[t]=n}getBaseTarget(t){var s;const{initial:n}=this.props;let r;if(typeof n=="string"||typeof n=="object"){const o=xu(this.props,n,(s=this.presenceContext)==null?void 0:s.custom);o&&(r=o[t])}if(n&&r!==void 0)return r;const i=this.getBaseTargetFromProps(this.props,t);return i!==void 0&&!ge(i)?i:this.initialValues[t]!==void 0&&r===void 0?void 0:this.baseTarget[t]}on(t,n){return this.events[t]||(this.events[t]=new Xl),this.events[t].add(n)}notify(t,...n){this.events[t]&&this.events[t].notify(...n)}scheduleRenderMicrotask(){du.render(this.render)}}class gm extends ex{constructor(){super(...arguments),this.KeyframeResolver=X1}sortInstanceNodePosition(t,n){return t.compareDocumentPosition(n)&2?1:-1}getBaseTargetFromProps(t,n){return t.style?t.style[n]:void 0}removeValueFromRenderState(t,{vars:n,style:r}){delete n[t],delete r[t]}handleChildMotionValue(){this.childSubscription&&(this.childSubscription(),delete this.childSubscription);const{children:t}=this.props;ge(t)&&(this.childSubscription=t.on("change",n=>{this.current&&(this.current.textContent=`${n}`)}))}}function ym(e,{style:t,vars:n},r,i){const s=e.style;let o;for(o in t)s[o]=t[o];i==null||i.applyProjectionStyles(s,r);for(o in n)s.setProperty(o,n[o])}function tx(e){return window.getComputedStyle(e)}class nx extends gm{constructor(){super(...arguments),this.type="html",this.renderInstance=ym}readValueFromInstance(t,n){var r;if(tr.has(n))return(r=this.projection)!=null&&r.isProjecting?Ra(n):m1(t,n);{const i=tx(t),s=(Jl(n)?i.getPropertyValue(n):i[n])||0;return typeof s=="string"?s.trim():s}}measureInstanceViewportBox(t,{transformPagePoint:n}){return pm(t,n)}build(t,n,r){gu(t,n,r.transformTemplate)}scrapeMotionValuesFromProps(t,n,r){return wu(t,n,r)}}const vm=new Set(["baseFrequency","diffuseConstant","kernelMatrix","kernelUnitLength","keySplines","keyTimes","limitingConeAngle","markerHeight","markerWidth","numOctaves","targetX","targetY","surfaceScale","specularConstant","specularExponent","stdDeviation","tableValues","viewBox","gradientTransform","pathLength","startOffset","textLength","lengthAdjust"]);function rx(e,t,n,r){ym(e,t,void 0,r);for(const i in t.attrs)e.setAttribute(vm.has(i)?i:ku(i),t.attrs[i])}class ix extends gm{constructor(){super(...arguments),this.type="svg",this.isSVGTag=!1,this.measureInstanceViewportBox=ne}getBaseTargetFromProps(t,n){return t[n]}readValueFromInstance(t,n){if(tr.has(n)){const r=bp(n);return r&&r.default||0}return n=vm.has(n)?n:ku(n),t.getAttribute(n)}scrapeMotionValuesFromProps(t,n,r){return am(t,n,r)}build(t,n,r){rm(t,n,this.isSVGTag,r.transformTemplate,r.style)}renderInstance(t,n,r,i){rx(t,n,r,i)}mount(t){this.isSVGTag=sm(t.tagName),super.mount(t)}}const sx=(e,t)=>vu(e)?new ix(t):new nx(t,{allowProjection:e!==N.Fragment});function Bn(e,t,n){const r=e.getProps();return xu(r,t,n!==void 0?n:r.custom,e)}const $a=e=>Array.isArray(e);function ox(e,t,n){e.hasValue(t)?e.getValue(t).set(n):e.addValue(t,Qn(n))}function ax(e){return $a(e)?e[e.length-1]||0:e}function lx(e,t){const n=Bn(e,t);let{transitionEnd:r={},transition:i={},...s}=n||{};s={...s,...r};for(const o in s){const a=ax(s[o]);ox(e,o,a)}}function ux(e){return!!(ge(e)&&e.add)}function Ha(e,t){const n=e.getValue("willChange");if(ux(n))return n.add(t);if(!n&&xt.WillChange){const r=new xt.WillChange("auto");e.addValue("willChange",r),r.add(t)}}function xm(e){return e.props[lm]}const cx=e=>e!==null;function dx(e,{repeat:t,repeatType:n="loop"},r){const i=e.filter(cx),s=t&&n!=="loop"&&t%2===1?0:i.length-1;return i[s]}const fx={type:"spring",stiffness:500,damping:25,restSpeed:10},hx=e=>({type:"spring",stiffness:550,damping:e===0?2*Math.sqrt(550):30,restSpeed:10}),px={type:"keyframes",duration:.8},mx={type:"keyframes",ease:[.25,.1,.35,1],duration:.3},gx=(e,{keyframes:t})=>t.length>2?px:tr.has(e)?e.startsWith("scale")?hx(t[1]):fx:mx;function yx({when:e,delay:t,delayChildren:n,staggerChildren:r,staggerDirection:i,repeat:s,repeatType:o,repeatDelay:a,from:l,elapsed:u,...c}){return!!Object.keys(c).length}const Su=(e,t,n,r={},i,s)=>o=>{const a=uu(r,e)||{},l=a.delay||r.delay||0;let{elapsed:u=0}=r;u=u-at(l);const c={keyframes:Array.isArray(n)?n:[null,n],ease:"easeOut",velocity:t.getVelocity(),...a,delay:-u,onUpdate:f=>{t.set(f),a.onUpdate&&a.onUpdate(f)},onComplete:()=>{o(),a.onComplete&&a.onComplete()},name:e,motionValue:t,element:s?void 0:i};yx(a)||Object.assign(c,gx(e,c)),c.duration&&(c.duration=at(c.duration)),c.repeatDelay&&(c.repeatDelay=at(c.repeatDelay)),c.from!==void 0&&(c.keyframes[0]=c.from);let d=!1;if((c.type===!1||c.duration===0&&!c.repeatDelay)&&(za(c),c.delay===0&&(d=!0)),(xt.instantAnimations||xt.skipAnimations)&&(d=!0,za(c),c.delay=0),c.allowFlatten=!a.type&&!a.ease,d&&!s&&t.get()!==void 0){const f=dx(c.keyframes,a);if(f!==void 0){$.update(()=>{c.onUpdate(f),c.onComplete()});return}}return a.isSync?new au(c):new F1(c)};function vx({protectedKeys:e,needsAnimating:t},n){const r=e.hasOwnProperty(n)&&t[n]!==!0;return t[n]=!1,r}function wm(e,t,{delay:n=0,transitionOverride:r,type:i}={}){let{transition:s=e.getDefaultTransition(),transitionEnd:o,...a}=t;r&&(s=r);const l=[],u=i&&e.animationState&&e.animationState.getState()[i];for(const c in a){const d=e.getValue(c,e.latestValues[c]??null),f=a[c];if(f===void 0||u&&vx(u,c))continue;const y={delay:n,...uu(s||{},c)},v=d.get();if(v!==void 0&&!d.isAnimating&&!Array.isArray(f)&&f===v&&!y.velocity)continue;let x=!1;if(window.MotionHandoffAnimation){const m=xm(e);if(m){const p=window.MotionHandoffAnimation(m,c,$);p!==null&&(y.startTime=p,x=!0)}}Ha(e,c),d.start(Su(c,d,f,e.shouldReduceMotion&&Bp.has(c)?{type:!1}:y,e,x));const S=d.animation;S&&l.push(S)}return o&&Promise.all(l).then(()=>{$.update(()=>{o&&lx(e,o)})}),l}function km(e,t,n,r=0,i=1){const s=Array.from(e).sort((u,c)=>u.sortNodePosition(c)).indexOf(t),o=e.size,a=(o-1)*r;return typeof n=="function"?n(s,o):i===1?s*r:a-s*r}function Ka(e,t,n={}){var l;const r=Bn(e,t,n.type==="exit"?(l=e.presenceContext)==null?void 0:l.custom:void 0);let{transition:i=e.getDefaultTransition()||{}}=r||{};n.transitionOverride&&(i=n.transitionOverride);const s=r?()=>Promise.all(wm(e,r,n)):()=>Promise.resolve(),o=e.variantChildren&&e.variantChildren.size?(u=0)=>{const{delayChildren:c=0,staggerChildren:d,staggerDirection:f}=i;return xx(e,t,u,c,d,f,n)}:()=>Promise.resolve(),{when:a}=i;if(a){const[u,c]=a==="beforeChildren"?[s,o]:[o,s];return u().then(()=>c())}else return Promise.all([s(),o(n.delay)])}function xx(e,t,n=0,r=0,i=0,s=1,o){const a=[];for(const l of e.variantChildren)l.notify("AnimationStart",t),a.push(Ka(l,t,{...o,delay:n+(typeof r=="function"?0:r)+km(e.variantChildren,l,r,i,s)}).then(()=>l.notify("AnimationComplete",t)));return Promise.all(a)}function wx(e,t,n={}){e.notify("AnimationStart",t);let r;if(Array.isArray(t)){const i=t.map(s=>Ka(e,s,n));r=Promise.all(i)}else if(typeof t=="string")r=Ka(e,t,n);else{const i=typeof t=="function"?Bn(e,t,n.custom):t;r=Promise.all(wm(e,i,n))}return r.then(()=>{e.notify("AnimationComplete",t)})}function Sm(e,t){if(!Array.isArray(t))return!1;const n=t.length;if(n!==e.length)return!1;for(let r=0;rPromise.all(t.map(({animation:n,options:r})=>wx(e,n,r)))}function Px(e){let t=Cx(e),n=dd(),r=!0;const i=l=>(u,c)=>{var f;const d=Bn(e,c,l==="exit"?(f=e.presenceContext)==null?void 0:f.custom:void 0);if(d){const{transition:y,transitionEnd:v,...x}=d;u={...u,...x,...v}}return u};function s(l){t=l(e)}function o(l){const{props:u}=e,c=Tm(e.parent)||{},d=[],f=new Set;let y={},v=1/0;for(let S=0;Sv&&w,R=!1;const M=Array.isArray(g)?g:[g];let X=M.reduce(i(m),{});k===!1&&(X={});const{prevResolvedValues:Ke={}}=p,Ie={...Ke,...X},kt=B=>{C=!0,f.has(B)&&(R=!0,f.delete(B)),p.needsAnimating[B]=!0;const E=e.getValue(B);E&&(E.liveStyle=!1)};for(const B in Ie){const E=X[B],L=Ke[B];if(y.hasOwnProperty(B))continue;let V=!1;$a(E)&&$a(L)?V=!Sm(E,L):V=E!==L,V?E!=null?kt(B):f.add(B):E!==void 0&&f.has(B)?kt(B):p.protectedKeys[B]=!0}p.prevProp=g,p.prevResolvedValues=X,p.isActive&&(y={...y,...X}),r&&e.blockInitialAnimation&&(C=!1);const F=T&&P;C&&(!F||R)&&d.push(...M.map(B=>{const E={type:m};if(typeof B=="string"&&r&&!F&&e.manuallyAnimateOnMount&&e.parent){const{parent:L}=e,V=Bn(L,B);if(L.enteringChildren&&V){const{delayChildren:H}=V.transition||{};E.delay=km(L.enteringChildren,e,H)}}return{animation:B,options:E}}))}if(f.size){const S={};if(typeof u.initial!="boolean"){const m=Bn(e,Array.isArray(u.initial)?u.initial[0]:u.initial);m&&m.transition&&(S.transition=m.transition)}f.forEach(m=>{const p=e.getBaseTarget(m),g=e.getValue(m);g&&(g.liveStyle=!0),S[m]=p??null}),d.push({animation:S})}let x=!!d.length;return r&&(u.initial===!1||u.initial===u.animate)&&!e.manuallyAnimateOnMount&&(x=!1),r=!1,x?t(d):Promise.resolve()}function a(l,u){var d;if(n[l].isActive===u)return Promise.resolve();(d=e.variantChildren)==null||d.forEach(f=>{var y;return(y=f.animationState)==null?void 0:y.setActive(l,u)}),n[l].isActive=u;const c=o(l);for(const f in n)n[f].protectedKeys={};return c}return{animateChanges:o,setActive:a,setAnimateFunction:s,getState:()=>n,reset:()=>{n=dd()}}}function jx(e,t){return typeof t=="string"?t!==e:Array.isArray(t)?!Sm(t,e):!1}function Yt(e=!1){return{isActive:e,protectedKeys:{},needsAnimating:{},prevResolvedValues:{}}}function dd(){return{animate:Yt(!0),whileInView:Yt(),whileHover:Yt(),whileTap:Yt(),whileDrag:Yt(),whileFocus:Yt(),exit:Yt()}}class Ht{constructor(t){this.isMounted=!1,this.node=t}update(){}}class Ex extends Ht{constructor(t){super(t),t.animationState||(t.animationState=Px(t))}updateAnimationControlsSubscription(){const{animate:t}=this.node.getProps();Us(t)&&(this.unmountControls=t.subscribe(this.node))}mount(){this.updateAnimationControlsSubscription()}update(){const{animate:t}=this.node.getProps(),{animate:n}=this.node.prevProps||{};t!==n&&this.updateAnimationControlsSubscription()}unmount(){var t;this.node.animationState.reset(),(t=this.unmountControls)==null||t.call(this)}}let Nx=0;class Dx extends Ht{constructor(){super(...arguments),this.id=Nx++}update(){if(!this.node.presenceContext)return;const{isPresent:t,onExitComplete:n}=this.node.presenceContext,{isPresent:r}=this.node.prevPresenceContext||{};if(!this.node.animationState||t===r)return;const i=this.node.animationState.setActive("exit",!t);n&&!t&&i.then(()=>{n(this.id)})}mount(){const{register:t,onExitComplete:n}=this.node.presenceContext||{};n&&n(this.id),t&&(this.unmount=t(this.id))}unmount(){}}const Mx={animation:{Feature:Ex},exit:{Feature:Dx}};function Zr(e,t,n,r={passive:!0}){return e.addEventListener(t,n,r),()=>e.removeEventListener(t,n)}function oi(e){return{point:{x:e.pageX,y:e.pageY}}}const Lx=e=>t=>fu(t)&&e(t,oi(t));function Er(e,t,n,r){return Zr(e,t,Lx(n),r)}const Cm=1e-4,Ax=1-Cm,Vx=1+Cm,Pm=.01,Rx=0-Pm,Ix=0+Pm;function xe(e){return e.max-e.min}function _x(e,t,n){return Math.abs(e-t)<=n}function fd(e,t,n,r=.5){e.origin=r,e.originPoint=G(t.min,t.max,e.origin),e.scale=xe(n)/xe(t),e.translate=G(n.min,n.max,e.origin)-e.originPoint,(e.scale>=Ax&&e.scale<=Vx||isNaN(e.scale))&&(e.scale=1),(e.translate>=Rx&&e.translate<=Ix||isNaN(e.translate))&&(e.translate=0)}function Nr(e,t,n,r){fd(e.x,t.x,n.x,r?r.originX:void 0),fd(e.y,t.y,n.y,r?r.originY:void 0)}function hd(e,t,n){e.min=n.min+t.min,e.max=e.min+xe(t)}function Ox(e,t,n){hd(e.x,t.x,n.x),hd(e.y,t.y,n.y)}function pd(e,t,n){e.min=t.min-n.min,e.max=e.min+xe(t)}function Ss(e,t,n){pd(e.x,t.x,n.x),pd(e.y,t.y,n.y)}function _e(e){return[e("x"),e("y")]}const jm=({current:e})=>e?e.ownerDocument.defaultView:null,md=(e,t)=>Math.abs(e-t);function Fx(e,t){const n=md(e.x,t.x),r=md(e.y,t.y);return Math.sqrt(n**2+r**2)}class Em{constructor(t,n,{transformPagePoint:r,contextWindow:i=window,dragSnapToOrigin:s=!1,distanceThreshold:o=3}={}){if(this.startEvent=null,this.lastMoveEvent=null,this.lastMoveEventInfo=null,this.handlers={},this.contextWindow=window,this.updatePoint=()=>{if(!(this.lastMoveEvent&&this.lastMoveEventInfo))return;const f=Eo(this.lastMoveEventInfo,this.history),y=this.startEvent!==null,v=Fx(f.offset,{x:0,y:0})>=this.distanceThreshold;if(!y&&!v)return;const{point:x}=f,{timestamp:S}=ce;this.history.push({...x,timestamp:S});const{onStart:m,onMove:p}=this.handlers;y||(m&&m(this.lastMoveEvent,f),this.startEvent=this.lastMoveEvent),p&&p(this.lastMoveEvent,f)},this.handlePointerMove=(f,y)=>{this.lastMoveEvent=f,this.lastMoveEventInfo=jo(y,this.transformPagePoint),$.update(this.updatePoint,!0)},this.handlePointerUp=(f,y)=>{this.end();const{onEnd:v,onSessionEnd:x,resumeAnimation:S}=this.handlers;if(this.dragSnapToOrigin&&S&&S(),!(this.lastMoveEvent&&this.lastMoveEventInfo))return;const m=Eo(f.type==="pointercancel"?this.lastMoveEventInfo:jo(y,this.transformPagePoint),this.history);this.startEvent&&v&&v(f,m),x&&x(f,m)},!fu(t))return;this.dragSnapToOrigin=s,this.handlers=n,this.transformPagePoint=r,this.distanceThreshold=o,this.contextWindow=i||window;const a=oi(t),l=jo(a,this.transformPagePoint),{point:u}=l,{timestamp:c}=ce;this.history=[{...u,timestamp:c}];const{onSessionStart:d}=n;d&&d(t,Eo(l,this.history)),this.removeListeners=ri(Er(this.contextWindow,"pointermove",this.handlePointerMove),Er(this.contextWindow,"pointerup",this.handlePointerUp),Er(this.contextWindow,"pointercancel",this.handlePointerUp))}updateHandlers(t){this.handlers=t}end(){this.removeListeners&&this.removeListeners(),Bt(this.updatePoint)}}function jo(e,t){return t?{point:t(e.point)}:e}function gd(e,t){return{x:e.x-t.x,y:e.y-t.y}}function Eo({point:e},t){return{point:e,delta:gd(e,Nm(t)),offset:gd(e,zx(t)),velocity:Bx(t,.1)}}function zx(e){return e[0]}function Nm(e){return e[e.length-1]}function Bx(e,t){if(e.length<2)return{x:0,y:0};let n=e.length-1,r=null;const i=Nm(e);for(;n>=0&&(r=e[n],!(i.timestamp-r.timestamp>at(t)));)n--;if(!r)return{x:0,y:0};const s=Be(i.timestamp-r.timestamp);if(s===0)return{x:0,y:0};const o={x:(i.x-r.x)/s,y:(i.y-r.y)/s};return o.x===1/0&&(o.x=0),o.y===1/0&&(o.y=0),o}function Ux(e,{min:t,max:n},r){return t!==void 0&&en&&(e=r?G(n,e,r.max):Math.min(e,n)),e}function yd(e,t,n){return{min:t!==void 0?e.min+t:void 0,max:n!==void 0?e.max+n-(e.max-e.min):void 0}}function Wx(e,{top:t,left:n,bottom:r,right:i}){return{x:yd(e.x,n,i),y:yd(e.y,t,r)}}function vd(e,t){let n=t.min-e.min,r=t.max-e.max;return t.max-t.minr?n=Kr(t.min,t.max-r,e.min):r>i&&(n=Kr(e.min,e.max-i,t.min)),vt(0,1,n)}function Hx(e,t){const n={};return t.min!==void 0&&(n.min=t.min-e.min),t.max!==void 0&&(n.max=t.max-e.min),n}const Ga=.35;function Kx(e=Ga){return e===!1?e=0:e===!0&&(e=Ga),{x:xd(e,"left","right"),y:xd(e,"top","bottom")}}function xd(e,t,n){return{min:wd(e,t),max:wd(e,n)}}function wd(e,t){return typeof e=="number"?e:e[t]||0}const Gx=new WeakMap;class Qx{constructor(t){this.openDragLock=null,this.isDragging=!1,this.currentDirection=null,this.originPoint={x:0,y:0},this.constraints=!1,this.hasMutatedConstraints=!1,this.elastic=ne(),this.latestPointerEvent=null,this.latestPanInfo=null,this.visualElement=t}start(t,{snapToCursor:n=!1,distanceThreshold:r}={}){const{presenceContext:i}=this.visualElement;if(i&&i.isPresent===!1)return;const s=d=>{const{dragSnapToOrigin:f}=this.getProps();f?this.pauseAnimation():this.stopAnimation(),n&&this.snapToCursor(oi(d).point)},o=(d,f)=>{const{drag:y,dragPropagation:v,onDragStart:x}=this.getProps();if(y&&!v&&(this.openDragLock&&this.openDragLock(),this.openDragLock=ev(y),!this.openDragLock))return;this.latestPointerEvent=d,this.latestPanInfo=f,this.isDragging=!0,this.currentDirection=null,this.resolveConstraints(),this.visualElement.projection&&(this.visualElement.projection.isAnimationBlocked=!0,this.visualElement.projection.target=void 0),_e(m=>{let p=this.getAxisMotionValue(m).get()||0;if(lt.test(p)){const{projection:g}=this.visualElement;if(g&&g.layout){const w=g.layout.layoutBox[m];w&&(p=xe(w)*(parseFloat(p)/100))}}this.originPoint[m]=p}),x&&$.postRender(()=>x(d,f)),Ha(this.visualElement,"transform");const{animationState:S}=this.visualElement;S&&S.setActive("whileDrag",!0)},a=(d,f)=>{this.latestPointerEvent=d,this.latestPanInfo=f;const{dragPropagation:y,dragDirectionLock:v,onDirectionLock:x,onDrag:S}=this.getProps();if(!y&&!this.openDragLock)return;const{offset:m}=f;if(v&&this.currentDirection===null){this.currentDirection=Yx(m),this.currentDirection!==null&&x&&x(this.currentDirection);return}this.updateAxis("x",f.point,m),this.updateAxis("y",f.point,m),this.visualElement.render(),S&&S(d,f)},l=(d,f)=>{this.latestPointerEvent=d,this.latestPanInfo=f,this.stop(d,f),this.latestPointerEvent=null,this.latestPanInfo=null},u=()=>_e(d=>{var f;return this.getAnimationState(d)==="paused"&&((f=this.getAxisMotionValue(d).animation)==null?void 0:f.play())}),{dragSnapToOrigin:c}=this.getProps();this.panSession=new Em(t,{onSessionStart:s,onStart:o,onMove:a,onSessionEnd:l,resumeAnimation:u},{transformPagePoint:this.visualElement.getTransformPagePoint(),dragSnapToOrigin:c,distanceThreshold:r,contextWindow:jm(this.visualElement)})}stop(t,n){const r=t||this.latestPointerEvent,i=n||this.latestPanInfo,s=this.isDragging;if(this.cancel(),!s||!i||!r)return;const{velocity:o}=i;this.startAnimation(o);const{onDragEnd:a}=this.getProps();a&&$.postRender(()=>a(r,i))}cancel(){this.isDragging=!1;const{projection:t,animationState:n}=this.visualElement;t&&(t.isAnimationBlocked=!1),this.panSession&&this.panSession.end(),this.panSession=void 0;const{dragPropagation:r}=this.getProps();!r&&this.openDragLock&&(this.openDragLock(),this.openDragLock=null),n&&n.setActive("whileDrag",!1)}updateAxis(t,n,r){const{drag:i}=this.getProps();if(!r||!Mi(t,i,this.currentDirection))return;const s=this.getAxisMotionValue(t);let o=this.originPoint[t]+r[t];this.constraints&&this.constraints[t]&&(o=Ux(o,this.constraints[t],this.elastic[t])),s.set(o)}resolveConstraints(){var s;const{dragConstraints:t,dragElastic:n}=this.getProps(),r=this.visualElement.projection&&!this.visualElement.projection.layout?this.visualElement.projection.measure(!1):(s=this.visualElement.projection)==null?void 0:s.layout,i=this.constraints;t&&Mn(t)?this.constraints||(this.constraints=this.resolveRefConstraints()):t&&r?this.constraints=Wx(r.layoutBox,t):this.constraints=!1,this.elastic=Kx(n),i!==this.constraints&&r&&this.constraints&&!this.hasMutatedConstraints&&_e(o=>{this.constraints!==!1&&this.getAxisMotionValue(o)&&(this.constraints[o]=Hx(r.layoutBox[o],this.constraints[o]))})}resolveRefConstraints(){const{dragConstraints:t,onMeasureDragConstraints:n}=this.getProps();if(!t||!Mn(t))return!1;const r=t.current,{projection:i}=this.visualElement;if(!i||!i.layout)return!1;const s=Xv(r,i.root,this.visualElement.getTransformPagePoint());let o=bx(i.layout.layoutBox,s);if(n){const a=n(Gv(o));this.hasMutatedConstraints=!!a,a&&(o=dm(a))}return o}startAnimation(t){const{drag:n,dragMomentum:r,dragElastic:i,dragTransition:s,dragSnapToOrigin:o,onDragTransitionEnd:a}=this.getProps(),l=this.constraints||{},u=_e(c=>{if(!Mi(c,n,this.currentDirection))return;let d=l&&l[c]||{};o&&(d={min:0,max:0});const f=i?200:1e6,y=i?40:1e7,v={type:"inertia",velocity:r?t[c]:0,bounceStiffness:f,bounceDamping:y,timeConstant:750,restDelta:1,restSpeed:10,...s,...d};return this.startAxisValueAnimation(c,v)});return Promise.all(u).then(a)}startAxisValueAnimation(t,n){const r=this.getAxisMotionValue(t);return Ha(this.visualElement,t),r.start(Su(t,r,0,n,this.visualElement,!1))}stopAnimation(){_e(t=>this.getAxisMotionValue(t).stop())}pauseAnimation(){_e(t=>{var n;return(n=this.getAxisMotionValue(t).animation)==null?void 0:n.pause()})}getAnimationState(t){var n;return(n=this.getAxisMotionValue(t).animation)==null?void 0:n.state}getAxisMotionValue(t){const n=`_drag${t.toUpperCase()}`,r=this.visualElement.getProps(),i=r[n];return i||this.visualElement.getValue(t,(r.initial?r.initial[t]:void 0)||0)}snapToCursor(t){_e(n=>{const{drag:r}=this.getProps();if(!Mi(n,r,this.currentDirection))return;const{projection:i}=this.visualElement,s=this.getAxisMotionValue(n);if(i&&i.layout){const{min:o,max:a}=i.layout.layoutBox[n];s.set(t[n]-G(o,a,.5))}})}scalePositionWithinConstraints(){if(!this.visualElement.current)return;const{drag:t,dragConstraints:n}=this.getProps(),{projection:r}=this.visualElement;if(!Mn(n)||!r||!this.constraints)return;this.stopAnimation();const i={x:0,y:0};_e(o=>{const a=this.getAxisMotionValue(o);if(a&&this.constraints!==!1){const l=a.get();i[o]=$x({min:l,max:l},this.constraints[o])}});const{transformTemplate:s}=this.visualElement.getProps();this.visualElement.current.style.transform=s?s({},""):"none",r.root&&r.root.updateScroll(),r.updateLayout(),this.resolveConstraints(),_e(o=>{if(!Mi(o,t,null))return;const a=this.getAxisMotionValue(o),{min:l,max:u}=this.constraints[o];a.set(G(l,u,i[o]))})}addListeners(){if(!this.visualElement.current)return;Gx.set(this.visualElement,this);const t=this.visualElement.current,n=Er(t,"pointerdown",l=>{const{drag:u,dragListener:c=!0}=this.getProps();u&&c&&this.start(l)}),r=()=>{const{dragConstraints:l}=this.getProps();Mn(l)&&l.current&&(this.constraints=this.resolveRefConstraints())},{projection:i}=this.visualElement,s=i.addEventListener("measure",r);i&&!i.layout&&(i.root&&i.root.updateScroll(),i.updateLayout()),$.read(r);const o=Zr(window,"resize",()=>this.scalePositionWithinConstraints()),a=i.addEventListener("didUpdate",({delta:l,hasLayoutChanged:u})=>{this.isDragging&&u&&(_e(c=>{const d=this.getAxisMotionValue(c);d&&(this.originPoint[c]+=l[c].translate,d.set(d.get()+l[c].translate))}),this.visualElement.render())});return()=>{o(),n(),s(),a&&a()}}getProps(){const t=this.visualElement.getProps(),{drag:n=!1,dragDirectionLock:r=!1,dragPropagation:i=!1,dragConstraints:s=!1,dragElastic:o=Ga,dragMomentum:a=!0}=t;return{...t,drag:n,dragDirectionLock:r,dragPropagation:i,dragConstraints:s,dragElastic:o,dragMomentum:a}}}function Mi(e,t,n){return(t===!0||t===e)&&(n===null||n===e)}function Yx(e,t=10){let n=null;return Math.abs(e.y)>t?n="y":Math.abs(e.x)>t&&(n="x"),n}class Xx extends Ht{constructor(t){super(t),this.removeGroupControls=We,this.removeListeners=We,this.controls=new Qx(t)}mount(){const{dragControls:t}=this.node.getProps();t&&(this.removeGroupControls=t.subscribe(this.controls)),this.removeListeners=this.controls.addListeners()||We}unmount(){this.removeGroupControls(),this.removeListeners()}}const kd=e=>(t,n)=>{e&&$.postRender(()=>e(t,n))};class Zx extends Ht{constructor(){super(...arguments),this.removePointerDownListener=We}onPointerDown(t){this.session=new Em(t,this.createPanHandlers(),{transformPagePoint:this.node.getTransformPagePoint(),contextWindow:jm(this.node)})}createPanHandlers(){const{onPanSessionStart:t,onPanStart:n,onPan:r,onPanEnd:i}=this.node.getProps();return{onSessionStart:kd(t),onStart:kd(n),onMove:r,onEnd:(s,o)=>{delete this.session,i&&$.postRender(()=>i(s,o))}}}mount(){this.removePointerDownListener=Er(this.node.current,"pointerdown",t=>this.onPointerDown(t))}update(){this.session&&this.session.updateHandlers(this.createPanHandlers())}unmount(){this.removePointerDownListener(),this.session&&this.session.end()}}const Ki={hasAnimatedSinceResize:!0,hasEverUpdated:!1};function Sd(e,t){return t.max===t.min?0:e/(t.max-t.min)*100}const cr={correct:(e,t)=>{if(!t.target)return e;if(typeof e=="string")if(A.test(e))e=parseFloat(e);else return e;const n=Sd(e,t.target.x),r=Sd(e,t.target.y);return`${n}% ${r}%`}},qx={correct:(e,{treeScale:t,projectionDelta:n})=>{const r=e,i=Ut.parse(e);if(i.length>5)return r;const s=Ut.createTransformer(e),o=typeof i[0]!="number"?1:0,a=n.x.scale*t.x,l=n.y.scale*t.y;i[0+o]/=a,i[1+o]/=l;const u=G(a,l,.5);return typeof i[2+o]=="number"&&(i[2+o]/=u),typeof i[3+o]=="number"&&(i[3+o]/=u),s(i)}};let No=!1;class Jx extends N.Component{componentDidMount(){const{visualElement:t,layoutGroup:n,switchLayoutGroup:r,layoutId:i}=this.props,{projection:s}=t;kv(e2),s&&(n.group&&n.group.add(s),r&&r.register&&i&&r.register(s),No&&s.root.didUpdate(),s.addEventListener("animationComplete",()=>{this.safeToRemove()}),s.setOptions({...s.options,onExitComplete:()=>this.safeToRemove()})),Ki.hasEverUpdated=!0}getSnapshotBeforeUpdate(t){const{layoutDependency:n,visualElement:r,drag:i,isPresent:s}=this.props,{projection:o}=r;return o&&(o.isPresent=s,No=!0,i||t.layoutDependency!==n||n===void 0||t.isPresent!==s?o.willUpdate():this.safeToRemove(),t.isPresent!==s&&(s?o.promote():o.relegate()||$.postRender(()=>{const a=o.getStack();(!a||!a.members.length)&&this.safeToRemove()}))),null}componentDidUpdate(){const{projection:t}=this.props.visualElement;t&&(t.root.didUpdate(),du.postRender(()=>{!t.currentAnimation&&t.isLead()&&this.safeToRemove()}))}componentWillUnmount(){const{visualElement:t,layoutGroup:n,switchLayoutGroup:r}=this.props,{projection:i}=t;No=!0,i&&(i.scheduleCheckAfterUnmount(),n&&n.group&&n.group.remove(i),r&&r.deregister&&r.deregister(i))}safeToRemove(){const{safeToRemove:t}=this.props;t&&t()}render(){return null}}function Dm(e){const[t,n]=Zp(),r=N.useContext(bl);return h.jsx(Jx,{...e,layoutGroup:r,switchLayoutGroup:N.useContext(um),isPresent:t,safeToRemove:n})}const e2={borderRadius:{...cr,applyTo:["borderTopLeftRadius","borderTopRightRadius","borderBottomLeftRadius","borderBottomRightRadius"]},borderTopLeftRadius:cr,borderTopRightRadius:cr,borderBottomLeftRadius:cr,borderBottomRightRadius:cr,boxShadow:qx};function t2(e,t,n){const r=ge(e)?e:Qn(e);return r.start(Su("",r,t,n)),r.animation}const n2=(e,t)=>e.depth-t.depth;class r2{constructor(){this.children=[],this.isDirty=!1}add(t){Kl(this.children,t),this.isDirty=!0}remove(t){Gl(this.children,t),this.isDirty=!0}forEach(t){this.isDirty&&this.children.sort(n2),this.isDirty=!1,this.children.forEach(t)}}function i2(e,t){const n=Pe.now(),r=({timestamp:i})=>{const s=i-n;s>=t&&(Bt(r),e(s-t))};return $.setup(r,!0),()=>Bt(r)}const Mm=["TopLeft","TopRight","BottomLeft","BottomRight"],s2=Mm.length,Td=e=>typeof e=="string"?parseFloat(e):e,Cd=e=>typeof e=="number"||A.test(e);function o2(e,t,n,r,i,s){i?(e.opacity=G(0,n.opacity??1,a2(r)),e.opacityExit=G(t.opacity??1,0,l2(r))):s&&(e.opacity=G(t.opacity??1,n.opacity??1,r));for(let o=0;ort?1:n(Kr(e,t,r))}function jd(e,t){e.min=t.min,e.max=t.max}function Qe(e,t){jd(e.x,t.x),jd(e.y,t.y)}function Ed(e,t){e.translate=t.translate,e.scale=t.scale,e.originPoint=t.originPoint,e.origin=t.origin}function Nd(e,t,n,r,i){return e-=t,e=ks(e,1/n,r),i!==void 0&&(e=ks(e,1/i,r)),e}function u2(e,t=0,n=1,r=.5,i,s=e,o=e){if(lt.test(t)&&(t=parseFloat(t),t=G(o.min,o.max,t/100)-o.min),typeof t!="number")return;let a=G(s.min,s.max,r);e===s&&(a-=t),e.min=Nd(e.min,t,n,a,i),e.max=Nd(e.max,t,n,a,i)}function Dd(e,t,[n,r,i],s,o){u2(e,t[n],t[r],t[i],t.scale,s,o)}const c2=["x","scaleX","originX"],d2=["y","scaleY","originY"];function Md(e,t,n,r){Dd(e.x,t,c2,n?n.x:void 0,r?r.x:void 0),Dd(e.y,t,d2,n?n.y:void 0,r?r.y:void 0)}function Ld(e){return e.translate===0&&e.scale===1}function Am(e){return Ld(e.x)&&Ld(e.y)}function Ad(e,t){return e.min===t.min&&e.max===t.max}function f2(e,t){return Ad(e.x,t.x)&&Ad(e.y,t.y)}function Vd(e,t){return Math.round(e.min)===Math.round(t.min)&&Math.round(e.max)===Math.round(t.max)}function Vm(e,t){return Vd(e.x,t.x)&&Vd(e.y,t.y)}function Rd(e){return xe(e.x)/xe(e.y)}function Id(e,t){return e.translate===t.translate&&e.scale===t.scale&&e.originPoint===t.originPoint}class h2{constructor(){this.members=[]}add(t){Kl(this.members,t),t.scheduleRender()}remove(t){if(Gl(this.members,t),t===this.prevLead&&(this.prevLead=void 0),t===this.lead){const n=this.members[this.members.length-1];n&&this.promote(n)}}relegate(t){const n=this.members.findIndex(i=>t===i);if(n===0)return!1;let r;for(let i=n;i>=0;i--){const s=this.members[i];if(s.isPresent!==!1){r=s;break}}return r?(this.promote(r),!0):!1}promote(t,n){const r=this.lead;if(t!==r&&(this.prevLead=r,this.lead=t,t.show(),r)){r.instance&&r.scheduleRender(),t.scheduleRender(),t.resumeFrom=r,n&&(t.resumeFrom.preserveOpacity=!0),r.snapshot&&(t.snapshot=r.snapshot,t.snapshot.latestValues=r.animationValues||r.latestValues),t.root&&t.root.isUpdating&&(t.isLayoutDirty=!0);const{crossfade:i}=t.options;i===!1&&r.hide()}}exitAnimationComplete(){this.members.forEach(t=>{const{options:n,resumingFrom:r}=t;n.onExitComplete&&n.onExitComplete(),r&&r.options.onExitComplete&&r.options.onExitComplete()})}scheduleRender(){this.members.forEach(t=>{t.instance&&t.scheduleRender(!1)})}removeLeadSnapshot(){this.lead&&this.lead.snapshot&&(this.lead.snapshot=void 0)}}function p2(e,t,n){let r="";const i=e.x.translate/t.x,s=e.y.translate/t.y,o=(n==null?void 0:n.z)||0;if((i||s||o)&&(r=`translate3d(${i}px, ${s}px, ${o}px) `),(t.x!==1||t.y!==1)&&(r+=`scale(${1/t.x}, ${1/t.y}) `),n){const{transformPerspective:u,rotate:c,rotateX:d,rotateY:f,skewX:y,skewY:v}=n;u&&(r=`perspective(${u}px) ${r}`),c&&(r+=`rotate(${c}deg) `),d&&(r+=`rotateX(${d}deg) `),f&&(r+=`rotateY(${f}deg) `),y&&(r+=`skewX(${y}deg) `),v&&(r+=`skewY(${v}deg) `)}const a=e.x.scale*t.x,l=e.y.scale*t.y;return(a!==1||l!==1)&&(r+=`scale(${a}, ${l})`),r||"none"}const Do=["","X","Y","Z"],m2=1e3;let g2=0;function Mo(e,t,n,r){const{latestValues:i}=t;i[e]&&(n[e]=i[e],t.setStaticValue(e,0),r&&(r[e]=0))}function Rm(e){if(e.hasCheckedOptimisedAppear=!0,e.root===e)return;const{visualElement:t}=e.options;if(!t)return;const n=xm(t);if(window.MotionHasOptimisedAnimation(n,"transform")){const{layout:i,layoutId:s}=e.options;window.MotionCancelOptimisedAnimation(n,"transform",$,!(i||s))}const{parent:r}=e;r&&!r.hasCheckedOptimisedAppear&&Rm(r)}function Im({attachResizeListener:e,defaultParent:t,measureScroll:n,checkIsScrollRoot:r,resetTransform:i}){return class{constructor(o={},a=t==null?void 0:t()){this.id=g2++,this.animationId=0,this.animationCommitId=0,this.children=new Set,this.options={},this.isTreeAnimating=!1,this.isAnimationBlocked=!1,this.isLayoutDirty=!1,this.isProjectionDirty=!1,this.isSharedProjectionDirty=!1,this.isTransformDirty=!1,this.updateManuallyBlocked=!1,this.updateBlockedByResize=!1,this.isUpdating=!1,this.isSVG=!1,this.needsReset=!1,this.shouldResetTransform=!1,this.hasCheckedOptimisedAppear=!1,this.treeScale={x:1,y:1},this.eventHandlers=new Map,this.hasTreeAnimated=!1,this.layoutVersion=0,this.updateScheduled=!1,this.scheduleUpdate=()=>this.update(),this.projectionUpdateScheduled=!1,this.checkUpdateFailed=()=>{this.isUpdating&&(this.isUpdating=!1,this.clearAllSnapshots())},this.updateProjection=()=>{this.projectionUpdateScheduled=!1,this.nodes.forEach(x2),this.nodes.forEach(T2),this.nodes.forEach(C2),this.nodes.forEach(w2)},this.resolvedRelativeTargetAt=0,this.linkedParentVersion=0,this.hasProjected=!1,this.isVisible=!0,this.animationProgress=0,this.sharedNodes=new Map,this.latestValues=o,this.root=a?a.root||a:this,this.path=a?[...a.path,a]:[],this.parent=a,this.depth=a?a.depth+1:0;for(let l=0;lthis.root.updateBlockedByResize=!1;$.read(()=>{d=window.innerWidth}),e(o,()=>{const y=window.innerWidth;y!==d&&(d=y,this.root.updateBlockedByResize=!0,c&&c(),c=i2(f,250),Ki.hasAnimatedSinceResize&&(Ki.hasAnimatedSinceResize=!1,this.nodes.forEach(Fd)))})}a&&this.root.registerSharedNode(a,this),this.options.animate!==!1&&u&&(a||l)&&this.addEventListener("didUpdate",({delta:c,hasLayoutChanged:d,hasRelativeLayoutChanged:f,layout:y})=>{if(this.isTreeAnimationBlocked()){this.target=void 0,this.relativeTarget=void 0;return}const v=this.options.transition||u.getDefaultTransition()||D2,{onLayoutAnimationStart:x,onLayoutAnimationComplete:S}=u.getProps(),m=!this.targetLayout||!Vm(this.targetLayout,y),p=!d&&f;if(this.options.layoutRoot||this.resumeFrom||p||d&&(m||!this.currentAnimation)){this.resumeFrom&&(this.resumingFrom=this.resumeFrom,this.resumingFrom.resumingFrom=void 0);const g={...uu(v,"layout"),onPlay:x,onComplete:S};(u.shouldReduceMotion||this.options.layoutRoot)&&(g.delay=0,g.type=!1),this.startAnimation(g),this.setAnimationOrigin(c,p)}else d||Fd(this),this.isLead()&&this.options.onExitComplete&&this.options.onExitComplete();this.targetLayout=y})}unmount(){this.options.layoutId&&this.willUpdate(),this.root.nodes.remove(this);const o=this.getStack();o&&o.remove(this),this.parent&&this.parent.children.delete(this),this.instance=void 0,this.eventHandlers.clear(),Bt(this.updateProjection)}blockUpdate(){this.updateManuallyBlocked=!0}unblockUpdate(){this.updateManuallyBlocked=!1}isUpdateBlocked(){return this.updateManuallyBlocked||this.updateBlockedByResize}isTreeAnimationBlocked(){return this.isAnimationBlocked||this.parent&&this.parent.isTreeAnimationBlocked()||!1}startUpdate(){this.isUpdateBlocked()||(this.isUpdating=!0,this.nodes&&this.nodes.forEach(P2),this.animationId++)}getTransformTemplate(){const{visualElement:o}=this.options;return o&&o.getProps().transformTemplate}willUpdate(o=!0){if(this.root.hasTreeAnimated=!0,this.root.isUpdateBlocked()){this.options.onExitComplete&&this.options.onExitComplete();return}if(window.MotionCancelOptimisedAnimation&&!this.hasCheckedOptimisedAppear&&Rm(this),!this.root.isUpdating&&this.root.startUpdate(),this.isLayoutDirty)return;this.isLayoutDirty=!0;for(let c=0;c{this.isLayoutDirty?this.root.didUpdate():this.root.checkUpdateFailed()})}updateSnapshot(){this.snapshot||!this.instance||(this.snapshot=this.measure(),this.snapshot&&!xe(this.snapshot.measuredBox.x)&&!xe(this.snapshot.measuredBox.y)&&(this.snapshot=void 0))}updateLayout(){if(!this.instance||(this.updateScroll(),!(this.options.alwaysMeasureLayout&&this.isLead())&&!this.isLayoutDirty))return;if(this.resumeFrom&&!this.resumeFrom.instance)for(let l=0;l{const k=w/1e3;zd(d.x,o.x,k),zd(d.y,o.y,k),this.setTargetDelta(d),this.relativeTarget&&this.relativeTargetOrigin&&this.layout&&this.relativeParent&&this.relativeParent.layout&&(Ss(f,this.layout.layoutBox,this.relativeParent.layout.layoutBox),E2(this.relativeTarget,this.relativeTargetOrigin,f,k),g&&f2(this.relativeTarget,g)&&(this.isProjectionDirty=!1),g||(g=ne()),Qe(g,this.relativeTarget)),x&&(this.animationValues=c,o2(c,u,this.latestValues,k,p,m)),this.root.scheduleUpdateProjection(),this.scheduleRender(),this.animationProgress=k},this.mixTargetDelta(this.options.layoutRoot?1e3:0)}startAnimation(o){var a,l,u;this.notifyListeners("animationStart"),(a=this.currentAnimation)==null||a.stop(),(u=(l=this.resumingFrom)==null?void 0:l.currentAnimation)==null||u.stop(),this.pendingAnimation&&(Bt(this.pendingAnimation),this.pendingAnimation=void 0),this.pendingAnimation=$.update(()=>{Ki.hasAnimatedSinceResize=!0,this.motionValue||(this.motionValue=Qn(0)),this.currentAnimation=t2(this.motionValue,[0,1e3],{...o,velocity:0,isSync:!0,onUpdate:c=>{this.mixTargetDelta(c),o.onUpdate&&o.onUpdate(c)},onStop:()=>{},onComplete:()=>{o.onComplete&&o.onComplete(),this.completeAnimation()}}),this.resumingFrom&&(this.resumingFrom.currentAnimation=this.currentAnimation),this.pendingAnimation=void 0})}completeAnimation(){this.resumingFrom&&(this.resumingFrom.currentAnimation=void 0,this.resumingFrom.preserveOpacity=void 0);const o=this.getStack();o&&o.exitAnimationComplete(),this.resumingFrom=this.currentAnimation=this.animationValues=void 0,this.notifyListeners("animationComplete")}finishAnimation(){this.currentAnimation&&(this.mixTargetDelta&&this.mixTargetDelta(m2),this.currentAnimation.stop()),this.completeAnimation()}applyTransformsToTarget(){const o=this.getLead();let{targetWithTransforms:a,target:l,layout:u,latestValues:c}=o;if(!(!a||!l||!u)){if(this!==o&&this.layout&&u&&_m(this.options.animationType,this.layout.layoutBox,u.layoutBox)){l=this.target||ne();const d=xe(this.layout.layoutBox.x);l.x.min=o.target.x.min,l.x.max=l.x.min+d;const f=xe(this.layout.layoutBox.y);l.y.min=o.target.y.min,l.y.max=l.y.min+f}Qe(a,l),An(a,c),Nr(this.projectionDeltaWithTransform,this.layoutCorrected,a,c)}}registerSharedNode(o,a){this.sharedNodes.has(o)||this.sharedNodes.set(o,new h2),this.sharedNodes.get(o).add(a);const u=a.options.initialPromotionConfig;a.promote({transition:u?u.transition:void 0,preserveFollowOpacity:u&&u.shouldPreserveFollowOpacity?u.shouldPreserveFollowOpacity(a):void 0})}isLead(){const o=this.getStack();return o?o.lead===this:!0}getLead(){var a;const{layoutId:o}=this.options;return o?((a=this.getStack())==null?void 0:a.lead)||this:this}getPrevLead(){var a;const{layoutId:o}=this.options;return o?(a=this.getStack())==null?void 0:a.prevLead:void 0}getStack(){const{layoutId:o}=this.options;if(o)return this.root.sharedNodes.get(o)}promote({needsReset:o,transition:a,preserveFollowOpacity:l}={}){const u=this.getStack();u&&u.promote(this,l),o&&(this.projectionDelta=void 0,this.needsReset=!0),a&&this.setOptions({transition:a})}relegate(){const o=this.getStack();return o?o.relegate(this):!1}resetSkewAndRotation(){const{visualElement:o}=this.options;if(!o)return;let a=!1;const{latestValues:l}=o;if((l.z||l.rotate||l.rotateX||l.rotateY||l.rotateZ||l.skewX||l.skewY)&&(a=!0),!a)return;const u={};l.z&&Mo("z",o,u,this.animationValues);for(let c=0;c{var a;return(a=o.currentAnimation)==null?void 0:a.stop()}),this.root.nodes.forEach(_d),this.root.sharedNodes.clear()}}}function y2(e){e.updateLayout()}function v2(e){var n;const t=((n=e.resumeFrom)==null?void 0:n.snapshot)||e.snapshot;if(e.isLead()&&e.layout&&t&&e.hasListeners("didUpdate")){const{layoutBox:r,measuredBox:i}=e.layout,{animationType:s}=e.options,o=t.source!==e.layout.source;s==="size"?_e(d=>{const f=o?t.measuredBox[d]:t.layoutBox[d],y=xe(f);f.min=r[d].min,f.max=f.min+y}):_m(s,t.layoutBox,r)&&_e(d=>{const f=o?t.measuredBox[d]:t.layoutBox[d],y=xe(r[d]);f.max=f.min+y,e.relativeTarget&&!e.currentAnimation&&(e.isProjectionDirty=!0,e.relativeTarget[d].max=e.relativeTarget[d].min+y)});const a=Vn();Nr(a,r,t.layoutBox);const l=Vn();o?Nr(l,e.applyTransform(i,!0),t.measuredBox):Nr(l,r,t.layoutBox);const u=!Am(a);let c=!1;if(!e.resumeFrom){const d=e.getClosestProjectingParent();if(d&&!d.resumeFrom){const{snapshot:f,layout:y}=d;if(f&&y){const v=ne();Ss(v,t.layoutBox,f.layoutBox);const x=ne();Ss(x,r,y.layoutBox),Vm(v,x)||(c=!0),d.options.layoutRoot&&(e.relativeTarget=x,e.relativeTargetOrigin=v,e.relativeParent=d)}}}e.notifyListeners("didUpdate",{layout:r,snapshot:t,delta:l,layoutDelta:a,hasLayoutChanged:u,hasRelativeLayoutChanged:c})}else if(e.isLead()){const{onExitComplete:r}=e.options;r&&r()}e.options.transition=void 0}function x2(e){e.parent&&(e.isProjecting()||(e.isProjectionDirty=e.parent.isProjectionDirty),e.isSharedProjectionDirty||(e.isSharedProjectionDirty=!!(e.isProjectionDirty||e.parent.isProjectionDirty||e.parent.isSharedProjectionDirty)),e.isTransformDirty||(e.isTransformDirty=e.parent.isTransformDirty))}function w2(e){e.isProjectionDirty=e.isSharedProjectionDirty=e.isTransformDirty=!1}function k2(e){e.clearSnapshot()}function _d(e){e.clearMeasurements()}function Od(e){e.isLayoutDirty=!1}function S2(e){const{visualElement:t}=e.options;t&&t.getProps().onBeforeLayoutMeasure&&t.notify("BeforeLayoutMeasure"),e.resetTransform()}function Fd(e){e.finishAnimation(),e.targetDelta=e.relativeTarget=e.target=void 0,e.isProjectionDirty=!0}function T2(e){e.resolveTargetDelta()}function C2(e){e.calcProjection()}function P2(e){e.resetSkewAndRotation()}function j2(e){e.removeLeadSnapshot()}function zd(e,t,n){e.translate=G(t.translate,0,n),e.scale=G(t.scale,1,n),e.origin=t.origin,e.originPoint=t.originPoint}function Bd(e,t,n,r){e.min=G(t.min,n.min,r),e.max=G(t.max,n.max,r)}function E2(e,t,n,r){Bd(e.x,t.x,n.x,r),Bd(e.y,t.y,n.y,r)}function N2(e){return e.animationValues&&e.animationValues.opacityExit!==void 0}const D2={duration:.45,ease:[.4,0,.1,1]},Ud=e=>typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().includes(e),Wd=Ud("applewebkit/")&&!Ud("chrome/")?Math.round:We;function bd(e){e.min=Wd(e.min),e.max=Wd(e.max)}function M2(e){bd(e.x),bd(e.y)}function _m(e,t,n){return e==="position"||e==="preserve-aspect"&&!_x(Rd(t),Rd(n),.2)}function L2(e){var t;return e!==e.root&&((t=e.scroll)==null?void 0:t.wasRoot)}const A2=Im({attachResizeListener:(e,t)=>Zr(e,"resize",t),measureScroll:()=>({x:document.documentElement.scrollLeft||document.body.scrollLeft,y:document.documentElement.scrollTop||document.body.scrollTop}),checkIsScrollRoot:()=>!0}),Lo={current:void 0},Om=Im({measureScroll:e=>({x:e.scrollLeft,y:e.scrollTop}),defaultParent:()=>{if(!Lo.current){const e=new A2({});e.mount(window),e.setOptions({layoutScroll:!0}),Lo.current=e}return Lo.current},resetTransform:(e,t)=>{e.style.transform=t!==void 0?t:"none"},checkIsScrollRoot:e=>window.getComputedStyle(e).position==="fixed"}),V2={pan:{Feature:Zx},drag:{Feature:Xx,ProjectionNode:Om,MeasureLayout:Dm}};function $d(e,t,n){const{props:r}=e;e.animationState&&r.whileHover&&e.animationState.setActive("whileHover",n==="Start");const i="onHover"+n,s=r[i];s&&$.postRender(()=>s(t,oi(t)))}class R2 extends Ht{mount(){const{current:t}=this.node;t&&(this.unmount=tv(t,(n,r)=>($d(this.node,r,"Start"),i=>$d(this.node,i,"End"))))}unmount(){}}class I2 extends Ht{constructor(){super(...arguments),this.isActive=!1}onFocus(){let t=!1;try{t=this.node.current.matches(":focus-visible")}catch{t=!0}!t||!this.node.animationState||(this.node.animationState.setActive("whileFocus",!0),this.isActive=!0)}onBlur(){!this.isActive||!this.node.animationState||(this.node.animationState.setActive("whileFocus",!1),this.isActive=!1)}mount(){this.unmount=ri(Zr(this.node.current,"focus",()=>this.onFocus()),Zr(this.node.current,"blur",()=>this.onBlur()))}unmount(){}}function Hd(e,t,n){const{props:r}=e;if(e.current instanceof HTMLButtonElement&&e.current.disabled)return;e.animationState&&r.whileTap&&e.animationState.setActive("whileTap",n==="Start");const i="onTap"+(n==="End"?"":n),s=r[i];s&&$.postRender(()=>s(t,oi(t)))}class _2 extends Ht{mount(){const{current:t}=this.node;t&&(this.unmount=sv(t,(n,r)=>(Hd(this.node,r,"Start"),(i,{success:s})=>Hd(this.node,i,s?"End":"Cancel")),{useGlobalTarget:this.node.props.globalTapTarget}))}unmount(){}}const Qa=new WeakMap,Ao=new WeakMap,O2=e=>{const t=Qa.get(e.target);t&&t(e)},F2=e=>{e.forEach(O2)};function z2({root:e,...t}){const n=e||document;Ao.has(n)||Ao.set(n,{});const r=Ao.get(n),i=JSON.stringify(t);return r[i]||(r[i]=new IntersectionObserver(F2,{root:e,...t})),r[i]}function B2(e,t,n){const r=z2(t);return Qa.set(e,n),r.observe(e),()=>{Qa.delete(e),r.unobserve(e)}}const U2={some:0,all:1};class W2 extends Ht{constructor(){super(...arguments),this.hasEnteredView=!1,this.isInView=!1}startObserver(){this.unmount();const{viewport:t={}}=this.node.getProps(),{root:n,margin:r,amount:i="some",once:s}=t,o={root:n?n.current:void 0,rootMargin:r,threshold:typeof i=="number"?i:U2[i]},a=l=>{const{isIntersecting:u}=l;if(this.isInView===u||(this.isInView=u,s&&!u&&this.hasEnteredView))return;u&&(this.hasEnteredView=!0),this.node.animationState&&this.node.animationState.setActive("whileInView",u);const{onViewportEnter:c,onViewportLeave:d}=this.node.getProps(),f=u?c:d;f&&f(l)};return B2(this.node.current,o,a)}mount(){this.startObserver()}update(){if(typeof IntersectionObserver>"u")return;const{props:t,prevProps:n}=this.node;["amount","margin","root"].some(b2(t,n))&&this.startObserver()}unmount(){}}function b2({viewport:e={}},{viewport:t={}}={}){return n=>e[n]!==t[n]}const $2={inView:{Feature:W2},tap:{Feature:_2},focus:{Feature:I2},hover:{Feature:R2}},H2={layout:{ProjectionNode:Om,MeasureLayout:Dm}},K2={...Mx,...$2,...V2,...H2},be=Kv(K2,sx),Kt="/api";async function G2(){return(await fetch(`${Kt}/state`)).json()}async function Kd(){return(await fetch(`${Kt}/dependencies/check`)).json()}async function Vo(){return(await fetch(`${Kt}/docker/status`)).json()}async function Q2(){return(await fetch(`${Kt}/docker/build`,{method:"POST"})).json()}async function Y2(){return(await fetch(`${Kt}/docker/start-background`,{method:"POST"})).json()}async function X2(){await fetch(`${Kt}/close`)}async function Z2(){return(await fetch(`${Kt}/defaults`)).json()}async function q2(e){return(await fetch(`${Kt}/defaults`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).json()}const J2="/assets/wails-logo-white-text-B284k7fX.svg",ew="/assets/wails-logo-black-text-Cx-vsZ4W.svg";function tw({className:e="",size:t=240,theme:n="dark"}){const r=n==="dark"?J2:ew;return h.jsx("img",{src:r,alt:"Wails",width:t,className:`object-contain ${e}`,style:{filter:"drop-shadow(0 0 60px rgba(239, 68, 68, 0.4))"}})}const Fm=N.createContext({theme:"dark",toggleTheme:()=>{}}),nw=()=>N.useContext(Fm);function rw(){const{theme:e,toggleTheme:t}=nw();return h.jsx("button",{onClick:t,className:"fixed top-4 left-4 z-50 p-2 rounded-lg bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 transition-colors",title:e==="dark"?"Switch to light mode":"Switch to dark mode",children:e==="dark"?h.jsx("svg",{className:"w-5 h-5 text-yellow-500",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"})}):h.jsx("svg",{className:"w-5 h-5 text-gray-700",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"})})})}const ai={initial:{opacity:0,x:50},animate:{opacity:1,x:0},exit:{opacity:0,x:-50}};function iw({steps:e,currentStep:t}){const n=e.findIndex(r=>r.id===t);return h.jsx("div",{className:"flex items-center justify-center gap-1 text-[11px] text-gray-500 dark:text-gray-400",children:e.map((r,i)=>h.jsxs("div",{className:"flex items-center",children:[h.jsx("span",{className:i<=n?"text-gray-900 dark:text-white font-medium":"text-gray-400 dark:text-gray-500",children:r.label}),iy.required&&!y.installed).length===0,c=e.filter(y=>!y.installed),d=(()=>{const y=c.filter(m=>{var p;return(p=m.installCommand)==null?void 0:p.startsWith("sudo ")}).map(m=>m.installCommand);if(y.length===0)return null;const v=[],x=[],S=[];for(const m of y)if(m.includes("pacman -S")){const p=m.match(/pacman -S\s+(.+)/);p&&v.push(...p[1].split(/\s+/))}else if(m.includes("apt install")){const p=m.match(/apt install\s+(.+)/);p&&x.push(...p[1].split(/\s+/))}else if(m.includes("dnf install")){const p=m.match(/dnf install\s+(.+)/);p&&S.push(...p[1].split(/\s+/))}return v.length>0?`sudo pacman -S ${v.join(" ")}`:x.length>0?`sudo apt install ${x.join(" ")}`:S.length>0?`sudo dnf install ${S.join(" ")}`:null})(),f=()=>{d&&(navigator.clipboard.writeText(d),a(!0),setTimeout(()=>a(!1),2e3))};return h.jsxs(be.div,{variants:ai,initial:"initial",animate:"animate",exit:"exit",transition:{duration:.2},className:"relative",children:[s&&h.jsx("div",{className:"absolute inset-0 bg-white/80 dark:bg-gray-900/80 rounded-lg flex items-center justify-center z-10",children:h.jsxs("div",{className:"flex flex-col items-center gap-4",children:[h.jsx(be.div,{animate:{rotate:360},transition:{duration:1,repeat:1/0,ease:"linear"},className:"w-8 h-8 border-2 border-gray-400 dark:border-gray-600 border-t-red-500 rounded-full"}),h.jsx("span",{className:"text-sm text-gray-600 dark:text-gray-400",children:"Checking dependencies..."})]})}),h.jsxs("div",{className:"mb-4",children:[h.jsx("h2",{className:"text-xl font-bold mb-1 text-gray-900 dark:text-white",children:"System Dependencies"}),h.jsx("p",{className:"text-sm text-gray-600 dark:text-gray-300",children:"The following dependencies are needed to build Wails applications."})]}),h.jsx("div",{className:"mb-4",children:h.jsx("div",{className:"bg-gray-100 dark:bg-gray-900/50 rounded-lg px-4",children:e.map(y=>h.jsx(ow,{dep:y},y.name))})}),d&&h.jsxs("div",{className:"mb-4 p-3 bg-gray-100 dark:bg-gray-900/50 rounded-lg",children:[h.jsx("div",{className:"text-xs text-gray-600 dark:text-gray-300 mb-2",children:"Install all missing dependencies:"}),h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx("code",{className:"flex-1 text-xs bg-gray-200 dark:bg-gray-900 text-gray-700 dark:text-gray-300 px-3 py-2 rounded font-mono overflow-x-auto",children:d}),h.jsx("button",{onClick:f,className:"text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition-colors p-2",title:"Copy command",children:o?h.jsx("svg",{className:"w-5 h-5 text-green-500 dark:text-green-400",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})}):h.jsx("svg",{className:"w-5 h-5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"})})})]})]}),u&&h.jsx("div",{className:"rounded-lg p-3 bg-green-500/10 border border-green-500/20",children:h.jsxs("div",{className:"flex items-center gap-2 text-green-600 dark:text-green-400 text-sm",children:[h.jsx("svg",{className:"w-4 h-4",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"})}),"All required dependencies are installed. You can proceed."]})}),h.jsx(bs,{onBack:n,onNext:t,onCancel:r,nextLabel:"Next",showRetry:!u,onRetry:i})]})}function lw({dockerStatus:e,buildingImage:t,onBuildImage:n,onNext:r,onBack:i,onCancel:s}){return h.jsxs(be.div,{variants:ai,initial:"initial",animate:"animate",exit:"exit",transition:{duration:.2},children:[h.jsxs("div",{className:"mb-6",children:[h.jsx("h2",{className:"text-xl font-bold mb-1 text-gray-900 dark:text-white",children:"Cross-Platform Builds"}),h.jsx("p",{className:"text-sm text-gray-600 dark:text-gray-300",children:"Docker enables building for macOS, Windows, and Linux from any platform."})]}),h.jsx("div",{className:"bg-gray-100 dark:bg-gray-900/50 rounded-lg p-4 mb-6",children:h.jsxs("div",{className:"flex items-start gap-4",children:[h.jsx("div",{className:"w-12 h-12 rounded-xl bg-blue-500/20 flex items-center justify-center flex-shrink-0",children:h.jsx("svg",{className:"w-7 h-7",viewBox:"0 0 756.26 596.9",children:h.jsx("path",{fill:"#1d63ed",d:"M743.96,245.25c-18.54-12.48-67.26-17.81-102.68-8.27-1.91-35.28-20.1-65.01-53.38-90.95l-12.32-8.27-8.21,12.4c-16.14,24.5-22.94,57.14-20.53,86.81,1.9,18.28,8.26,38.83,20.53,53.74-46.1,26.74-88.59,20.67-276.77,20.67H.06c-.85,42.49,5.98,124.23,57.96,190.77,5.74,7.35,12.04,14.46,18.87,21.31,42.26,42.32,106.11,73.35,201.59,73.44,145.66.13,270.46-78.6,346.37-268.97,24.98.41,90.92,4.48,123.19-57.88.79-1.05,8.21-16.54,8.21-16.54l-12.3-8.27ZM189.67,206.39h-81.7v81.7h81.7v-81.7ZM295.22,206.39h-81.7v81.7h81.7v-81.7ZM400.77,206.39h-81.7v81.7h81.7v-81.7ZM506.32,206.39h-81.7v81.7h81.7v-81.7ZM84.12,206.39H2.42v81.7h81.7v-81.7ZM189.67,103.2h-81.7v81.7h81.7v-81.7ZM295.22,103.2h-81.7v81.7h81.7v-81.7ZM400.77,103.2h-81.7v81.7h81.7v-81.7ZM400.77,0h-81.7v81.7h81.7V0Z"})})}),h.jsxs("div",{className:"flex-1",children:[h.jsx("h3",{className:"font-medium text-gray-900 dark:text-white mb-1",children:"Docker Status"}),e?e.installed?e.running?e.imageBuilt?h.jsxs("div",{children:[h.jsxs("div",{className:"flex items-center gap-2 text-green-600 dark:text-green-400 text-sm mb-2",children:[h.jsx("span",{className:"w-2 h-2 rounded-full bg-green-500"}),"Ready for cross-platform builds"]}),h.jsxs("p",{className:"text-xs text-gray-500",children:["Docker ",e.version," • wails-cross image installed"]})]}):t?h.jsxs("div",{children:[h.jsxs("div",{className:"flex items-center gap-2 text-blue-500 dark:text-blue-400 text-sm mb-2",children:[h.jsx(be.span,{className:"w-3 h-3 border-2 border-blue-500 dark:border-blue-400 border-t-transparent rounded-full",animate:{rotate:360},transition:{duration:1,repeat:1/0,ease:"linear"}}),"Building wails-cross image... ",e.pullProgress,"%"]}),h.jsx("div",{className:"h-1.5 bg-gray-300 dark:bg-gray-700 rounded-full overflow-hidden",children:h.jsx(be.div,{className:"h-full bg-blue-500",animate:{width:`${e.pullProgress}%`}})})]}):h.jsxs("div",{children:[h.jsxs("div",{className:"flex items-center gap-2 text-gray-600 dark:text-gray-400 text-sm mb-2",children:[h.jsx("span",{className:"w-2 h-2 rounded-full bg-gray-400 dark:bg-gray-500"}),"Cross-compilation image not installed"]}),h.jsxs("p",{className:"text-xs text-gray-500 mb-3",children:["Docker ",e.version," is running. Build the wails-cross image to enable cross-platform builds."]}),h.jsx("button",{onClick:n,className:"text-sm px-4 py-2 rounded-lg bg-blue-500/20 text-blue-600 dark:text-blue-400 hover:bg-blue-500/30 transition-colors border border-blue-500/30",children:"Build Cross-Compilation Image"})]}):h.jsxs("div",{children:[h.jsxs("div",{className:"flex items-center gap-2 text-yellow-600 dark:text-yellow-400 text-sm mb-2",children:[h.jsx("span",{className:"w-2 h-2 rounded-full bg-yellow-500"}),"Installed but not running"]}),h.jsx("p",{className:"text-xs text-gray-500",children:"Start Docker Desktop to enable cross-platform builds."})]}):h.jsxs("div",{children:[h.jsxs("div",{className:"flex items-center gap-2 text-yellow-600 dark:text-yellow-400 text-sm mb-2",children:[h.jsx("span",{className:"w-2 h-2 rounded-full bg-yellow-500"}),"Not installed"]}),h.jsx("p",{className:"text-xs text-gray-500 mb-2",children:"Docker is optional but required for cross-platform builds."}),h.jsxs("a",{href:"https://docs.docker.com/get-docker/",target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center gap-1 text-xs text-blue-500 dark:text-blue-400 hover:text-blue-600 dark:hover:text-blue-300",children:["Install Docker Desktop",h.jsx("svg",{className:"w-3 h-3",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"})})]})]}):h.jsx("div",{className:"text-sm text-gray-600 dark:text-gray-300",children:"Checking Docker..."})]})]})}),h.jsxs("div",{className:"bg-gray-100 dark:bg-gray-900/50 rounded-lg p-4",children:[h.jsx("h3",{className:"text-sm font-medium text-gray-600 dark:text-gray-400 mb-2",children:"What you can build:"}),h.jsxs("div",{className:"grid grid-cols-3 gap-4 text-center text-sm",children:[h.jsxs("div",{className:"py-2",children:[h.jsx("div",{className:"flex justify-center mb-2",children:h.jsx("svg",{className:"w-8 h-8 text-gray-700 dark:text-gray-300",viewBox:"0 0 24 24",fill:"currentColor",children:h.jsx("path",{d:"M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"})})}),h.jsx("div",{className:"text-gray-700 dark:text-gray-300",children:"macOS"}),h.jsx("div",{className:"text-xs text-gray-500",children:".app / .dmg"})]}),h.jsxs("div",{className:"py-2",children:[h.jsx("div",{className:"flex justify-center mb-2",children:h.jsx("svg",{className:"w-8 h-8 text-gray-700 dark:text-gray-300",viewBox:"0 0 24 24",fill:"currentColor",children:h.jsx("path",{d:"M0 3.449L9.75 2.1v9.451H0m10.949-9.602L24 0v11.4H10.949M0 12.6h9.75v9.451L0 20.699M10.949 12.6H24V24l-12.9-1.801"})})}),h.jsx("div",{className:"text-gray-700 dark:text-gray-300",children:"Windows"}),h.jsx("div",{className:"text-xs text-gray-500",children:".exe / .msi"})]}),h.jsxs("div",{className:"py-2",children:[h.jsx("div",{className:"flex justify-center mb-2",children:h.jsx("svg",{className:"w-8 h-8",viewBox:"0 0 1024 1024",fill:"currentColor",children:h.jsx("path",{className:"text-gray-700 dark:text-gray-300",fillRule:"evenodd",clipRule:"evenodd",d:"M186.828,734.721c8.135,22.783-2.97,48.36-25.182,55.53c-12.773,4.121-27.021,5.532-40.519,5.145c-24.764-0.714-32.668,8.165-24.564,31.376c2.795,8.01,6.687,15.644,10.269,23.363c7.095,15.287,7.571,30.475-0.168,45.697c-2.572,5.057-5.055,10.168-7.402,15.337c-9.756,21.488-5.894,30.47,17.115,36.3c18.451,4.676,37.425,7.289,55.885,11.932c40.455,10.175,80.749,21,121.079,31.676c20.128,5.325,40.175,9.878,61.075,3.774c27.01-7.889,41.849-27.507,36.217-54.78c-4.359-21.112-10.586-43.132-21.634-61.314c-26.929-44.322-56.976-86.766-86.174-129.69c-5.666-8.329-12.819-15.753-19.905-22.987c-23.511-24.004-32.83-26.298-64.022-16.059c-7.589-15.327-5.198-31.395-2.56-47.076c1.384-8.231,4.291-16.796,8.718-23.821c18.812-29.824,29.767-62.909,41.471-95.738c13.545-37.999,30.87-73.47,57.108-105.131c21.607-26.074,38.626-55.982,57.303-84.44c6.678-10.173,6.803-21.535,6.23-33.787c-2.976-63.622-6.561-127.301-6.497-190.957c0.081-78.542,65.777-139.631,156.443-127.536c99.935,13.331,159.606,87.543,156.629,188.746c-2.679,91.191,27.38,170.682,89.727,239.686c62.132,68.767,91.194,153.119,96.435,245.38c0.649,11.46-1.686,23.648-5.362,34.583c-2.265,6.744-9.651,11.792-14.808,17.536c-6.984,7.781-14.497,15.142-20.959,23.328c-12.077,15.294-25.419,28.277-45.424,32.573c-30.163,6.475-50.177-2.901-63.81-30.468c-1.797-3.636-3.358-7.432-5.555-10.812c-5.027-7.741-10.067-18.974-20.434-15.568c-6.727,2.206-14.165,11.872-15.412,19.197c-2.738,16.079-5.699,33.882-1.532,49.047c11.975,43.604,9.224,86.688,3.062,130.371c-3.513,24.898-0.414,49.037,23.13,63.504c24.495,15.044,48.407,7.348,70.818-6.976c3.742-2.394,7.25-5.249,10.536-8.252c30.201-27.583,65.316-46.088,104.185-58.488c14.915-4.759,29.613-11.405,42.97-19.554c19.548-11.932,18.82-25.867-0.854-38.036c-7.187-4.445-14.944-8.5-22.984-10.933c-23.398-7.067-34.812-23.963-39.767-46.375c-3.627-16.398-4.646-32.782,4.812-51.731c1.689,10.577,2.771,17.974,4.062,25.334c5.242,29.945,20.805,52.067,48.321,66.04c8.869,4.5,17.161,10.973,24.191,18.055c10.372,10.447,10.407,22.541,0.899,33.911c-4.886,5.837-10.683,11.312-17.052,15.427c-11.894,7.685-23.962,15.532-36.92,21.056c-45.461,19.375-84.188,48.354-120.741,80.964c-19.707,17.582-44.202,15.855-68.188,13.395c-21.502-2.203-38.363-12.167-48.841-31.787c-6.008-11.251-15.755-18.053-28.35-18.262c-42.991-0.722-85.995-0.785-128.993-0.914c-8.92-0.026-17.842,0.962-26.769,1.1c-25.052,0.391-47.926,7.437-68.499,21.808c-5.987,4.186-12.068,8.24-17.954,12.562c-19.389,14.233-40.63,17.873-63.421,10.497c-25.827-8.353-51.076-18.795-77.286-25.591c-38.792-10.057-78.257-17.493-117.348-26.427c-43.557-9.959-51.638-24.855-33.733-65.298c8.605-19.435,8.812-38.251,3.55-58.078c-2.593-9.773-5.126-19.704-6.164-29.72c-1.788-17.258,4.194-24.958,21.341-27.812c12.367-2.059,25.069-2.132,37.423-4.255C165.996,776.175,182.158,759.821,186.828,734.721z M698.246,454.672c9.032,15.582,18.872,30.76,26.936,46.829c20.251,40.355,34.457,82.42,30.25,128.537c-0.871,9.573-2.975,19.332-6.354,28.313c-5.088,13.528-18.494,19.761-33.921,17.5c-13.708-2.007-15.566-12.743-16.583-23.462c-1.035-10.887-1.435-21.864-1.522-32.809c-0.314-39.017-7.915-76.689-22.456-112.7c-5.214-12.915-14.199-24.3-21.373-36.438c-2.792-4.72-6.521-9.291-7.806-14.435c-8.82-35.31-21.052-68.866-43.649-98.164c-11.154-14.454-14.638-31.432-9.843-49.572c1.656-6.269,3.405-12.527,4.695-18.875c3.127-15.406-1.444-22.62-15.969-28.01c-15.509-5.752-30.424-13.273-46.179-18.138c-12.963-4.001-15.764-12.624-15.217-23.948c0.31-6.432,0.895-13.054,2.767-19.159c3.27-10.672,9.56-18.74,21.976-19.737c12.983-1.044,22.973,4.218,28.695,16.137c5.661,11.8,6.941,23.856,1.772,36.459c-4.638,11.314-0.159,17.13,11.52,13.901c4.966-1.373,11.677-7.397,12.217-11.947c2.661-22.318,1.795-44.577-9.871-64.926c-11.181-19.503-31.449-27.798-52.973-21.69c-26.941,7.646-39.878,28.604-37.216,60.306c0.553,6.585,1.117,13.171,1.539,18.14c-15.463-1.116-29.71-2.144-44.146-3.184c-0.73-8.563-0.741-16.346-2.199-23.846c-1.843-9.481-3.939-19.118-7.605-27.993c-4.694-11.357-12.704-20.153-26.378-20.08c-13.304,0.074-20.082,9.253-25.192,19.894c-11.385,23.712-9.122,47.304,1.739,70.415c1.69,3.598,6.099,8.623,8.82,8.369c3.715-0.347,7.016-5.125,11.028-8.443c-17.322-9.889-25.172-30.912-16.872-46.754c3.016-5.758,10.86-10.391,17.474-12.498c8.076-2.575,15.881,2.05,18.515,10.112c3.214,9.837,4.66,20.323,6.051,30.641c0.337,2.494-1.911,6.161-4.06,8.031c-12.73,11.068-25.827,21.713-38.686,32.635c-2.754,2.339-5.533,4.917-7.455,7.921c-5.453,8.523-6.483,16.016,3.903,22.612c6.351,4.035,11.703,10.012,16.616,15.86c7.582,9.018,17.047,14.244,28.521,13.972c46.214-1.09,91.113-6.879,128.25-38.61c1.953-1.668,7.641-1.83,9.262-0.271c1.896,1.823,2.584,6.983,1.334,9.451c-1.418,2.797-5.315,4.806-8.555,6.139c-22.846,9.401-45.863,18.383-68.699,27.808c-22.67,9.355-45.875,13.199-70.216,8.43c-2.864-0.562-5.932-0.076-10.576-0.076c10.396,14.605,21.893,24.62,38.819,23.571c12.759-0.79,26.125-2.244,37.846-6.879c17.618-6.967,33.947-17.144,51.008-25.588c5.737-2.837,11.903-5.131,18.133-6.474c2.185-0.474,5.975,2.106,7.427,4.334c0.804,1.237-1.1,5.309-2.865,6.903c-2.953,2.667-6.796,4.339-10.227,6.488c-21.264,13.325-42.521,26.658-63.771,40.002c-8.235,5.17-16.098,11.071-24.745,15.408c-16.571,8.316-28.156,6.68-40.559-7.016c-10.026-11.072-18.225-23.792-27.376-35.669c-2.98-3.87-6.41-7.393-9.635-11.074c-1.543,26.454-14.954,46.662-26.272,67.665c-12.261,22.755-21.042,45.964-8.633,69.951c-4.075,4.752-7.722,8.13-10.332,12.18c-29.353,45.525-52.72,93.14-52.266,149.186c0.109,13.75-0.516,27.55-1.751,41.24c-0.342,3.793-3.706,9.89-6.374,10.287c-3.868,0.573-10.627-1.946-12.202-5.111c-6.939-13.938-14.946-28.106-17.81-43.101c-3.031-15.865-0.681-32.759-0.681-50.958c-2.558,5.441-5.907,9.771-6.539,14.466c-1.612,11.975-3.841,24.322-2.489,36.14c2.343,20.486,5.578,41.892,21.418,56.922c21.76,20.642,44.75,40.021,67.689,59.375c20.161,17.01,41.426,32.724,61.388,49.954c22.306,19.257,15.029,51.589-13.006,60.711c-2.144,0.697-4.25,1.513-8.117,2.9c20.918,28.527,40.528,56.508,38.477,93.371c23.886-27.406,2.287-47.712-10.241-69.677c6.972-6.97,12.504-8.75,21.861-1.923c10.471,7.639,23.112,15.599,35.46,16.822c62.957,6.229,123.157,2.18,163.56-57.379c2.57-3.788,8.177-5.519,12.37-8.205c1.981,4.603,5.929,9.354,5.596,13.78c-1.266,16.837-3.306,33.673-6.265,50.292c-1.978,11.097-6.572,21.71-8.924,32.766c-1.849,8.696,1.109,15.219,12.607,15.204c1.387-6.761,2.603-13.474,4.154-20.108c10.602-45.342,16.959-90.622,6.691-137.28c-3.4-15.454-2.151-32.381-0.526-48.377c2.256-22.174,12.785-32.192,33.649-37.142c2.765-0.654,6.489-3.506,7.108-6.002c4.621-18.597,18.218-26.026,35.236-28.913c19.98-3.386,39.191-0.066,59.491,10.485c-2.108-3.7-2.525-5.424-3.612-6.181c-8.573-5.968-17.275-11.753-25.307-17.164C776.523,585.58,758.423,514.082,698.246,454.672z M427.12,221.259c1.83-0.584,3.657-1.169,5.486-1.755c-2.37-7.733-4.515-15.555-7.387-23.097c-0.375-0.983-4.506-0.533-6.002-0.668C422.211,205.409,424.666,213.334,427.12,221.259z M565.116,212.853c5.3-12.117-1.433-21.592-14.086-20.792C555.663,198.899,560.315,205.768,565.116,212.853z"})})}),h.jsx("div",{className:"text-gray-700 dark:text-gray-300",children:"Linux"}),h.jsx("div",{className:"text-xs text-gray-500",children:".deb / .rpm / PKGBUILD"})]})]})]}),h.jsx(bs,{onBack:i,onNext:r,onCancel:s,nextLabel:"Next"})]})}function uw({defaults:e,onDefaultsChange:t,onNext:n,onBack:r,onCancel:i,saving:s}){var o,a,l,u,c,d,f,y,v,x;return h.jsxs(be.div,{variants:ai,initial:"initial",animate:"animate",exit:"exit",transition:{duration:.2},children:[h.jsxs("div",{className:"mb-3",children:[h.jsx("h2",{className:"text-lg font-bold mb-0.5 text-gray-900 dark:text-white",children:"Project Defaults"}),h.jsx("p",{className:"text-xs text-gray-600 dark:text-gray-300",children:"Configure defaults for new Wails projects."})]}),h.jsxs("div",{className:"bg-gray-100 dark:bg-gray-900/50 rounded-lg p-3 mb-3",children:[h.jsx("h3",{className:"text-[11px] font-medium text-gray-500 dark:text-gray-400 mb-2",children:"Author Information"}),h.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[h.jsxs("div",{children:[h.jsx("label",{className:"block text-[10px] text-gray-500 mb-0.5",children:"Your Name"}),h.jsx("input",{type:"text",value:e.author.name,onChange:S=>t({...e,author:{...e.author,name:S.target.value}}),placeholder:"John Doe",className:"w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none"})]}),h.jsxs("div",{children:[h.jsx("label",{className:"block text-[10px] text-gray-500 mb-0.5",children:"Company"}),h.jsx("input",{type:"text",value:e.author.company,onChange:S=>t({...e,author:{...e.author,company:S.target.value}}),placeholder:"My Company",className:"w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none"})]})]})]}),h.jsxs("div",{className:"bg-gray-100 dark:bg-gray-900/50 rounded-lg p-3 mb-3",children:[h.jsx("h3",{className:"text-[11px] font-medium text-gray-500 dark:text-gray-400 mb-2",children:"Project Settings"}),h.jsxs("div",{className:"space-y-2",children:[h.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[h.jsxs("div",{children:[h.jsx("label",{className:"block text-[10px] text-gray-500 mb-0.5",children:"Bundle ID Prefix"}),h.jsx("input",{type:"text",value:e.project.productIdentifierPrefix,onChange:S=>t({...e,project:{...e.project,productIdentifierPrefix:S.target.value}}),placeholder:"com.mycompany",className:"w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none font-mono"})]}),h.jsxs("div",{children:[h.jsx("label",{className:"block text-[10px] text-gray-500 mb-0.5",children:"Default Version"}),h.jsx("input",{type:"text",value:e.project.defaultVersion,onChange:S=>t({...e,project:{...e.project,defaultVersion:S.target.value}}),placeholder:"0.1.0",className:"w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none font-mono"})]})]}),h.jsxs("div",{children:[h.jsx("label",{className:"block text-[10px] text-gray-500 mb-0.5",children:"Default Template"}),h.jsxs("select",{value:e.project.defaultTemplate,onChange:S=>t({...e,project:{...e.project,defaultTemplate:S.target.value}}),className:"w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 focus:border-red-500 focus:outline-none",children:[h.jsx("option",{value:"vanilla",children:"Vanilla (JavaScript)"}),h.jsx("option",{value:"vanilla-ts",children:"Vanilla (TypeScript)"}),h.jsx("option",{value:"react",children:"React"}),h.jsx("option",{value:"react-ts",children:"React (TypeScript)"}),h.jsx("option",{value:"react-swc",children:"React + SWC"}),h.jsx("option",{value:"react-swc-ts",children:"React + SWC (TypeScript)"}),h.jsx("option",{value:"preact",children:"Preact"}),h.jsx("option",{value:"preact-ts",children:"Preact (TypeScript)"}),h.jsx("option",{value:"svelte",children:"Svelte"}),h.jsx("option",{value:"svelte-ts",children:"Svelte (TypeScript)"}),h.jsx("option",{value:"solid",children:"Solid"}),h.jsx("option",{value:"solid-ts",children:"Solid (TypeScript)"}),h.jsx("option",{value:"lit",children:"Lit"}),h.jsx("option",{value:"lit-ts",children:"Lit (TypeScript)"}),h.jsx("option",{value:"vue",children:"Vue"}),h.jsx("option",{value:"vue-ts",children:"Vue (TypeScript)"})]})]})]})]}),h.jsxs("div",{className:"bg-gray-100 dark:bg-gray-900/50 rounded-lg p-3 mb-3",children:[h.jsxs("div",{className:"flex items-center gap-2 mb-1",children:[h.jsx("svg",{className:"w-4 h-4 text-gray-500 dark:text-gray-400",viewBox:"0 0 24 24",fill:"currentColor",children:h.jsx("path",{d:"M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"})}),h.jsx("h3",{className:"text-[11px] font-medium text-gray-500 dark:text-gray-400",children:"macOS Code Signing"}),h.jsx("span",{className:"text-[9px] text-gray-400 dark:text-gray-500",children:"(optional)"})]}),h.jsx("p",{className:"text-[9px] text-gray-400 dark:text-gray-500 mb-2 ml-6",children:"These are public identifiers. App-specific passwords are stored securely in your Keychain."}),h.jsxs("div",{className:"space-y-2",children:[h.jsxs("div",{children:[h.jsx("label",{className:"block text-[10px] text-gray-500 mb-0.5",children:"Developer ID"}),h.jsx("input",{type:"text",value:((a=(o=e.signing)==null?void 0:o.macOS)==null?void 0:a.developerID)||"",onChange:S=>{var m,p,g,w,k,T;return t({...e,signing:{...e.signing,macOS:{...(m=e.signing)==null?void 0:m.macOS,developerID:S.target.value,appleID:((g=(p=e.signing)==null?void 0:p.macOS)==null?void 0:g.appleID)||"",teamID:((k=(w=e.signing)==null?void 0:w.macOS)==null?void 0:k.teamID)||""},windows:((T=e.signing)==null?void 0:T.windows)||{certificatePath:"",timestampServer:""}}})},placeholder:"Developer ID Application: John Doe (TEAMID)",className:"w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none font-mono"})]}),h.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[h.jsxs("div",{children:[h.jsx("label",{className:"block text-[10px] text-gray-500 mb-0.5",children:"Apple ID"}),h.jsx("input",{type:"email",value:((u=(l=e.signing)==null?void 0:l.macOS)==null?void 0:u.appleID)||"",onChange:S=>{var m,p,g,w,k,T;return t({...e,signing:{...e.signing,macOS:{...(m=e.signing)==null?void 0:m.macOS,appleID:S.target.value,developerID:((g=(p=e.signing)==null?void 0:p.macOS)==null?void 0:g.developerID)||"",teamID:((k=(w=e.signing)==null?void 0:w.macOS)==null?void 0:k.teamID)||""},windows:((T=e.signing)==null?void 0:T.windows)||{certificatePath:"",timestampServer:""}}})},placeholder:"you@example.com",className:"w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none"})]}),h.jsxs("div",{children:[h.jsx("label",{className:"block text-[10px] text-gray-500 mb-0.5",children:"Team ID"}),h.jsx("input",{type:"text",value:((d=(c=e.signing)==null?void 0:c.macOS)==null?void 0:d.teamID)||"",onChange:S=>{var m,p,g,w,k,T;return t({...e,signing:{...e.signing,macOS:{...(m=e.signing)==null?void 0:m.macOS,teamID:S.target.value,developerID:((g=(p=e.signing)==null?void 0:p.macOS)==null?void 0:g.developerID)||"",appleID:((k=(w=e.signing)==null?void 0:w.macOS)==null?void 0:k.appleID)||""},windows:((T=e.signing)==null?void 0:T.windows)||{certificatePath:"",timestampServer:""}}})},placeholder:"ABCD1234EF",className:"w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none font-mono"})]})]})]})]}),h.jsxs("div",{className:"bg-gray-100 dark:bg-gray-900/50 rounded-lg p-3 mb-3",children:[h.jsxs("div",{className:"flex items-center gap-2 mb-2",children:[h.jsx("svg",{className:"w-4 h-4 text-gray-500 dark:text-gray-400",viewBox:"0 0 24 24",fill:"currentColor",children:h.jsx("path",{d:"M0 3.449L9.75 2.1v9.451H0m10.949-9.602L24 0v11.4H10.949M0 12.6h9.75v9.451L0 20.699M10.949 12.6H24V24l-12.9-1.801"})}),h.jsx("h3",{className:"text-[11px] font-medium text-gray-500 dark:text-gray-400",children:"Windows Code Signing"}),h.jsx("span",{className:"text-[9px] text-gray-400 dark:text-gray-500",children:"(optional)"})]}),h.jsxs("div",{className:"space-y-2",children:[h.jsxs("div",{children:[h.jsx("label",{className:"block text-[10px] text-gray-500 mb-0.5",children:"Certificate Path (.pfx)"}),h.jsx("input",{type:"text",value:((y=(f=e.signing)==null?void 0:f.windows)==null?void 0:y.certificatePath)||"",onChange:S=>{var m,p,g,w;return t({...e,signing:{...e.signing,macOS:((m=e.signing)==null?void 0:m.macOS)||{developerID:"",appleID:"",teamID:""},windows:{...(p=e.signing)==null?void 0:p.windows,certificatePath:S.target.value,timestampServer:((w=(g=e.signing)==null?void 0:g.windows)==null?void 0:w.timestampServer)||""}}})},placeholder:"/path/to/certificate.pfx",className:"w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none font-mono"})]}),h.jsxs("div",{children:[h.jsx("label",{className:"block text-[10px] text-gray-500 mb-0.5",children:"Timestamp Server"}),h.jsx("input",{type:"text",value:((x=(v=e.signing)==null?void 0:v.windows)==null?void 0:x.timestampServer)||"",onChange:S=>{var m,p,g,w;return t({...e,signing:{...e.signing,macOS:((m=e.signing)==null?void 0:m.macOS)||{developerID:"",appleID:"",teamID:""},windows:{...(p=e.signing)==null?void 0:p.windows,timestampServer:S.target.value,certificatePath:((w=(g=e.signing)==null?void 0:g.windows)==null?void 0:w.certificatePath)||""}}})},placeholder:"http://timestamp.digicert.com",className:"w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none font-mono"})]})]})]}),h.jsxs("div",{className:"text-[10px] text-gray-500 dark:text-gray-600 mb-3",children:[h.jsx("span",{className:"text-gray-400 dark:text-gray-500",children:"Stored in:"})," ~/.config/wails/defaults.yaml"]}),h.jsx(bs,{onBack:r,onNext:n,onCancel:i,nextLabel:s?"Saving...":"Finish",nextDisabled:s})]})}function cw({status:e,visible:t}){if(!t||!e||!e.installed||!e.running||e.imageBuilt&&e.pullStatus!=="pulling")return null;const n=e.pullStatus==="pulling",r=e.pullProgress||0;return h.jsx(be.div,{initial:{opacity:0,y:-10},animate:{opacity:1,y:0},exit:{opacity:0,y:-10},className:"fixed top-4 right-4 z-50",children:h.jsx("div",{className:"bg-white/95 dark:bg-gray-900/95 border border-gray-200 dark:border-gray-700 rounded-lg shadow-xl px-4 py-3 backdrop-blur-sm min-w-[240px]",children:h.jsxs("div",{className:"flex items-center gap-3",children:[h.jsx("div",{className:"w-8 h-8 rounded-lg bg-blue-500/20 flex items-center justify-center flex-shrink-0",children:h.jsx("svg",{className:"w-5 h-5",viewBox:"0 0 756.26 596.9",children:h.jsx("path",{fill:"#1d63ed",d:"M743.96,245.25c-18.54-12.48-67.26-17.81-102.68-8.27-1.91-35.28-20.1-65.01-53.38-90.95l-12.32-8.27-8.21,12.4c-16.14,24.5-22.94,57.14-20.53,86.81,1.9,18.28,8.26,38.83,20.53,53.74-46.1,26.74-88.59,20.67-276.77,20.67H.06c-.85,42.49,5.98,124.23,57.96,190.77,5.74,7.35,12.04,14.46,18.87,21.31,42.26,42.32,106.11,73.35,201.59,73.44,145.66.13,270.46-78.6,346.37-268.97,24.98.41,90.92,4.48,123.19-57.88.79-1.05,8.21-16.54,8.21-16.54l-12.3-8.27Z"})})}),h.jsx("div",{className:"flex-1 min-w-0",children:n?h.jsxs(h.Fragment,{children:[h.jsxs("div",{className:"flex items-center gap-2 text-blue-600 dark:text-blue-400 text-sm mb-1",children:[h.jsx(be.span,{className:"w-3 h-3 border-2 border-blue-600 dark:border-blue-400 border-t-transparent rounded-full",animate:{rotate:360},transition:{duration:1,repeat:1/0,ease:"linear"}}),h.jsx("span",{className:"truncate",children:"Downloading cross-compile image..."})]}),h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx("div",{className:"flex-1 h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden",children:h.jsx(be.div,{className:"h-full bg-blue-500",animate:{width:`${r}%`}})}),h.jsxs("span",{className:"text-xs text-gray-500 tabular-nums",children:[r,"%"]})]})]}):e.imageBuilt?h.jsxs("div",{className:"flex items-center gap-2 text-green-600 dark:text-green-400 text-sm",children:[h.jsx("svg",{className:"w-4 h-4",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})}),h.jsx("span",{children:"Docker image ready"})]}):h.jsx("div",{className:"text-sm text-gray-600 dark:text-gray-400",children:"Preparing Docker build..."})})]})})})}function Ro({command:e,label:t}){const[n,r]=N.useState(!1),i=()=>{navigator.clipboard.writeText(e),r(!0),setTimeout(()=>r(!1),2e3)};return h.jsxs("div",{children:[h.jsx("p",{className:"text-gray-600 dark:text-gray-400 mb-1",children:t}),h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx("code",{className:"flex-1 text-green-600 dark:text-green-400 font-mono text-xs bg-gray-100 dark:bg-gray-900 px-2 py-1 rounded",children:e}),h.jsx("button",{onClick:i,className:"text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition-colors p-1",title:"Copy command",children:n?h.jsx("svg",{className:"w-4 h-4 text-green-600 dark:text-green-400",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})}):h.jsx("svg",{className:"w-4 h-4",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"})})})]})]})}function dw({onClose:e}){return h.jsxs(be.div,{variants:ai,initial:"initial",animate:"animate",exit:"exit",transition:{duration:.2},className:"text-center py-8",children:[h.jsx(be.div,{initial:{scale:0},animate:{scale:1},transition:{type:"spring",stiffness:200,damping:15},className:"w-16 h-16 rounded-full bg-green-500/20 flex items-center justify-center mx-auto mb-6",children:h.jsx("svg",{className:"w-8 h-8 text-green-600 dark:text-green-400",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})}),h.jsx("h2",{className:"text-2xl font-bold mb-2 text-gray-900 dark:text-white",children:"Setup Complete"}),h.jsx("p",{className:"text-gray-600 dark:text-gray-300 mb-8",children:"Your development environment is ready to use."}),h.jsxs("div",{className:"bg-gray-100 dark:bg-gray-900/50 rounded-lg p-4 text-left mb-6 max-w-sm mx-auto",children:[h.jsx("h3",{className:"text-sm font-medium text-gray-600 dark:text-gray-400 mb-3",children:"Next Steps"}),h.jsxs("div",{className:"space-y-3 text-sm",children:[h.jsx(Ro,{command:"wails3 init -n myapp",label:"Create a new project:"}),h.jsx(Ro,{command:"wails3 dev",label:"Start development server:"}),h.jsx(Ro,{command:"wails3 build",label:"Build for production:"})]})]}),h.jsx("button",{onClick:e,className:"px-6 py-2.5 rounded-lg bg-red-600 text-white font-medium hover:bg-red-500 transition-colors",children:"Close"})]})}function fw(){const[e,t]=N.useState("welcome"),[n,r]=N.useState([]),[i,s]=N.useState(null),[o,a]=N.useState(null),[l,u]=N.useState(!1),[c,d]=N.useState(!1),[f,y]=N.useState({author:{name:"",company:""},project:{productIdentifierPrefix:"com.example",defaultTemplate:"vanilla",copyrightTemplate:"© {year}, {company}",descriptionTemplate:"A {name} application",defaultVersion:"0.1.0"}}),[v,x]=N.useState(!1),[S,m]=N.useState(!1),[p,g]=N.useState(()=>{if(typeof window<"u"){const F=localStorage.getItem("wails-setup-theme");if(F==="light"||F==="dark")return F;if(window.matchMedia("(prefers-color-scheme: light)").matches)return"light"}return"dark"}),w=()=>{g(F=>{const se=F==="dark"?"light":"dark";return localStorage.setItem("wails-setup-theme",se),se})};N.useEffect(()=>{p==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")},[p]);const k=[{id:"welcome",label:"Welcome"},{id:"dependencies",label:"Dependencies"},{id:"docker",label:"Docker"},{id:"defaults",label:"Defaults"},{id:"complete",label:"Complete"}];N.useEffect(()=>{T()},[]);const T=async()=>{const F=await G2();s(F.system)},P=async()=>{if(e==="welcome"){d(!0);const F=await Kd();r(F),d(!1),t("dependencies")}else if(e==="dependencies"){const F=n.find(se=>se.name==="docker");if(F!=null&&F.installed){const se=await Vo();a(se),X(n)}t("docker")}else if(e==="docker"){const F=await Z2();y(F),t("defaults")}else e==="defaults"&&(x(!0),await q2(f),x(!1),t("complete"))},C=async()=>{d(!0);const F=await Kd();r(F),d(!1)},R=()=>{e==="dependencies"?t("welcome"):e==="docker"?t("dependencies"):e==="defaults"&&t("docker")},M=async()=>{u(!0),await Q2();const F=async()=>{const se=await Vo();a(se),se.pullStatus==="pulling"?setTimeout(F,1e3):u(!1)};F()},X=async F=>{const se=F.find(E=>E.name==="docker");if(!(se!=null&&se.installed)||S)return;m(!0);const B=await Y2();if(a(B.status),B.started&&B.status.pullStatus==="pulling"){u(!0);const E=async()=>{const L=await Vo();a(L),L.pullStatus==="pulling"?setTimeout(E,1e3):u(!1)};setTimeout(E,1e3)}},Ke=async()=>{await X2(),window.close()},Ie=Ke,kt=S&&e==="defaults";return h.jsx(Fm.Provider,{value:{theme:p,toggleTheme:w},children:h.jsxs("div",{className:"min-h-screen bg-gray-50 dark:bg-[#0f0f0f] flex items-center justify-center p-4 transition-colors",children:[h.jsx(rw,{}),h.jsx(Jc,{children:kt&&h.jsx(cw,{status:o,visible:kt})}),h.jsxs("div",{className:"w-full max-w-lg",children:[h.jsxs("div",{className:"flex flex-col items-center mb-4",children:[h.jsx(tw,{size:160,theme:p}),h.jsx("div",{className:"mt-3",children:h.jsx(iw,{steps:k,currentStep:e})})]}),h.jsx("div",{className:"bg-white dark:bg-gray-900/80 border border-gray-200 dark:border-gray-800 rounded-xl p-5 shadow-2xl max-h-[70vh] overflow-y-auto",children:h.jsxs(Jc,{mode:"wait",children:[e==="welcome"&&h.jsx(sw,{system:i,onNext:P,onCancel:Ie,checking:c},"welcome"),e==="dependencies"&&h.jsx(aw,{dependencies:n,onNext:P,onBack:R,onCancel:Ie,onRetry:C,checking:c},"dependencies"),e==="defaults"&&h.jsx(uw,{defaults:f,onDefaultsChange:y,onNext:P,onBack:R,onCancel:Ie,saving:v},"defaults"),e==="docker"&&h.jsx(lw,{dockerStatus:o,buildingImage:l,onBuildImage:M,onNext:P,onBack:R,onCancel:Ie},"docker"),e==="complete"&&h.jsx(dw,{onClose:Ke},"complete")]})}),h.jsx("div",{className:"text-center mt-4 text-xs text-gray-500 dark:text-gray-600",children:"Wails • Build cross-platform apps with Go"})]})]})})}Io.createRoot(document.getElementById("root")).render(h.jsx(t0.StrictMode,{children:h.jsx(fw,{})})); diff --git a/v3/internal/setupwizard/frontend/dist/assets/index-CCNHCwJO.css b/v3/internal/setupwizard/frontend/dist/assets/index-CCNHCwJO.css new file mode 100644 index 000000000..d08fa080b --- /dev/null +++ b/v3/internal/setupwizard/frontend/dist/assets/index-CCNHCwJO.css @@ -0,0 +1 @@ +*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.\!visible{visibility:visible!important}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.left-4{left:1rem}.right-4{right:1rem}.top-4{top:1rem}.z-10{z-index:10}.z-50{z-index:50}.mx-1\.5{margin-left:.375rem;margin-right:.375rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-0\.5{margin-bottom:.125rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-6{margin-left:1.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-full{height:100%}.max-h-\[70vh\]{max-height:70vh}.min-h-screen{min-height:100vh}.w-1\.5{width:.375rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[240px\]{min-width:240px}.max-w-lg{max-width:32rem}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.animate-spin{animation:spin 1s linear infinite}.cursor-not-allowed{cursor:not-allowed}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-y-1\.5{row-gap:.375rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-blue-500{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-blue-500\/30{border-color:#3b82f64d}.border-blue-600{--tw-border-opacity: 1;border-color:rgb(37 99 235 / var(--tw-border-opacity, 1))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.border-gray-200\/50{border-color:#e5e7eb80}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.border-gray-400{--tw-border-opacity: 1;border-color:rgb(156 163 175 / var(--tw-border-opacity, 1))}.border-green-500\/20{border-color:#22c55e33}.border-t-red-500{--tw-border-opacity: 1;border-top-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-t-transparent{border-top-color:transparent}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/20{background-color:#3b82f633}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.bg-gray-300{--tw-bg-opacity: 1;background-color:rgb(209 213 219 / var(--tw-bg-opacity, 1))}.bg-gray-400{--tw-bg-opacity: 1;background-color:rgb(156 163 175 / var(--tw-bg-opacity, 1))}.bg-gray-400\/20{background-color:#9ca3af33}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/20{background-color:#22c55e33}.bg-red-500\/20{background-color:#ef444433}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-white\/80{background-color:#fffc}.bg-white\/95{background-color:#fffffff2}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.object-contain{-o-object-fit:contain;object-fit:contain}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[9px\]{font-size:9px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.text-yellow-600{--tw-text-opacity: 1;color:rgb(202 138 4 / var(--tw-text-opacity, 1))}.placeholder-gray-400::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(156 163 175 / var(--tw-placeholder-opacity, 1))}.placeholder-gray-400::placeholder{--tw-placeholder-opacity: 1;color:rgb(156 163 175 / var(--tw-placeholder-opacity, 1))}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.drop-shadow{--tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / .1)) drop-shadow(0 1px 1px rgb(0 0 0 / .06));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}:root{--wails-red: #ef4444;--wails-red-dark: #dc2626;--wails-red-light: #f87171;--bg-primary: #0f0f0f;--bg-secondary: #1f2937;--bg-tertiary: #374151}*{box-sizing:border-box}html,body,#root{margin:0;padding:0;min-height:100vh;background:var(--bg-primary);color:#fff;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.gradient-text{background:linear-gradient(135deg,#fff,#ef4444);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.glass-card{background:#1f2937cc;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(55,65,81,.5)}.grid-bg{background-image:linear-gradient(rgba(239,68,68,.03) 1px,transparent 1px),linear-gradient(90deg,rgba(239,68,68,.03) 1px,transparent 1px);background-size:40px 40px}.radial-glow{background:radial-gradient(ellipse at center,rgba(239,68,68,.1) 0%,transparent 70%)}::-webkit-scrollbar{width:8px}::-webkit-scrollbar-track{background:var(--bg-primary)}::-webkit-scrollbar-thumb{background:var(--bg-tertiary);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#4b5563}.btn-primary{border-radius:.75rem;background-image:linear-gradient(to right,var(--tw-gradient-stops));--tw-gradient-from: #ef4444 var(--tw-gradient-from-position);--tw-gradient-to: rgb(239 68 68 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);--tw-gradient-to: #dc2626 var(--tw-gradient-to-position);padding:.75rem 2rem;font-weight:600;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1));--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.3s}.btn-primary:hover{--tw-scale-x: 1.05;--tw-scale-y: 1.05;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);--tw-shadow-color: rgb(239 68 68 / .3);--tw-shadow: var(--tw-shadow-colored)}.btn-primary:active{--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.btn-secondary{border-radius:.75rem;border-width:1px;--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1));background-color:transparent;padding:.75rem 2rem;font-weight:500;--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1));transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.3s}.btn-secondary:hover{--tw-border-opacity: 1;border-color:rgb(107 114 128 / var(--tw-border-opacity, 1));background-color:#1f293780;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}@keyframes spin{to{transform:rotate(360deg)}}.spinner{animation:spin 1s linear infinite}.check-path{stroke-dasharray:100;stroke-dashoffset:100;animation:drawCheck .5s ease-out forwards}@media (prefers-reduced-motion: reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}.last\:border-0:last-child{border-width:0px}.hover\:bg-blue-500:hover{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.hover\:bg-blue-500\/30:hover{background-color:#3b82f64d}.hover\:bg-gray-200:hover{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-300:hover{--tw-bg-opacity: 1;background-color:rgb(209 213 219 / var(--tw-bg-opacity, 1))}.hover\:bg-red-500:hover{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.hover\:text-blue-600:hover{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.focus\:border-red-500:focus{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.dark\:border-blue-400:is(.dark *){--tw-border-opacity: 1;border-color:rgb(96 165 250 / var(--tw-border-opacity, 1))}.dark\:border-gray-600:is(.dark *){--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.dark\:border-gray-700:is(.dark *){--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.dark\:border-gray-800:is(.dark *){--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.dark\:border-gray-800\/50:is(.dark *){border-color:#1f293780}.dark\:bg-\[\#0f0f0f\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(15 15 15 / var(--tw-bg-opacity, 1))}.dark\:bg-gray-500:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.dark\:bg-gray-600\/20:is(.dark *){background-color:#4b556333}.dark\:bg-gray-700:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.dark\:bg-gray-800:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.dark\:bg-gray-900:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.dark\:bg-gray-900\/50:is(.dark *){background-color:#11182780}.dark\:bg-gray-900\/80:is(.dark *){background-color:#111827cc}.dark\:bg-gray-900\/95:is(.dark *){background-color:#111827f2}.dark\:text-blue-400:is(.dark *){--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.dark\:text-gray-200:is(.dark *){--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.dark\:text-gray-300:is(.dark *){--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.dark\:text-gray-400:is(.dark *){--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.dark\:text-gray-500:is(.dark *){--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.dark\:text-gray-600:is(.dark *){--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.dark\:text-green-400:is(.dark *){--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.dark\:text-red-300:is(.dark *){--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.dark\:text-red-400:is(.dark *){--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.dark\:text-white:is(.dark *){--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.dark\:text-yellow-400:is(.dark *){--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.dark\:placeholder-gray-600:is(.dark *)::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(75 85 99 / var(--tw-placeholder-opacity, 1))}.dark\:placeholder-gray-600:is(.dark *)::placeholder{--tw-placeholder-opacity: 1;color:rgb(75 85 99 / var(--tw-placeholder-opacity, 1))}.dark\:hover\:bg-gray-700:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.dark\:hover\:text-blue-300:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.dark\:hover\:text-gray-300:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))} diff --git a/v3/internal/setupwizard/frontend/dist/assets/wails-logo-black-text-Cx-vsZ4W.svg b/v3/internal/setupwizard/frontend/dist/assets/wails-logo-black-text-Cx-vsZ4W.svg new file mode 100644 index 000000000..e90698049 --- /dev/null +++ b/v3/internal/setupwizard/frontend/dist/assets/wails-logo-black-text-Cx-vsZ4W.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v3/internal/setupwizard/frontend/dist/assets/wails-logo-white-text-B284k7fX.svg b/v3/internal/setupwizard/frontend/dist/assets/wails-logo-white-text-B284k7fX.svg new file mode 100644 index 000000000..5ebf9d616 --- /dev/null +++ b/v3/internal/setupwizard/frontend/dist/assets/wails-logo-white-text-B284k7fX.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v3/internal/setupwizard/frontend/dist/index.html b/v3/internal/setupwizard/frontend/dist/index.html new file mode 100644 index 000000000..1a00bbd54 --- /dev/null +++ b/v3/internal/setupwizard/frontend/dist/index.html @@ -0,0 +1,16 @@ + + + + + + Wails Setup Wizard + + + + + + + +
        + + diff --git a/v3/internal/setupwizard/frontend/dist/wails-logo.png b/v3/internal/setupwizard/frontend/dist/wails-logo.png new file mode 100755 index 000000000..0f32ab92f Binary files /dev/null and b/v3/internal/setupwizard/frontend/dist/wails-logo.png differ diff --git a/v3/internal/setupwizard/frontend/index.html b/v3/internal/setupwizard/frontend/index.html new file mode 100644 index 000000000..ba497ab66 --- /dev/null +++ b/v3/internal/setupwizard/frontend/index.html @@ -0,0 +1,15 @@ + + + + + + Wails Setup Wizard + + + + + +
        + + + diff --git a/v3/internal/setupwizard/frontend/package-lock.json b/v3/internal/setupwizard/frontend/package-lock.json new file mode 100644 index 000000000..ec25c0e62 --- /dev/null +++ b/v3/internal/setupwizard/frontend/package-lock.json @@ -0,0 +1,2687 @@ +{ + "name": "wails-setup-wizard", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wails-setup-wizard", + "version": "0.0.0", + "dependencies": { + "framer-motion": "^12.23.25", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.14", + "typescript": "~5.6.2", + "vite": "^5.4.10" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", + "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.27.0", + "caniuse-lite": "^1.0.30001754", + "fraction.js": "^5.3.4", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.3.tgz", + "integrity": "sha512-8QdH6czo+G7uBsNo0GiUfouPN1lRzKdJTGnKXwe12gkFbnnOUaUKGN55dMkfy+mnxmvjwl9zcI4VncczcVXDhA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001759", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", + "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.266", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz", + "integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "12.23.25", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.25.tgz", + "integrity": "sha512-gUHGl2e4VG66jOcH0JHhuJQr6ZNwrET9g31ZG0xdXzT0CznP7fHX4P8Bcvuc4MiUB90ysNnWX2ukHRIggkl6hQ==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", + "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/v3/internal/setupwizard/frontend/package.json b/v3/internal/setupwizard/frontend/package.json new file mode 100644 index 000000000..f46a9cab6 --- /dev/null +++ b/v3/internal/setupwizard/frontend/package.json @@ -0,0 +1,26 @@ +{ + "name": "wails-setup-wizard", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "framer-motion": "^12.23.25", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.14", + "typescript": "~5.6.2", + "vite": "^5.4.10" + } +} diff --git a/v3/internal/setupwizard/frontend/postcss.config.js b/v3/internal/setupwizard/frontend/postcss.config.js new file mode 100644 index 000000000..2e7af2b7f --- /dev/null +++ b/v3/internal/setupwizard/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/v3/internal/setupwizard/frontend/public/wails-logo.png b/v3/internal/setupwizard/frontend/public/wails-logo.png new file mode 100755 index 000000000..0f32ab92f Binary files /dev/null and b/v3/internal/setupwizard/frontend/public/wails-logo.png differ diff --git a/v3/internal/setupwizard/frontend/src/App.tsx b/v3/internal/setupwizard/frontend/src/App.tsx new file mode 100644 index 000000000..9202e172b --- /dev/null +++ b/v3/internal/setupwizard/frontend/src/App.tsx @@ -0,0 +1,1264 @@ +import { useState, useEffect, createContext, useContext } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import type { DependencyStatus, SystemInfo, DockerStatus, GlobalDefaults } from './types'; +import { checkDependencies, getState, getDockerStatus, buildDockerImage, close, getDefaults, saveDefaults, startDockerBuildBackground } from './api'; +import WailsLogo from './components/WailsLogo'; + +type Step = 'welcome' | 'dependencies' | 'defaults' | 'docker' | 'complete'; +type Theme = 'light' | 'dark'; + +// Theme context +const ThemeContext = createContext<{ theme: Theme; toggleTheme: () => void }>({ + theme: 'dark', + toggleTheme: () => {} +}); + +const useTheme = () => useContext(ThemeContext); + +// Theme toggle button component +function ThemeToggle() { + const { theme, toggleTheme } = useTheme(); + + return ( + + ); +} + +// Classic wizard page slide animation +const pageVariants = { + initial: { opacity: 0, x: 50 }, + animate: { opacity: 1, x: 0 }, + exit: { opacity: 0, x: -50 } +}; + +// Wizard step indicator +function StepIndicator({ steps, currentStep }: { steps: { id: Step; label: string }[]; currentStep: Step }) { + const currentIndex = steps.findIndex(s => s.id === currentStep); + + return ( +
        + {steps.map((step, i) => ( +
        + + {step.label} + + {i < steps.length - 1 && ( + + )} +
        + ))} +
        + ); +} + +// Wizard footer with navigation buttons +function WizardFooter({ + onBack, + onNext, + onCancel, + nextLabel = 'Next', + backLabel = 'Back', + showBack = true, + nextDisabled = false, + showRetry = false, + onRetry +}: { + onBack?: () => void; + onNext: () => void; + onCancel?: () => void; + nextLabel?: string; + backLabel?: string; + showBack?: boolean; + nextDisabled?: boolean; + showRetry?: boolean; + onRetry?: () => void; +}) { + return ( +
        +
        + {onCancel && ( + + )} +
        +
        + {showBack && onBack && ( + + )} + {showRetry && onRetry && ( + + )} + +
        +
        + ); +} + +// Welcome Page +function WelcomePage({ system, onNext, onCancel, checking }: { system: SystemInfo | null; onNext: () => void; onCancel: () => void; checking: boolean }) { + return ( + +
        +

        + This wizard will help you set up your development environment. +

        +
        + + {system && ( +
        +

        System Information

        +
        + Operating System + {system.osName || system.os} ({system.arch}) + Wails Version + {system.wailsVersion.replace(/^v+/, '')} + Go Version + {system.goVersion.replace(/^go/, '')} +
        +
        + )} + + {checking ? ( +
        +
        +
        + Checking dependencies... +
        +
        + ) : ( +
        +

        Setup will check:

        +
          +
        • + + Required build dependencies (GTK, WebKit, GCC) +
        • +
        • + + Optional tools (npm, Docker) +
        • +
        • + + Cross-compilation capabilities +
        • +
        +
        + )} + + + + ); +} + +// Dependency row component +function DependencyRow({ + dep +}: { + dep: DependencyStatus; +}) { + return ( +
        + {/* Status icon */} +
        + {dep.installed ? ( +
        + + + +
        + ) : dep.required ? ( +
        + + + +
        + ) : ( +
        +
        +
        + )} +
        + + {/* Info */} +
        +
        + + {dep.name} + + {!dep.required && ( + (optional) + )} + + {dep.version && ( + {dep.version} + )} +
        + {dep.message && ( +

        {dep.message}

        + )} + + {/* Help URL link for non-system installs */} + {!dep.installed && dep.helpUrl && ( + + )} +
        +
        + ); +} + +// Dependencies Page +function DependenciesPage({ + dependencies, + onNext, + onBack, + onCancel, + onRetry, + checking +}: { + dependencies: DependencyStatus[]; + onNext: () => void; + onBack: () => void; + onCancel: () => void; + onRetry: () => void; + checking: boolean; +}) { + const [copied, setCopied] = useState(false); + const missingRequired = dependencies.filter(d => d.required && !d.installed); + const allRequiredInstalled = missingRequired.length === 0; + const missingDeps = dependencies.filter(d => !d.installed); + + // Build combined install command from all missing deps that have system commands (starting with sudo) + const combinedInstallCommand = (() => { + const systemCommands = missingDeps + .filter(d => d.installCommand?.startsWith('sudo ')) + .map(d => d.installCommand!); + + if (systemCommands.length === 0) return null; + + // Extract package names from "sudo pacman -S pkg" style commands + // Group by package manager + const pacmanPkgs: string[] = []; + const aptPkgs: string[] = []; + const dnfPkgs: string[] = []; + + for (const cmd of systemCommands) { + if (cmd.includes('pacman -S')) { + const match = cmd.match(/pacman -S\s+(.+)/); + if (match) pacmanPkgs.push(...match[1].split(/\s+/)); + } else if (cmd.includes('apt install')) { + const match = cmd.match(/apt install\s+(.+)/); + if (match) aptPkgs.push(...match[1].split(/\s+/)); + } else if (cmd.includes('dnf install')) { + const match = cmd.match(/dnf install\s+(.+)/); + if (match) dnfPkgs.push(...match[1].split(/\s+/)); + } + } + + if (pacmanPkgs.length > 0) { + return `sudo pacman -S ${pacmanPkgs.join(' ')}`; + } else if (aptPkgs.length > 0) { + return `sudo apt install ${aptPkgs.join(' ')}`; + } else if (dnfPkgs.length > 0) { + return `sudo dnf install ${dnfPkgs.join(' ')}`; + } + + return null; + })(); + + const copyCommand = () => { + if (combinedInstallCommand) { + navigator.clipboard.writeText(combinedInstallCommand); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + }; + + return ( + + {/* Loading overlay for retry */} + {checking && ( +
        +
        + + Checking dependencies... +
        +
        + )} + +
        +

        System Dependencies

        +

        + The following dependencies are needed to build Wails applications. +

        +
        + + {/* All Dependencies */} +
        +
        + {dependencies.map(dep => ( + + ))} +
        +
        + + {/* Combined Install Command */} + {combinedInstallCommand && ( +
        +
        Install all missing dependencies:
        +
        + + {combinedInstallCommand} + + +
        +
        + )} + + {/* Status Summary - only show when all required are installed */} + {allRequiredInstalled && ( +
        +
        + + + + All required dependencies are installed. You can proceed. +
        +
        + )} + + +
        + ); +} + +// Docker Page +function DockerPage({ + dockerStatus, + buildingImage, + onBuildImage, + onNext, + onBack, + onCancel +}: { + dockerStatus: DockerStatus | null; + buildingImage: boolean; + onBuildImage: () => void; + onNext: () => void; + onBack: () => void; + onCancel: () => void; +}) { + return ( + +
        +

        Cross-Platform Builds

        +

        + Docker enables building for macOS, Windows, and Linux from any platform. +

        +
        + +
        +
        +
        + + + +
        + +
        +

        Docker Status

        + + {!dockerStatus ? ( +
        Checking Docker...
        + ) : !dockerStatus.installed ? ( +
        +
        + + Not installed +
        +

        + Docker is optional but required for cross-platform builds. +

        + + Install Docker Desktop + + + + +
        + ) : !dockerStatus.running ? ( +
        +
        + + Installed but not running +
        +

        + Start Docker Desktop to enable cross-platform builds. +

        +
        + ) : dockerStatus.imageBuilt ? ( +
        +
        + + Ready for cross-platform builds +
        +

        + Docker {dockerStatus.version} • wails-cross image installed +

        +
        + ) : buildingImage ? ( +
        +
        + + Building wails-cross image... {dockerStatus.pullProgress}% +
        +
        + +
        +
        + ) : ( +
        +
        + + Cross-compilation image not installed +
        +

        + Docker {dockerStatus.version} is running. Build the wails-cross image to enable cross-platform builds. +

        + +
        + )} +
        +
        +
        + +
        +

        What you can build:

        +
        +
        +
        + {/* Apple logo */} + + + +
        +
        macOS
        +
        .app / .dmg
        +
        +
        +
        + {/* Windows logo */} + + + +
        +
        Windows
        +
        .exe / .msi
        +
        +
        +
        + {/* Tux - Linux penguin */} + + + +
        +
        Linux
        +
        .deb / .rpm / PKGBUILD
        +
        +
        +
        + + +
        + ); +} + +// Defaults Page - Configure global defaults for new projects +function DefaultsPage({ + defaults, + onDefaultsChange, + onNext, + onBack, + onCancel, + saving +}: { + defaults: GlobalDefaults; + onDefaultsChange: (defaults: GlobalDefaults) => void; + onNext: () => void; + onBack: () => void; + onCancel: () => void; + saving: boolean; +}) { + return ( + +
        +

        Project Defaults

        +

        + Configure defaults for new Wails projects. +

        +
        + + {/* Author Information */} +
        +

        Author Information

        +
        +
        + + onDefaultsChange({ + ...defaults, + author: { ...defaults.author, name: e.target.value } + })} + placeholder="John Doe" + className="w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none" + /> +
        +
        + + onDefaultsChange({ + ...defaults, + author: { ...defaults.author, company: e.target.value } + })} + placeholder="My Company" + className="w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none" + /> +
        +
        +
        + + {/* Project Defaults */} +
        +

        Project Settings

        +
        +
        +
        + + onDefaultsChange({ + ...defaults, + project: { ...defaults.project, productIdentifierPrefix: e.target.value } + })} + placeholder="com.mycompany" + className="w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none font-mono" + /> +
        +
        + + onDefaultsChange({ + ...defaults, + project: { ...defaults.project, defaultVersion: e.target.value } + })} + placeholder="0.1.0" + className="w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none font-mono" + /> +
        +
        +
        + + +
        +
        +
        + + {/* macOS Signing */} +
        +
        + + + +

        macOS Code Signing

        + (optional) +
        +

        These are public identifiers. App-specific passwords are stored securely in your Keychain.

        +
        +
        + + onDefaultsChange({ + ...defaults, + signing: { + ...defaults.signing, + macOS: { ...defaults.signing?.macOS, developerID: e.target.value, appleID: defaults.signing?.macOS?.appleID || '', teamID: defaults.signing?.macOS?.teamID || '' }, + windows: defaults.signing?.windows || { certificatePath: '', timestampServer: '' } + } + })} + placeholder="Developer ID Application: John Doe (TEAMID)" + className="w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none font-mono" + /> +
        +
        +
        + + onDefaultsChange({ + ...defaults, + signing: { + ...defaults.signing, + macOS: { ...defaults.signing?.macOS, appleID: e.target.value, developerID: defaults.signing?.macOS?.developerID || '', teamID: defaults.signing?.macOS?.teamID || '' }, + windows: defaults.signing?.windows || { certificatePath: '', timestampServer: '' } + } + })} + placeholder="you@example.com" + className="w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none" + /> +
        +
        + + onDefaultsChange({ + ...defaults, + signing: { + ...defaults.signing, + macOS: { ...defaults.signing?.macOS, teamID: e.target.value, developerID: defaults.signing?.macOS?.developerID || '', appleID: defaults.signing?.macOS?.appleID || '' }, + windows: defaults.signing?.windows || { certificatePath: '', timestampServer: '' } + } + })} + placeholder="ABCD1234EF" + className="w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none font-mono" + /> +
        +
        +
        +
        + + {/* Windows Signing */} +
        +
        + + + +

        Windows Code Signing

        + (optional) +
        +
        +
        + + onDefaultsChange({ + ...defaults, + signing: { + ...defaults.signing, + macOS: defaults.signing?.macOS || { developerID: '', appleID: '', teamID: '' }, + windows: { ...defaults.signing?.windows, certificatePath: e.target.value, timestampServer: defaults.signing?.windows?.timestampServer || '' } + } + })} + placeholder="/path/to/certificate.pfx" + className="w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none font-mono" + /> +
        +
        + + onDefaultsChange({ + ...defaults, + signing: { + ...defaults.signing, + macOS: defaults.signing?.macOS || { developerID: '', appleID: '', teamID: '' }, + windows: { ...defaults.signing?.windows, timestampServer: e.target.value, certificatePath: defaults.signing?.windows?.certificatePath || '' } + } + })} + placeholder="http://timestamp.digicert.com" + className="w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-xs text-gray-900 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 focus:border-red-500 focus:outline-none font-mono" + /> +
        +
        +
        + + {/* Info about where this is stored */} +
        + Stored in: ~/.config/wails/defaults.yaml +
        + + +
        + ); +} + +// Persistent Docker status indicator - shown across all pages when Docker build is in progress +function DockerStatusIndicator({ + status, + visible +}: { + status: DockerStatus | null; + visible: boolean; +}) { + if (!visible || !status) return null; + + // Don't show if Docker is not installed/running or if image is already built + if (!status.installed || !status.running) return null; + if (status.imageBuilt && status.pullStatus !== 'pulling') return null; + + const isPulling = status.pullStatus === 'pulling'; + const progress = status.pullProgress || 0; + + return ( + +
        +
        + {/* Docker icon */} +
        + + + +
        + +
        + {isPulling ? ( + <> +
        + + Downloading cross-compile image... +
        +
        +
        + +
        + {progress}% +
        + + ) : status.imageBuilt ? ( +
        + + + + Docker image ready +
        + ) : ( +
        + Preparing Docker build... +
        + )} +
        +
        +
        +
        + ); +} + +// Copyable command component +function CopyableCommand({ command, label }: { command: string; label: string }) { + const [copied, setCopied] = useState(false); + + const copyCommand = () => { + navigator.clipboard.writeText(command); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
        +

        {label}

        +
        + + {command} + + +
        +
        + ); +} + +// Complete Page +function CompletePage({ onClose }: { onClose: () => void }) { + return ( + + + + + + + +

        Setup Complete

        +

        + Your development environment is ready to use. +

        + +
        +

        Next Steps

        +
        + + + +
        +
        + + +
        + ); +} + +// Main App +export default function App() { + const [step, setStep] = useState('welcome'); + const [dependencies, setDependencies] = useState([]); + const [system, setSystem] = useState(null); + const [dockerStatus, setDockerStatus] = useState(null); + const [buildingImage, setBuildingImage] = useState(false); + const [checkingDeps, setCheckingDeps] = useState(false); + const [defaults, setDefaults] = useState({ + author: { name: '', company: '' }, + project: { + productIdentifierPrefix: 'com.example', + defaultTemplate: 'vanilla', + copyrightTemplate: '© {year}, {company}', + descriptionTemplate: 'A {name} application', + defaultVersion: '0.1.0' + } + }); + const [savingDefaults, setSavingDefaults] = useState(false); + const [backgroundDockerStarted, setBackgroundDockerStarted] = useState(false); + const [theme, setTheme] = useState(() => { + // Default to dark, but check for saved preference or system preference + if (typeof window !== 'undefined') { + const saved = localStorage.getItem('wails-setup-theme'); + if (saved === 'light' || saved === 'dark') return saved; + if (window.matchMedia('(prefers-color-scheme: light)').matches) return 'light'; + } + return 'dark'; + }); + + const toggleTheme = () => { + setTheme(prev => { + const next = prev === 'dark' ? 'light' : 'dark'; + localStorage.setItem('wails-setup-theme', next); + return next; + }); + }; + + // Apply theme class to document + useEffect(() => { + if (theme === 'dark') { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + }, [theme]); + + const steps: { id: Step; label: string }[] = [ + { id: 'welcome', label: 'Welcome' }, + { id: 'dependencies', label: 'Dependencies' }, + { id: 'docker', label: 'Docker' }, + { id: 'defaults', label: 'Defaults' }, + { id: 'complete', label: 'Complete' }, + ]; + + useEffect(() => { + init(); + }, []); + + const init = async () => { + const state = await getState(); + setSystem(state.system); + }; + + const handleNext = async () => { + if (step === 'welcome') { + setCheckingDeps(true); + const deps = await checkDependencies(); + setDependencies(deps); + setCheckingDeps(false); + setStep('dependencies'); + } else if (step === 'dependencies') { + // Check docker status and start background build if available + const dockerDep = dependencies.find(d => d.name === 'docker'); + if (dockerDep?.installed) { + const docker = await getDockerStatus(); + setDockerStatus(docker); + // Start background Docker build (so it downloads while user configures defaults) + startBackgroundDockerBuild(dependencies); + } + setStep('docker'); + } else if (step === 'docker') { + // Load existing defaults when entering defaults page + const loadedDefaults = await getDefaults(); + setDefaults(loadedDefaults); + setStep('defaults'); + } else if (step === 'defaults') { + // Save defaults before proceeding + setSavingDefaults(true); + await saveDefaults(defaults); + setSavingDefaults(false); + setStep('complete'); + } + }; + + const handleRetryDeps = async () => { + setCheckingDeps(true); + const deps = await checkDependencies(); + setDependencies(deps); + setCheckingDeps(false); + }; + + const handleBack = () => { + if (step === 'dependencies') setStep('welcome'); + else if (step === 'docker') setStep('dependencies'); + else if (step === 'defaults') setStep('docker'); + }; + + const handleBuildImage = async () => { + setBuildingImage(true); + await buildDockerImage(); + + const poll = async () => { + const status = await getDockerStatus(); + setDockerStatus(status); + if (status.pullStatus === 'pulling') { + setTimeout(poll, 1000); + } else { + setBuildingImage(false); + } + }; + poll(); + }; + + // Start background Docker build after dependencies check + const startBackgroundDockerBuild = async (deps: DependencyStatus[]) => { + const dockerDep = deps.find(d => d.name === 'docker'); + if (!dockerDep?.installed || backgroundDockerStarted) return; + + setBackgroundDockerStarted(true); + + // Try to start background build + const result = await startDockerBuildBackground(); + setDockerStatus(result.status); + + // If build started, poll for status + if (result.started && result.status.pullStatus === 'pulling') { + setBuildingImage(true); + const poll = async () => { + const status = await getDockerStatus(); + setDockerStatus(status); + if (status.pullStatus === 'pulling') { + setTimeout(poll, 1000); + } else { + setBuildingImage(false); + } + }; + setTimeout(poll, 1000); + } + }; + + const handleClose = async () => { + await close(); + window.close(); + }; + + const handleCancel = handleClose; + + // Show Docker indicator on defaults page when Docker build is in progress (Docker now downloads while user configures) + const showDockerIndicator = backgroundDockerStarted && step === 'defaults'; + + return ( + +
        + {/* Theme toggle */} + + + {/* Persistent Docker status indicator */} + + {showDockerIndicator && ( + + )} + + +
        + {/* Header with logo and step indicator */} +
        + +
        + +
        +
        + + {/* Wizard container */} +
        + + + {step === 'welcome' && ( + + )} + {step === 'dependencies' && ( + + )} + {step === 'defaults' && ( + + )} + {step === 'docker' && ( + + )} + {step === 'complete' && ( + + )} + +
        + + {/* Footer */} +
        + Wails • Build cross-platform apps with Go +
        +
        +
        +
        + ); +} diff --git a/v3/internal/setupwizard/frontend/src/api.ts b/v3/internal/setupwizard/frontend/src/api.ts new file mode 100644 index 000000000..73c312b89 --- /dev/null +++ b/v3/internal/setupwizard/frontend/src/api.ts @@ -0,0 +1,100 @@ +import type { WizardState, DependencyStatus, DockerStatus, UserConfig, WailsConfig, GlobalDefaults } from './types'; + +const API_BASE = '/api'; + +export async function getState(): Promise { + const response = await fetch(`${API_BASE}/state`); + return response.json(); +} + +export async function checkDependencies(): Promise { + const response = await fetch(`${API_BASE}/dependencies/check`); + return response.json(); +} + +export async function getDockerStatus(): Promise { + const response = await fetch(`${API_BASE}/docker/status`); + return response.json(); +} + +export async function buildDockerImage(): Promise<{ status: string }> { + const response = await fetch(`${API_BASE}/docker/build`, { method: 'POST' }); + return response.json(); +} + +export interface DockerStartBackgroundResponse { + started: boolean; + reason?: string; + status: DockerStatus; +} + +export async function startDockerBuildBackground(): Promise { + const response = await fetch(`${API_BASE}/docker/start-background`, { method: 'POST' }); + return response.json(); +} + +export async function detectConfig(): Promise> { + const response = await fetch(`${API_BASE}/config/detect`); + return response.json(); +} + +export async function saveConfig(config: UserConfig): Promise<{ status: string }> { + const response = await fetch(`${API_BASE}/config/save`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(config), + }); + return response.json(); +} + +export async function complete(): Promise<{ status: string; duration: string }> { + const response = await fetch(`${API_BASE}/complete`); + return response.json(); +} + +export async function close(): Promise { + await fetch(`${API_BASE}/close`); +} + +export async function getWailsConfig(): Promise { + const response = await fetch(`${API_BASE}/wails-config`); + return response.json(); +} + +export async function saveWailsConfig(config: WailsConfig): Promise<{ status: string }> { + const response = await fetch(`${API_BASE}/wails-config`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(config), + }); + return response.json(); +} + +export interface InstallResult { + success: boolean; + output: string; + error?: string; +} + +export async function installDependency(command: string): Promise { + const response = await fetch(`${API_BASE}/dependencies/install`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ command }), + }); + return response.json(); +} + +export async function getDefaults(): Promise { + const response = await fetch(`${API_BASE}/defaults`); + return response.json(); +} + +export async function saveDefaults(defaults: GlobalDefaults): Promise<{ status: string; path: string }> { + const response = await fetch(`${API_BASE}/defaults`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(defaults), + }); + return response.json(); +} diff --git a/v3/internal/setupwizard/frontend/src/assets/wails-logo-black-text.svg b/v3/internal/setupwizard/frontend/src/assets/wails-logo-black-text.svg new file mode 100755 index 000000000..e90698049 --- /dev/null +++ b/v3/internal/setupwizard/frontend/src/assets/wails-logo-black-text.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v3/internal/setupwizard/frontend/src/assets/wails-logo-white-text.svg b/v3/internal/setupwizard/frontend/src/assets/wails-logo-white-text.svg new file mode 100755 index 000000000..5ebf9d616 --- /dev/null +++ b/v3/internal/setupwizard/frontend/src/assets/wails-logo-white-text.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v3/internal/setupwizard/frontend/src/components/WailsLogo.tsx b/v3/internal/setupwizard/frontend/src/components/WailsLogo.tsx new file mode 100644 index 000000000..ef873d80c --- /dev/null +++ b/v3/internal/setupwizard/frontend/src/components/WailsLogo.tsx @@ -0,0 +1,25 @@ +import wailsLogoWhite from '../assets/wails-logo-white-text.svg'; +import wailsLogoBlack from '../assets/wails-logo-black-text.svg'; + +interface WailsLogoProps { + className?: string; + size?: number; + theme?: 'light' | 'dark'; +} + +export default function WailsLogo({ className = '', size = 240, theme = 'dark' }: WailsLogoProps) { + // White text for dark mode, black text for light mode + const logoSrc = theme === 'dark' ? wailsLogoWhite : wailsLogoBlack; + + return ( + Wails + ); +} diff --git a/v3/internal/setupwizard/frontend/src/index.css b/v3/internal/setupwizard/frontend/src/index.css new file mode 100644 index 000000000..2edd377fc --- /dev/null +++ b/v3/internal/setupwizard/frontend/src/index.css @@ -0,0 +1,112 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --wails-red: #ef4444; + --wails-red-dark: #dc2626; + --wails-red-light: #f87171; + --bg-primary: #0f0f0f; + --bg-secondary: #1f2937; + --bg-tertiary: #374151; +} + +* { + box-sizing: border-box; +} + +html, body, #root { + margin: 0; + padding: 0; + min-height: 100vh; + background: var(--bg-primary); + color: white; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Gradient text utility */ +.gradient-text { + background: linear-gradient(135deg, #ffffff 0%, #ef4444 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +/* Glass morphism card */ +.glass-card { + background: rgba(31, 41, 55, 0.8); + backdrop-filter: blur(10px); + border: 1px solid rgba(55, 65, 81, 0.5); +} + +/* Subtle grid background */ +.grid-bg { + background-image: + linear-gradient(rgba(239, 68, 68, 0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(239, 68, 68, 0.03) 1px, transparent 1px); + background-size: 40px 40px; +} + +/* Radial glow */ +.radial-glow { + background: radial-gradient(ellipse at center, rgba(239, 68, 68, 0.1) 0%, transparent 70%); +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: var(--bg-primary); +} + +::-webkit-scrollbar-thumb { + background: var(--bg-tertiary); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #4b5563; +} + +/* Button hover effect */ +.btn-primary { + @apply bg-gradient-to-r from-red-500 to-red-600 text-white font-semibold + py-3 px-8 rounded-xl shadow-lg transition-all duration-300 + hover:scale-105 hover:shadow-xl hover:shadow-red-500/30 + active:scale-95; +} + +.btn-secondary { + @apply bg-transparent border border-gray-600 text-gray-300 font-medium + py-3 px-8 rounded-xl transition-all duration-300 + hover:border-gray-500 hover:text-white hover:bg-gray-800/50; +} + +/* Status badge animations */ +@keyframes spin { + to { transform: rotate(360deg); } +} + +.spinner { + animation: spin 1s linear infinite; +} + +/* Draw check animation for success */ +.check-path { + stroke-dasharray: 100; + stroke-dashoffset: 100; + animation: drawCheck 0.5s ease-out forwards; +} + +/* Reduced motion support */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} diff --git a/v3/internal/setupwizard/frontend/src/main.tsx b/v3/internal/setupwizard/frontend/src/main.tsx new file mode 100644 index 000000000..964aeb4c7 --- /dev/null +++ b/v3/internal/setupwizard/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/v3/internal/setupwizard/frontend/src/types.ts b/v3/internal/setupwizard/frontend/src/types.ts new file mode 100644 index 000000000..7545955e0 --- /dev/null +++ b/v3/internal/setupwizard/frontend/src/types.ts @@ -0,0 +1,96 @@ +export interface DependencyStatus { + name: string; + installed: boolean; + version?: string; + path?: string; + status: 'installed' | 'not_installed' | 'needs_update' | 'checking'; + required: boolean; + message?: string; + installCommand?: string; + helpUrl?: string; +} + +export interface DockerStatus { + installed: boolean; + running: boolean; + version?: string; + imageBuilt: boolean; + imageName: string; + pullProgress: number; + pullStatus: 'idle' | 'pulling' | 'complete' | 'error'; + pullError?: string; +} + +export interface UserConfig { + developerName: string; + email: string; + defaultFramework: string; + projectDirectory: string; + editor: string; +} + +export interface WailsConfig { + info: { + companyName: string; + productName: string; + productIdentifier: string; + description: string; + copyright: string; + comments: string; + version: string; + }; +} + +export interface SystemInfo { + os: string; + arch: string; + wailsVersion: string; + goVersion: string; + homeDir: string; + osName?: string; + osVersion?: string; + gitName?: string; + gitEmail?: string; +} + +export interface WizardState { + currentStep: number; + dependencies: DependencyStatus[]; + docker: DockerStatus; + config: UserConfig; + system: SystemInfo; + startTime: string; +} + +export type Step = 'splash' | 'welcome' | 'dependencies' | 'docker' | 'defaults' | 'config' | 'wails-config' | 'complete'; + +export interface AuthorDefaults { + name: string; + company: string; +} + +export interface ProjectDefaults { + productIdentifierPrefix: string; + defaultTemplate: string; + copyrightTemplate: string; + descriptionTemplate: string; + defaultVersion: string; +} + +export interface SigningDefaults { + macOS: { + developerID: string; // e.g., "Developer ID Application: John Doe (TEAMID)" + appleID: string; // Apple ID for notarization + teamID: string; // Apple Team ID + }; + windows: { + certificatePath: string; // Path to .pfx certificate + timestampServer: string; // e.g., "http://timestamp.digicert.com" + }; +} + +export interface GlobalDefaults { + author: AuthorDefaults; + project: ProjectDefaults; + signing?: SigningDefaults; +} diff --git a/v3/internal/setupwizard/frontend/src/vite-env.d.ts b/v3/internal/setupwizard/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..d786e046b --- /dev/null +++ b/v3/internal/setupwizard/frontend/src/vite-env.d.ts @@ -0,0 +1,6 @@ +/// + +declare module '*.svg' { + const content: string; + export default content; +} diff --git a/v3/internal/setupwizard/frontend/tailwind.config.js b/v3/internal/setupwizard/frontend/tailwind.config.js new file mode 100644 index 000000000..eaf94b71a --- /dev/null +++ b/v3/internal/setupwizard/frontend/tailwind.config.js @@ -0,0 +1,57 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + darkMode: 'class', + theme: { + extend: { + colors: { + 'wails-red': { + DEFAULT: '#ef4444', + dark: '#dc2626', + light: '#f87171', + }, + }, + fontFamily: { + sans: ['Inter', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'sans-serif'], + }, + animation: { + 'fade-in': 'fadeIn 0.5s ease-out', + 'slide-up': 'slideUp 0.5s ease-out', + 'scale-in': 'scaleIn 0.3s ease-out', + 'pulse-glow': 'pulseGlow 2s ease-in-out infinite', + 'draw-check': 'drawCheck 0.5s ease-out forwards', + 'shimmer': 'shimmer 2s linear infinite', + }, + keyframes: { + fadeIn: { + '0%': { opacity: '0' }, + '100%': { opacity: '1' }, + }, + slideUp: { + '0%': { opacity: '0', transform: 'translateY(20px)' }, + '100%': { opacity: '1', transform: 'translateY(0)' }, + }, + scaleIn: { + '0%': { opacity: '0', transform: 'scale(0.9)' }, + '100%': { opacity: '1', transform: 'scale(1)' }, + }, + pulseGlow: { + '0%, 100%': { boxShadow: '0 0 20px rgba(239, 68, 68, 0.4)' }, + '50%': { boxShadow: '0 0 40px rgba(239, 68, 68, 0.6)' }, + }, + drawCheck: { + '0%': { strokeDashoffset: '100' }, + '100%': { strokeDashoffset: '0' }, + }, + shimmer: { + '0%': { backgroundPosition: '-200% 0' }, + '100%': { backgroundPosition: '200% 0' }, + }, + }, + }, + }, + plugins: [], +} diff --git a/v3/internal/setupwizard/frontend/tsconfig.json b/v3/internal/setupwizard/frontend/tsconfig.json new file mode 100644 index 000000000..109f0ac28 --- /dev/null +++ b/v3/internal/setupwizard/frontend/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/v3/internal/setupwizard/frontend/vite.config.ts b/v3/internal/setupwizard/frontend/vite.config.ts new file mode 100644 index 000000000..927fd4858 --- /dev/null +++ b/v3/internal/setupwizard/frontend/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + build: { + outDir: 'dist', + emptyOutDir: true, + }, +}) diff --git a/v3/internal/setupwizard/wizard.go b/v3/internal/setupwizard/wizard.go new file mode 100644 index 000000000..7073f1fe1 --- /dev/null +++ b/v3/internal/setupwizard/wizard.go @@ -0,0 +1,628 @@ +package setupwizard + +import ( + "context" + "embed" + "encoding/json" + "fmt" + "io/fs" + "net" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "sync" + "time" + + "github.com/pkg/browser" + "github.com/wailsapp/wails/v3/internal/operatingsystem" + "github.com/wailsapp/wails/v3/internal/version" + "gopkg.in/yaml.v3" +) + +//go:embed frontend/dist/* +var frontendFS embed.FS + +// DependencyStatus represents the status of a dependency +type DependencyStatus struct { + Name string `json:"name"` + Installed bool `json:"installed"` + Version string `json:"version,omitempty"` + Status string `json:"status"` // "installed", "not_installed", "needs_update" + Required bool `json:"required"` + Message string `json:"message,omitempty"` + InstallCommand string `json:"installCommand,omitempty"` + HelpURL string `json:"helpUrl,omitempty"` +} + +// DockerStatus represents Docker installation and image status +type DockerStatus struct { + Installed bool `json:"installed"` + Running bool `json:"running"` + Version string `json:"version,omitempty"` + ImageBuilt bool `json:"imageBuilt"` + ImageName string `json:"imageName"` + PullProgress int `json:"pullProgress"` + PullStatus string `json:"pullStatus"` // "idle", "pulling", "complete", "error" + PullError string `json:"pullError,omitempty"` +} + +// WailsConfigInfo represents the info section of wails.yaml +type WailsConfigInfo struct { + CompanyName string `json:"companyName" yaml:"companyName"` + ProductName string `json:"productName" yaml:"productName"` + ProductIdentifier string `json:"productIdentifier" yaml:"productIdentifier"` + Description string `json:"description" yaml:"description"` + Copyright string `json:"copyright" yaml:"copyright"` + Comments string `json:"comments,omitempty" yaml:"comments,omitempty"` + Version string `json:"version" yaml:"version"` +} + +// WailsConfig represents the wails.yaml configuration +type WailsConfig struct { + Info WailsConfigInfo `json:"info" yaml:"info"` +} + +// SystemInfo contains detected system information +type SystemInfo struct { + OS string `json:"os"` + Arch string `json:"arch"` + WailsVersion string `json:"wailsVersion"` + GoVersion string `json:"goVersion"` + HomeDir string `json:"homeDir"` + OSName string `json:"osName,omitempty"` + OSVersion string `json:"osVersion,omitempty"` +} + +// WizardState represents the complete wizard state +type WizardState struct { + Dependencies []DependencyStatus `json:"dependencies"` + System SystemInfo `json:"system"` + StartTime time.Time `json:"startTime"` +} + +// Wizard is the setup wizard server +type Wizard struct { + server *http.Server + state WizardState + stateMu sync.RWMutex + dockerStatus DockerStatus + dockerMu sync.RWMutex + done chan struct{} + shutdown chan struct{} +} + +// New creates a new setup wizard +func New() *Wizard { + return &Wizard{ + done: make(chan struct{}), + shutdown: make(chan struct{}), + state: WizardState{ + StartTime: time.Now(), + }, + } +} + +// Run starts the wizard and opens it in the browser +func (w *Wizard) Run() error { + // Initialize system info + w.initSystemInfo() + + // Find an available port + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return fmt.Errorf("failed to find available port: %w", err) + } + + port := listener.Addr().(*net.TCPAddr).Port + url := fmt.Sprintf("http://127.0.0.1:%d", port) + + // Set up HTTP routes + mux := http.NewServeMux() + w.setupRoutes(mux) + + w.server = &http.Server{ + Handler: mux, + } + + // Start server in goroutine + go func() { + if err := w.server.Serve(listener); err != nil && err != http.ErrServerClosed { + fmt.Fprintf(os.Stderr, "Server error: %v\n", err) + } + }() + + fmt.Printf("Setup wizard running at %s\n", url) + + // Open browser + if err := browser.OpenURL(url); err != nil { + fmt.Printf("Please open %s in your browser\n", url) + } + + // Wait for completion or shutdown + select { + case <-w.done: + fmt.Println("\nSetup completed successfully!") + case <-w.shutdown: + fmt.Println("\nSetup wizard closed.") + } + + // Shutdown server + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + return w.server.Shutdown(ctx) +} + +func (w *Wizard) setupRoutes(mux *http.ServeMux) { + // API routes + mux.HandleFunc("/api/state", w.handleState) + mux.HandleFunc("/api/dependencies/check", w.handleCheckDependencies) + mux.HandleFunc("/api/dependencies/install", w.handleInstallDependency) + mux.HandleFunc("/api/docker/status", w.handleDockerStatus) + mux.HandleFunc("/api/docker/build", w.handleDockerBuild) + mux.HandleFunc("/api/docker/start-background", w.handleDockerStartBackground) + mux.HandleFunc("/api/wails-config", w.handleWailsConfig) + mux.HandleFunc("/api/defaults", w.handleDefaults) + mux.HandleFunc("/api/complete", w.handleComplete) + mux.HandleFunc("/api/close", w.handleClose) + + // Serve frontend + frontendDist, err := fs.Sub(frontendFS, "frontend/dist") + if err != nil { + panic(err) + } + fileServer := http.FileServer(http.FS(frontendDist)) + + mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { + // Try to serve the file + path := r.URL.Path + if path == "/" { + path = "/index.html" + } + + // Check if file exists + if _, err := fs.Stat(frontendDist, strings.TrimPrefix(path, "/")); err != nil { + // Serve index.html for SPA routing + r.URL.Path = "/" + } + + fileServer.ServeHTTP(rw, r) + }) +} + +func (w *Wizard) initSystemInfo() { + w.stateMu.Lock() + defer w.stateMu.Unlock() + + homeDir, _ := os.UserHomeDir() + + w.state.System = SystemInfo{ + OS: runtime.GOOS, + Arch: runtime.GOARCH, + WailsVersion: version.String(), + GoVersion: runtime.Version(), + HomeDir: homeDir, + } + + // Get OS details + if info, err := operatingsystem.Info(); err == nil { + w.state.System.OSName = info.Name + w.state.System.OSVersion = info.Version + } +} + +func (w *Wizard) handleState(rw http.ResponseWriter, r *http.Request) { + w.stateMu.RLock() + defer w.stateMu.RUnlock() + + rw.Header().Set("Content-Type", "application/json") + json.NewEncoder(rw).Encode(w.state) +} + +func (w *Wizard) handleCheckDependencies(rw http.ResponseWriter, r *http.Request) { + deps := w.checkAllDependencies() + + w.stateMu.Lock() + w.state.Dependencies = deps + w.stateMu.Unlock() + + rw.Header().Set("Content-Type", "application/json") + json.NewEncoder(rw).Encode(deps) +} + +func (w *Wizard) handleWailsConfig(rw http.ResponseWriter, r *http.Request) { + rw.Header().Set("Content-Type", "application/json") + + // Find wails.yaml in current directory or parent directories + configPath := findWailsConfig() + + switch r.Method { + case http.MethodGet: + if configPath == "" { + json.NewEncoder(rw).Encode(nil) + return + } + + data, err := os.ReadFile(configPath) + if err != nil { + json.NewEncoder(rw).Encode(nil) + return + } + + var config WailsConfig + if err := yaml.Unmarshal(data, &config); err != nil { + json.NewEncoder(rw).Encode(nil) + return + } + + json.NewEncoder(rw).Encode(config) + + case http.MethodPost: + var config WailsConfig + if err := json.NewDecoder(r.Body).Decode(&config); err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + if configPath == "" { + configPath = "wails.yaml" + } + + data, err := yaml.Marshal(&config) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + if err := os.WriteFile(configPath, data, 0644); err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + json.NewEncoder(rw).Encode(map[string]string{"status": "saved", "path": configPath}) + + default: + http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func findWailsConfig() string { + dir, err := os.Getwd() + if err != nil { + return "" + } + + for { + configPath := filepath.Join(dir, "wails.yaml") + if _, err := os.Stat(configPath); err == nil { + return configPath + } + + parent := filepath.Dir(dir) + if parent == dir { + break + } + dir = parent + } + + return "" +} + +func (w *Wizard) handleComplete(rw http.ResponseWriter, r *http.Request) { + w.stateMu.RLock() + state := w.state + w.stateMu.RUnlock() + + duration := time.Since(state.StartTime) + + response := map[string]interface{}{ + "status": "complete", + "duration": duration.String(), + } + + rw.Header().Set("Content-Type", "application/json") + json.NewEncoder(rw).Encode(response) + + close(w.done) +} + +func (w *Wizard) handleClose(rw http.ResponseWriter, r *http.Request) { + rw.Header().Set("Content-Type", "application/json") + json.NewEncoder(rw).Encode(map[string]string{"status": "closing"}) + + close(w.shutdown) +} + +// execCommand runs a command and returns its output +func execCommand(name string, args ...string) (string, error) { + cmd := exec.Command(name, args...) + output, err := cmd.Output() + return strings.TrimSpace(string(output)), err +} + +// commandExists checks if a command exists in PATH +func commandExists(name string) bool { + _, err := exec.LookPath(name) + return err == nil +} + +func (w *Wizard) handleDockerStatus(rw http.ResponseWriter, r *http.Request) { + status := w.checkDocker() + + w.dockerMu.Lock() + w.dockerStatus = status + w.dockerMu.Unlock() + + rw.Header().Set("Content-Type", "application/json") + json.NewEncoder(rw).Encode(status) +} + +func (w *Wizard) checkDocker() DockerStatus { + status := DockerStatus{ + ImageName: "wails-cross", + PullStatus: "idle", + } + + // Check if Docker is installed + output, err := execCommand("docker", "--version") + if err != nil { + status.Installed = false + return status + } + + status.Installed = true + // Parse version from "Docker version 24.0.7, build afdd53b" + parts := strings.Split(output, ",") + if len(parts) > 0 { + status.Version = strings.TrimPrefix(strings.TrimSpace(parts[0]), "Docker version ") + } + + // Check if Docker daemon is running + if _, err := execCommand("docker", "info"); err != nil { + status.Running = false + return status + } + status.Running = true + + // Check if wails-cross image exists + imageOutput, err := execCommand("docker", "image", "inspect", "wails-cross") + status.ImageBuilt = err == nil && len(imageOutput) > 0 + + return status +} + +func (w *Wizard) handleDockerBuild(rw http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + w.dockerMu.Lock() + w.dockerStatus.PullStatus = "pulling" + w.dockerStatus.PullProgress = 0 + w.dockerMu.Unlock() + + // Build the Docker image in background + go func() { + // Run: wails3 task setup:docker + cmd := exec.Command("wails3", "task", "setup:docker") + err := cmd.Run() + + w.dockerMu.Lock() + if err != nil { + w.dockerStatus.PullStatus = "error" + w.dockerStatus.PullError = err.Error() + } else { + w.dockerStatus.PullStatus = "complete" + w.dockerStatus.ImageBuilt = true + } + w.dockerStatus.PullProgress = 100 + w.dockerMu.Unlock() + }() + + // Simulate progress updates while building + go func() { + for i := 0; i < 90; i += 5 { + time.Sleep(2 * time.Second) + w.dockerMu.Lock() + if w.dockerStatus.PullStatus != "pulling" { + w.dockerMu.Unlock() + return + } + w.dockerStatus.PullProgress = i + w.dockerMu.Unlock() + } + }() + + rw.Header().Set("Content-Type", "application/json") + json.NewEncoder(rw).Encode(map[string]string{"status": "started"}) +} + +// handleDockerStartBackground checks if Docker is available and starts building in background +// This is called early in the wizard flow to get a head start on the image build +func (w *Wizard) handleDockerStartBackground(rw http.ResponseWriter, r *http.Request) { + rw.Header().Set("Content-Type", "application/json") + + // Check Docker status first + status := w.checkDocker() + + w.dockerMu.Lock() + w.dockerStatus = status + w.dockerMu.Unlock() + + // Only start build if Docker is installed, running, and image not built yet + if !status.Installed || !status.Running || status.ImageBuilt { + json.NewEncoder(rw).Encode(map[string]interface{}{ + "started": false, + "reason": getDockerNotStartedReason(status), + "status": status, + }) + return + } + + // Check if already building + w.dockerMu.RLock() + alreadyBuilding := w.dockerStatus.PullStatus == "pulling" + w.dockerMu.RUnlock() + + if alreadyBuilding { + json.NewEncoder(rw).Encode(map[string]interface{}{ + "started": false, + "reason": "already_building", + "status": status, + }) + return + } + + // Start building in background + w.dockerMu.Lock() + w.dockerStatus.PullStatus = "pulling" + w.dockerStatus.PullProgress = 0 + w.dockerMu.Unlock() + + // Build the Docker image in background + go func() { + cmd := exec.Command("wails3", "task", "setup:docker") + err := cmd.Run() + + w.dockerMu.Lock() + if err != nil { + w.dockerStatus.PullStatus = "error" + w.dockerStatus.PullError = err.Error() + } else { + w.dockerStatus.PullStatus = "complete" + w.dockerStatus.ImageBuilt = true + } + w.dockerStatus.PullProgress = 100 + w.dockerMu.Unlock() + }() + + // Simulate progress updates while building + go func() { + for i := 0; i < 90; i += 5 { + time.Sleep(2 * time.Second) + w.dockerMu.Lock() + if w.dockerStatus.PullStatus != "pulling" { + w.dockerMu.Unlock() + return + } + w.dockerStatus.PullProgress = i + w.dockerMu.Unlock() + } + }() + + json.NewEncoder(rw).Encode(map[string]interface{}{ + "started": true, + "status": status, + }) +} + +func getDockerNotStartedReason(status DockerStatus) string { + if !status.Installed { + return "not_installed" + } + if !status.Running { + return "not_running" + } + if status.ImageBuilt { + return "already_built" + } + return "unknown" +} + +// InstallRequest represents a request to install a dependency +type InstallRequest struct { + Command string `json:"command"` +} + +// InstallResponse represents the result of an install attempt +type InstallResponse struct { + Success bool `json:"success"` + Output string `json:"output"` + Error string `json:"error,omitempty"` +} + +func (w *Wizard) handleInstallDependency(rw http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var req InstallRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + rw.Header().Set("Content-Type", "application/json") + + // Execute the install command + // Split the command into parts + parts := strings.Fields(req.Command) + if len(parts) == 0 { + json.NewEncoder(rw).Encode(InstallResponse{ + Success: false, + Error: "Empty command", + }) + return + } + + cmd := exec.Command(parts[0], parts[1:]...) + output, err := cmd.CombinedOutput() + + if err != nil { + json.NewEncoder(rw).Encode(InstallResponse{ + Success: false, + Output: string(output), + Error: err.Error(), + }) + return + } + + json.NewEncoder(rw).Encode(InstallResponse{ + Success: true, + Output: string(output), + }) +} + +func (w *Wizard) handleDefaults(rw http.ResponseWriter, r *http.Request) { + rw.Header().Set("Content-Type", "application/json") + + switch r.Method { + case http.MethodGet: + defaults, err := LoadGlobalDefaults() + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + // Try to pre-populate author info from git config if empty + if defaults.Author.Name == "" { + if name, err := execCommand("git", "config", "--global", "user.name"); err == nil && name != "" { + defaults.Author.Name = name + } + } + + json.NewEncoder(rw).Encode(defaults) + + case http.MethodPost: + var defaults GlobalDefaults + if err := json.NewDecoder(r.Body).Decode(&defaults); err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + if err := SaveGlobalDefaults(defaults); err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + path, _ := GetDefaultsPath() + json.NewEncoder(rw).Encode(map[string]string{"status": "saved", "path": path}) + + default: + http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed) + } +} diff --git a/v3/internal/setupwizard/wizard_darwin.go b/v3/internal/setupwizard/wizard_darwin.go new file mode 100644 index 000000000..aab27d118 --- /dev/null +++ b/v3/internal/setupwizard/wizard_darwin.go @@ -0,0 +1,135 @@ +//go:build darwin + +package setupwizard + +import ( + "os/exec" + "strconv" + "strings" +) + +func (w *Wizard) checkAllDependencies() []DependencyStatus { + var deps []DependencyStatus + + // Check Xcode Command Line Tools + deps = append(deps, checkXcode()) + + // Check npm (common dependency) + deps = append(deps, checkNpm()) + + // Check Docker (optional) + deps = append(deps, checkDocker()) + + return deps +} + +func checkXcode() DependencyStatus { + dep := DependencyStatus{ + Name: "Xcode Command Line Tools", + Required: true, + } + + path, err := execCommand("xcode-select", "-p") + if err != nil { + dep.Status = "not_installed" + dep.Installed = false + dep.Message = "Run: xcode-select --install" + return dep + } + + dep.Installed = true + dep.Status = "installed" + + // Try to get version + cmd := exec.Command("pkgutil", "--pkg-info=com.apple.pkg.CLTools_Executables") + output, err := cmd.Output() + if err == nil { + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "version:") { + dep.Version = strings.TrimSpace(strings.TrimPrefix(line, "version:")) + break + } + } + } + + _ = path // suppress unused warning + return dep +} + +func checkNpm() DependencyStatus { + dep := DependencyStatus{ + Name: "npm", + Required: true, + } + + version, err := execCommand("npm", "-v") + if err != nil { + dep.Status = "not_installed" + dep.Installed = false + dep.Message = "npm is required. Install Node.js from https://nodejs.org/" + return dep + } + + dep.Version = version + + // Check minimum version (7.0.0) + parts := strings.Split(version, ".") + if len(parts) > 0 { + major, _ := strconv.Atoi(parts[0]) + if major < 7 { + dep.Status = "needs_update" + dep.Installed = true + dep.Message = "npm 7.0.0 or higher is required" + return dep + } + } + + dep.Installed = true + dep.Status = "installed" + return dep +} + +func checkDocker() DependencyStatus { + dep := DependencyStatus{ + Name: "docker", + Required: false, // Optional for cross-compilation + } + + version, err := execCommand("docker", "--version") + if err != nil { + dep.Status = "not_installed" + dep.Installed = false + dep.Message = "Optional - for cross-compilation" + return dep + } + + // Parse version from "Docker version 24.0.7, build afdd53b" + parts := strings.Split(version, ",") + if len(parts) > 0 { + dep.Version = strings.TrimPrefix(strings.TrimSpace(parts[0]), "Docker version ") + } + + // Check if daemon is running + _, err = execCommand("docker", "info") + if err != nil { + dep.Installed = true + dep.Status = "installed" + dep.Message = "Daemon not running" + return dep + } + + // Check for wails-cross image + imageCheck, _ := execCommand("docker", "image", "inspect", "wails-cross") + if imageCheck == "" || strings.Contains(imageCheck, "Error") { + dep.Installed = true + dep.Status = "installed" + dep.Message = "wails-cross image not built" + } else { + dep.Installed = true + dep.Status = "installed" + dep.Message = "Cross-compilation ready" + } + + return dep +} diff --git a/v3/internal/setupwizard/wizard_linux.go b/v3/internal/setupwizard/wizard_linux.go new file mode 100644 index 000000000..c286f504a --- /dev/null +++ b/v3/internal/setupwizard/wizard_linux.go @@ -0,0 +1,139 @@ +//go:build linux + +package setupwizard + +import ( + "strconv" + "strings" + + "github.com/wailsapp/wails/v3/internal/doctor/packagemanager" + "github.com/wailsapp/wails/v3/internal/operatingsystem" +) + +func (w *Wizard) checkAllDependencies() []DependencyStatus { + var deps []DependencyStatus + hasNpm := false + + // Get OS info for package manager detection + info, _ := operatingsystem.Info() + + // Find the package manager + pm := packagemanager.Find(info.ID) + if pm != nil { + // Get platform dependencies from the doctor package + platformDeps, _ := packagemanager.Dependencies(pm) + for _, dep := range platformDeps { + if dep.Name == "npm" { + hasNpm = true + } + status := DependencyStatus{ + Name: dep.Name, + Required: !dep.Optional, + } + + if dep.Installed { + status.Installed = true + status.Status = "installed" + status.Version = dep.Version + } else { + status.Installed = false + status.Status = "not_installed" + status.InstallCommand = dep.InstallCommand + } + + deps = append(deps, status) + } + } + + // Check npm (common dependency) - only if not already added by package manager + if !hasNpm { + deps = append(deps, checkNpm()) + } + + // Check Docker (optional) + deps = append(deps, checkDocker()) + + return deps +} + +func checkNpm() DependencyStatus { + dep := DependencyStatus{ + Name: "npm", + Required: false, // Optional - not strictly required for Go-only projects + } + + version, err := execCommand("npm", "-v") + if err != nil { + dep.Status = "not_installed" + dep.Installed = false + dep.Message = "Required for frontend development" + dep.HelpURL = "https://nodejs.org/" + dep.InstallCommand = "Install Node.js from https://nodejs.org/" + return dep + } + + dep.Version = version + + // Check minimum version (7.0.0) + parts := strings.Split(version, ".") + if len(parts) > 0 { + major, _ := strconv.Atoi(parts[0]) + if major < 7 { + dep.Status = "needs_update" + dep.Installed = true + dep.Message = "npm 7.0.0 or higher recommended" + dep.HelpURL = "https://nodejs.org/" + return dep + } + } + + dep.Installed = true + dep.Status = "installed" + return dep +} + +func checkDocker() DependencyStatus { + dep := DependencyStatus{ + Name: "docker", + Required: false, // Optional for cross-compilation + } + + version, err := execCommand("docker", "--version") + if err != nil { + dep.Status = "not_installed" + dep.Installed = false + dep.Message = "Enables cross-platform builds" + dep.HelpURL = "https://docs.docker.com/get-docker/" + dep.InstallCommand = "Install Docker from https://docs.docker.com/get-docker/" + return dep + } + + // Parse version from "Docker version 24.0.7, build afdd53b" + parts := strings.Split(version, ",") + if len(parts) > 0 { + dep.Version = strings.TrimPrefix(strings.TrimSpace(parts[0]), "Docker version ") + } + + // Check if daemon is running + _, err = execCommand("docker", "info") + if err != nil { + dep.Installed = true + dep.Status = "installed" + dep.Message = "Start Docker to enable cross-compilation" + return dep + } + + // Check for wails-cross image + imageCheck, _ := execCommand("docker", "image", "inspect", "wails-cross") + if imageCheck == "" || strings.Contains(imageCheck, "Error") { + dep.Installed = true + dep.Status = "installed" + dep.Message = "Run 'wails3 task setup:docker' to build cross-compilation image" + } else { + dep.Installed = true + dep.Status = "installed" + dep.Message = "Cross-compilation ready" + } + + return dep +} diff --git a/v3/internal/setupwizard/wizard_windows.go b/v3/internal/setupwizard/wizard_windows.go new file mode 100644 index 000000000..afa2653a4 --- /dev/null +++ b/v3/internal/setupwizard/wizard_windows.go @@ -0,0 +1,142 @@ +//go:build windows + +package setupwizard + +import ( + "os" + "path/filepath" + "strconv" + "strings" +) + +func (w *Wizard) checkAllDependencies() []DependencyStatus { + var deps []DependencyStatus + + // Check WebView2 Runtime + deps = append(deps, checkWebView2()) + + // Check npm (common dependency) + deps = append(deps, checkNpm()) + + // Check Docker (optional) + deps = append(deps, checkDocker()) + + return deps +} + +func checkWebView2() DependencyStatus { + dep := DependencyStatus{ + Name: "WebView2 Runtime", + Required: true, + } + + // Check common installation paths + paths := []string{ + filepath.Join(os.Getenv("PROGRAMFILES(X86)"), "Microsoft", "EdgeWebView", "Application"), + filepath.Join(os.Getenv("LOCALAPPDATA"), "Microsoft", "EdgeWebView", "Application"), + filepath.Join(os.Getenv("PROGRAMFILES"), "Microsoft", "EdgeWebView", "Application"), + } + + for _, path := range paths { + if info, err := os.Stat(path); err == nil && info.IsDir() { + dep.Installed = true + dep.Status = "installed" + + // Try to get version from directory name + entries, _ := os.ReadDir(path) + for _, entry := range entries { + if entry.IsDir() { + name := entry.Name() + // Version directories look like "120.0.2210.91" + if len(name) > 0 && name[0] >= '0' && name[0] <= '9' { + dep.Version = name + break + } + } + } + return dep + } + } + + dep.Status = "not_installed" + dep.Installed = false + dep.Message = "Download from Microsoft Edge WebView2" + return dep +} + +func checkNpm() DependencyStatus { + dep := DependencyStatus{ + Name: "npm", + Required: true, + } + + version, err := execCommand("npm", "-v") + if err != nil { + dep.Status = "not_installed" + dep.Installed = false + dep.Message = "npm is required. Install Node.js from https://nodejs.org/" + return dep + } + + dep.Version = version + + // Check minimum version (7.0.0) + parts := strings.Split(version, ".") + if len(parts) > 0 { + major, _ := strconv.Atoi(parts[0]) + if major < 7 { + dep.Status = "needs_update" + dep.Installed = true + dep.Message = "npm 7.0.0 or higher is required" + return dep + } + } + + dep.Installed = true + dep.Status = "installed" + return dep +} + +func checkDocker() DependencyStatus { + dep := DependencyStatus{ + Name: "docker", + Required: false, // Optional for cross-compilation + } + + version, err := execCommand("docker", "--version") + if err != nil { + dep.Status = "not_installed" + dep.Installed = false + dep.Message = "Optional - for cross-compilation" + return dep + } + + // Parse version from "Docker version 24.0.7, build afdd53b" + parts := strings.Split(version, ",") + if len(parts) > 0 { + dep.Version = strings.TrimPrefix(strings.TrimSpace(parts[0]), "Docker version ") + } + + // Check if daemon is running + _, err = execCommand("docker", "info") + if err != nil { + dep.Installed = true + dep.Status = "installed" + dep.Message = "Daemon not running" + return dep + } + + // Check for wails-cross image + imageCheck, _ := execCommand("docker", "image", "inspect", "wails-cross") + if imageCheck == "" || strings.Contains(imageCheck, "Error") { + dep.Installed = true + dep.Status = "installed" + dep.Message = "wails-cross image not built" + } else { + dep.Installed = true + dep.Status = "installed" + dep.Message = "Cross-compilation ready" + } + + return dep +} diff --git a/v3/internal/signal/signal.go b/v3/internal/signal/signal.go new file mode 100644 index 000000000..8ac9821d0 --- /dev/null +++ b/v3/internal/signal/signal.go @@ -0,0 +1,50 @@ +package signal + +import ( + "fmt" + "log/slog" + "os" + "os/signal" + "syscall" +) + +type SignalHandler struct { + cleanup func() + ExitMessage func(sig os.Signal) string + MaxSignal int + Logger *slog.Logger + LogLevel slog.Level +} + +func NewSignalHandler(cleanup func()) *SignalHandler { + return &SignalHandler{ + cleanup: cleanup, + ExitMessage: func(sig os.Signal) string { return fmt.Sprintf("Received signal: %v. Quitting...\n", sig) }, + MaxSignal: 3, + Logger: slog.New(slog.NewTextHandler(os.Stderr, nil)), + LogLevel: slog.LevelInfo, + } +} + +func (s *SignalHandler) Start() { + ctrlC := make(chan os.Signal, s.MaxSignal) + signal.Notify(ctrlC, os.Interrupt, syscall.SIGTERM) + + go func() { + for i := 1; i <= s.MaxSignal; i++ { + sig := <-ctrlC + + if i == 1 { + s.Logger.Info(s.ExitMessage(sig)) + s.cleanup() + break + } else if i < s.MaxSignal { + s.Logger.Info(fmt.Sprintf("Received signal: %v. Press CTRL+C %d more times to force quit...\n", sig, s.MaxSignal-i)) + continue + } else { + s.Logger.Info(fmt.Sprintf("Received signal: %v. Force quitting...\n", sig)) + os.Exit(1) + } + } + }() +} diff --git a/v3/internal/sliceutil/sliceutil.go b/v3/internal/sliceutil/sliceutil.go new file mode 100644 index 000000000..5023d6110 --- /dev/null +++ b/v3/internal/sliceutil/sliceutil.go @@ -0,0 +1,39 @@ +// Package sliceutil provides generic utility functions not available in stdlib. +// For most slice operations, use the standard library "slices" package directly. +// This package only contains functions that have no stdlib equivalent. +package sliceutil + +// Unique returns a new slice with duplicate elements removed. +// Preserves the order of first occurrence. +// The original slice is not modified. +// +// Unique returns a new slice containing the first occurrence of each element from the input slice, preserving their original order. +// If the input slice is nil, Unique returns nil. +// The original slice is not modified. +func Unique[T comparable](slice []T) []T { + if slice == nil { + return nil + } + seen := make(map[T]struct{}, len(slice)) + result := make([]T, 0, len(slice)) + for _, v := range slice { + if _, ok := seen[v]; !ok { + seen[v] = struct{}{} + result = append(result, v) + } + } + return result +} + +// FindMapKey returns the first key in map m whose value equals val. +// FindMapKey returns the first key in m whose value equals val. +// If no such key exists it returns the zero value of K and false. If multiple keys map to val, the returned key depends on Go's map iteration order. +func FindMapKey[K comparable, V comparable](m map[K]V, val V) (K, bool) { + for k, v := range m { + if v == val { + return k, true + } + } + var zero K + return zero, false +} \ No newline at end of file diff --git a/v3/internal/sliceutil/sliceutil_test.go b/v3/internal/sliceutil/sliceutil_test.go new file mode 100644 index 000000000..1b10cc123 --- /dev/null +++ b/v3/internal/sliceutil/sliceutil_test.go @@ -0,0 +1,172 @@ +package sliceutil + +import ( + "reflect" + "testing" +) + +func TestUnique(t *testing.T) { + tests := []struct { + name string + slice []int + want []int + }{ + { + name: "no duplicates", + slice: []int{1, 2, 3}, + want: []int{1, 2, 3}, + }, + { + name: "with duplicates", + slice: []int{1, 2, 2, 3, 3, 3}, + want: []int{1, 2, 3}, + }, + { + name: "all duplicates", + slice: []int{1, 1, 1}, + want: []int{1}, + }, + { + name: "preserves order", + slice: []int{3, 1, 2, 1, 3, 2}, + want: []int{3, 1, 2}, + }, + { + name: "single element", + slice: []int{1}, + want: []int{1}, + }, + { + name: "empty slice", + slice: []int{}, + want: []int{}, + }, + { + name: "nil slice", + slice: nil, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Unique(tt.slice) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Unique() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnique_Strings(t *testing.T) { + slice := []string{"a", "b", "a", "c", "b"} + got := Unique(slice) + want := []string{"a", "b", "c"} + if !reflect.DeepEqual(got, want) { + t.Errorf("Unique() = %v, want %v", got, want) + } +} + +func TestUnique_DoesNotModifyOriginal(t *testing.T) { + original := []int{1, 2, 2, 3} + originalCopy := make([]int, len(original)) + copy(originalCopy, original) + + _ = Unique(original) + + if !reflect.DeepEqual(original, originalCopy) { + t.Errorf("Unique() modified original slice: got %v, want %v", original, originalCopy) + } +} + +func TestFindMapKey(t *testing.T) { + tests := []struct { + name string + m map[string]int + val int + wantKey string + wantFound bool + }{ + { + name: "find existing value", + m: map[string]int{"a": 1, "b": 2, "c": 3}, + val: 2, + wantKey: "b", + wantFound: true, + }, + { + name: "value not found", + m: map[string]int{"a": 1, "b": 2}, + val: 3, + wantKey: "", + wantFound: false, + }, + { + name: "empty map", + m: map[string]int{}, + val: 1, + wantKey: "", + wantFound: false, + }, + { + name: "nil map", + m: nil, + val: 1, + wantKey: "", + wantFound: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotKey, gotFound := FindMapKey(tt.m, tt.val) + if gotFound != tt.wantFound { + t.Errorf("FindMapKey() found = %v, want %v", gotFound, tt.wantFound) + } + if gotFound && gotKey != tt.wantKey { + t.Errorf("FindMapKey() key = %v, want %v", gotKey, tt.wantKey) + } + }) + } +} + +func TestFindMapKey_DuplicateValues(t *testing.T) { + // When multiple keys have the same value, any matching key is acceptable + m := map[string]int{"a": 1, "b": 1, "c": 2} + key, found := FindMapKey(m, 1) + if !found { + t.Error("FindMapKey() should find a key") + } + if key != "a" && key != "b" { + t.Errorf("FindMapKey() = %v, want 'a' or 'b'", key) + } +} + +func TestFindMapKey_IntKeys(t *testing.T) { + m := map[int]string{1: "one", 2: "two", 3: "three"} + key, found := FindMapKey(m, "two") + if !found || key != 2 { + t.Errorf("FindMapKey() = (%v, %v), want (2, true)", key, found) + } +} + +// Benchmarks + +func BenchmarkUnique(b *testing.B) { + slice := []int{1, 2, 3, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 10} + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Unique(slice) + } +} + +func BenchmarkFindMapKey(b *testing.B) { + m := make(map[string]int) + for i := 0; i < 100; i++ { + m[string(rune('a'+i))] = i + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindMapKey(m, 50) + } +} diff --git a/v3/internal/templates/_common/README.md b/v3/internal/templates/_common/README.md new file mode 100644 index 000000000..ad12c3f40 --- /dev/null +++ b/v3/internal/templates/_common/README.md @@ -0,0 +1,59 @@ +# Welcome to Your New Wails3 Project! + +Congratulations on generating your Wails3 application! This README will guide you through the next steps to get your project up and running. + +## Getting Started + +1. Navigate to your project directory in the terminal. + +2. To run your application in development mode, use the following command: + + ``` + wails3 dev + ``` + + This will start your application and enable hot-reloading for both frontend and backend changes. + +3. To build your application for production, use: + + ``` + wails3 build + ``` + + This will create a production-ready executable in the `build` directory. + +## Exploring Wails3 Features + +Now that you have your project set up, it's time to explore the features that Wails3 offers: + +1. **Check out the examples**: The best way to learn is by example. Visit the `examples` directory in the `v3/examples` directory to see various sample applications. + +2. **Run an example**: To run any of the examples, navigate to the example's directory and use: + + ``` + go run . + ``` + + Note: Some examples may be under development during the alpha phase. + +3. **Explore the documentation**: Visit the [Wails3 documentation](https://v3.wails.io/) for in-depth guides and API references. + +4. **Join the community**: Have questions or want to share your progress? Join the [Wails Discord](https://discord.gg/JDdSxwjhGf) or visit the [Wails discussions on GitHub](https://github.com/wailsapp/wails/discussions). + +## Project Structure + +Take a moment to familiarize yourself with your project structure: + +- `frontend/`: Contains your frontend code (HTML, CSS, JavaScript/TypeScript) +- `main.go`: The entry point of your Go backend +- `app.go`: Define your application structure and methods here +- `wails.json`: Configuration file for your Wails project + +## Next Steps + +1. Modify the frontend in the `frontend/` directory to create your desired UI. +2. Add backend functionality in `main.go`. +3. Use `wails3 dev` to see your changes in real-time. +4. When ready, build your application with `wails3 build`. + +Happy coding with Wails3! If you encounter any issues or have questions, don't hesitate to consult the documentation or reach out to the Wails community. diff --git a/v3/internal/templates/_common/Taskfile.tmpl.yml b/v3/internal/templates/_common/Taskfile.tmpl.yml new file mode 100644 index 000000000..acf87581f --- /dev/null +++ b/v3/internal/templates/_common/Taskfile.tmpl.yml @@ -0,0 +1,60 @@ +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + ios: ./build/ios/Taskfile.yml + android: ./build/android/Taskfile.yml + +vars: + APP_NAME: "{{.ProjectName}}" + BIN_DIR: "bin" + VITE_PORT: {{ "'{{.WAILS_VITE_PORT | default 9245}}'" }} + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{ "{{OS}}" }}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{ "{{OS}}" }}:package" + + run: + summary: Runs the application + cmds: + - task: "{{ "{{OS}}" }}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{ "{{.VITE_PORT}}" }} + + setup:docker: + summary: Builds Docker image for cross-compilation (~800MB download) + cmds: + - task: common:setup:docker + + build:server: + summary: Builds the application in server mode (no GUI, HTTP server only) + cmds: + - task: common:build:server + + run:server: + summary: Runs the application in server mode + cmds: + - task: common:run:server + + build:docker: + summary: Builds a Docker image for server mode deployment + cmds: + - task: common:build:docker + + run:docker: + summary: Builds and runs the Docker image + cmds: + - task: common:run:docker diff --git a/v3/internal/templates/_common/frontend/Inter Font License.txt b/v3/internal/templates/_common/frontend/Inter Font License.txt new file mode 100644 index 000000000..b525cbf3a --- /dev/null +++ b/v3/internal/templates/_common/frontend/Inter Font License.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/v3/internal/templates/_common/gitignore.tmpl b/v3/internal/templates/_common/gitignore.tmpl new file mode 100644 index 000000000..ba8194ab6 --- /dev/null +++ b/v3/internal/templates/_common/gitignore.tmpl @@ -0,0 +1,6 @@ +.task +bin +frontend/dist +frontend/node_modules +build/linux/appimage/build +build/windows/nsis/MicrosoftEdgeWebview2Setup.exe \ No newline at end of file diff --git a/v3/internal/templates/_common/go.mod.tmpl b/v3/internal/templates/_common/go.mod.tmpl new file mode 100644 index 000000000..f33ac683a --- /dev/null +++ b/v3/internal/templates/_common/go.mod.tmpl @@ -0,0 +1,51 @@ +module {{.ModulePath}} + +go 1.24 + +require github.com/wailsapp/wails/v3 {{.WailsVersion}} + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.5 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/cloudflare/circl v1.6.0 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/ebitengine/purego v0.8.2 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.13.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/lmittmann/tint v1.0.7 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/wailsapp/go-webview2 v1.0.19 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) +{{if .LocalModulePath}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/_common/go.sum.tmpl b/v3/internal/templates/_common/go.sum.tmpl new file mode 100644 index 000000000..977b11504 --- /dev/null +++ b/v3/internal/templates/_common/go.sum.tmpl @@ -0,0 +1,146 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= +github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +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/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= +github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= +github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU= +github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/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-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/internal/templates/_common/greetservice.go b/v3/internal/templates/_common/greetservice.go new file mode 100644 index 000000000..8972c39cd --- /dev/null +++ b/v3/internal/templates/_common/greetservice.go @@ -0,0 +1,7 @@ +package main + +type GreetService struct{} + +func (g *GreetService) Greet(name string) string { + return "Hello " + name + "!" +} diff --git a/v3/internal/templates/_common/main.go.tmpl b/v3/internal/templates/_common/main.go.tmpl new file mode 100644 index 000000000..2e81bf2fb --- /dev/null +++ b/v3/internal/templates/_common/main.go.tmpl @@ -0,0 +1,84 @@ +package main + +import ( + "embed" + _ "embed" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// Wails uses Go's `embed` package to embed the frontend files into the binary. +// Any files in the frontend/dist folder will be embedded into the binary and +// made available to the frontend. +// See https://pkg.go.dev/embed for more information. + +//go:embed all:frontend/dist +var assets embed.FS + +func init() { + // Register a custom event whose associated data type is string. + // This is not required, but the binding generator will pick up registered events + // and provide a strongly typed JS/TS API for them. + application.RegisterEvent[string]("time") +} + +// main function serves as the application's entry point. It initializes the application, creates a window, +// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and +// logs any error that might occur. +func main() { + + // Create a new Wails application by providing the necessary options. + // Variables 'Name' and 'Description' are for application metadata. + // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files. + // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances. + // 'Mac' options tailor the application when running an macOS. + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a new window with the necessary options. + // 'Title' is the title of the window. + // 'Mac' options tailor the window when running on macOS. + // 'BackgroundColour' is the background colour of the window. + // 'URL' is the URL that will be loaded into the webview. + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + BackgroundColour: application.NewRGB(27, 38, 54), + URL: "/", + }) + + // Create a goroutine that emits an event containing the current time every second. + // The frontend can listen to this event and update the UI accordingly. + go func() { + for { + now := time.Now().Format(time.RFC1123) + app.Event.Emit("time", now) + time.Sleep(time.Second) + } + }() + + // Run the application. This blocks until the application has been exited. + err := app.Run() + + // If an error occurred while running the application, log it and exit. + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/base/NEXTSTEPS.md b/v3/internal/templates/base/NEXTSTEPS.md new file mode 100644 index 000000000..6b2e29a08 --- /dev/null +++ b/v3/internal/templates/base/NEXTSTEPS.md @@ -0,0 +1,3 @@ +# Next Steps + +For a full guide on how to create templates, see [Creating Custom Templates](https://v3.wails.io/guides/custom-templates). \ No newline at end of file diff --git a/v3/internal/templates/base/frontend/.gitignore b/v3/internal/templates/base/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/base/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/base/frontend/index.html b/v3/internal/templates/base/frontend/index.html new file mode 100644 index 000000000..ec262d00a --- /dev/null +++ b/v3/internal/templates/base/frontend/index.html @@ -0,0 +1,35 @@ + + + + + + + + Wails App + + +
        + +

        Wails + Javascript

        +
        +
        Please enter your name below 👇
        +
        + + +
        +
        + +
        + + + diff --git a/v3/internal/templates/base/frontend/main.js.tmpl b/v3/internal/templates/base/frontend/main.js.tmpl new file mode 100644 index 000000000..98b43c825 --- /dev/null +++ b/v3/internal/templates/base/frontend/main.js.tmpl @@ -0,0 +1,21 @@ +import {Events} from "@wailsio/runtime"; +import {GreetService} from "./bindings/{{js .ModulePath}}"; + +const resultElement = document.getElementById('result'); +const timeElement = document.getElementById('time'); + +window.doGreet = () => { + let name = document.getElementById('name').value; + if (!name) { + name = 'anonymous'; + } + GreetService.Greet(name).then((result) => { + resultElement.innerText = result; + }).catch((err) => { + console.log(err); + }); +} + +Events.On('time', (time) => { + timeElement.innerText = time.data; +}); diff --git a/v3/internal/templates/base/frontend/package.json b/v3/internal/templates/base/frontend/package.json new file mode 100644 index 000000000..9ae87549e --- /dev/null +++ b/v3/internal/templates/base/frontend/package.json @@ -0,0 +1,16 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^5.0.0", + "@wailsio/runtime": "latest" + } +} \ No newline at end of file diff --git a/v3/internal/templates/ios/frontend/.gitignore b/v3/internal/templates/ios/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/ios/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/ios/frontend/index.html b/v3/internal/templates/ios/frontend/index.html new file mode 100644 index 000000000..0541019f1 --- /dev/null +++ b/v3/internal/templates/ios/frontend/index.html @@ -0,0 +1,36 @@ + + + + + + + + + Wails App + + +
        + +

        Wails + Javascript

        +
        +
        Please enter your name below 👇
        +
        + + +
        +
        + +
        + + + diff --git a/v3/internal/templates/ios/frontend/main.js b/v3/internal/templates/ios/frontend/main.js new file mode 100644 index 000000000..442ca8474 --- /dev/null +++ b/v3/internal/templates/ios/frontend/main.js @@ -0,0 +1,24 @@ +import {GreetService} from "./bindings/changeme"; +import {Events} from "@wailsio/runtime"; + +const resultElement = document.getElementById('result'); +const timeElement = document.getElementById('time'); + +window.doGreet = () => { + let name = document.getElementById('name').value; + if (!name) { + name = 'anonymous'; + } + GreetService.Greet(name).then((result) => { + resultElement.innerText = result; + }).catch((err) => { + console.log(err); + }); +} + +Events.On('time', (payload) => { + // payload may be a plain value or an object with a `data` field depending on emitter/runtime + const value = (payload && typeof payload === 'object' && 'data' in payload) ? payload.data : payload; + console.log('[frontend] time event:', payload, '->', value); + timeElement.innerText = value; +}); diff --git a/v3/internal/templates/ios/frontend/package.json b/v3/internal/templates/ios/frontend/package.json new file mode 100644 index 000000000..0a118e984 --- /dev/null +++ b/v3/internal/templates/ios/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "vite": "^5.0.0" + } +} diff --git a/v3/internal/templates/ios/frontend/public/Inter-Medium.ttf b/v3/internal/templates/ios/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/ios/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/ios/frontend/public/javascript.svg b/v3/internal/templates/ios/frontend/public/javascript.svg new file mode 100644 index 000000000..f9abb2b72 --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/ios/frontend/public/puppertino/LICENSE b/v3/internal/templates/ios/frontend/public/puppertino/LICENSE new file mode 100644 index 000000000..ed9065e06 --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/puppertino/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Edgar Pérez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/v3/internal/templates/ios/frontend/public/puppertino/css/actions.css b/v3/internal/templates/ios/frontend/public/puppertino/css/actions.css new file mode 100644 index 000000000..22a9d5a13 --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/puppertino/css/actions.css @@ -0,0 +1,149 @@ +:root { + --font: -apple-system, "Inter", sans-serif; + --primary-col-ac: #0f75f5; + --p-modal-bg: rgba(255, 255, 255, 0.8); + --p-modal-bd-color: rgba(0,0,0,.1); + --p-modal-fallback-color: rgba(255,255,255,.95); + --p-actions-static-color: #555761; +} + +.p-modal-opened { + overflow: hidden; +} + +.p-action-background{ + background: rgba(0, 0, 0, 0.7); + height: 100vh; + left: 0; + opacity: 0; + pointer-events: none; + position: fixed; + top: 0; + transition: 0.3s; + width: 100vw; + z-index: 5; +} + +.p-action-background.nowactive { + opacity: 1; + pointer-events: auto; +} + + +.p-action-big-container{ + position:fixed; + width: 100%; + box-sizing: border-box; + padding: 1rem 5vw; + bottom:0; +} + +.p-action-container{ + background: var(--p-modal-bg); + display:block; + margin:auto; + margin-bottom: 10px; + border-radius: 10px; + max-width: 700px; +} + +.p-action-big-container .p-action-container:first-child{ + margin-bottom:10px; +} + +.p-action--intern{ + width: 100%; + display:block; + margin:auto; + font-size: 1rem; + font-weight: 600; + text-align:center; + padding: 15px 0; + border: 0; + border-bottom: 1px solid #bfbfbf; + color: #0f75f5; + text-decoration:none; + background-color: transparent; +} + +.p-action-destructive{ + color: #c6262e; +} + +.p-action-neutral{ + color: var(--p-actions-static-color); +} + +.p-action-cancel, .p-action-container a:last-child{ + border-bottom:none; +} + +.p-action-cancel{ + font-weight:bold; +} + +.p-action-icon{ + position:relative; +} +.p-action-icon svg, .p-action-icon img{ + position:absolute; + left:5%; + top:50%; + transform:translateY(-50%); +} + +.p-action-icon-inline{ + text-align: left; + display: flex; + align-items: center; +} + +.p-action-icon-inline svg, .p-action-icon-inline img{ + margin-left: 5%; + margin-right: 3%; +} + +.p-action-title{ + padding: 30px 15px; + border-bottom: 1px solid #bfbfbf; +} + +.p-action-title--intern,.p-action-text{ + margin:0; + color:var(--p-actions-static-color); +} + +.p-action-title--intern{ + margin-bottom: .3rem; +} + +@supports not (backdrop-filter: blur(10px)) { + .p-action-container { + background: var(--p-modal-fallback-color); + } +} + +.p-action-big-container{ + -webkit-transform: translateY(30%); + transform: translateY(30%); + opacity: 0; + transition: opacity 0.4s, transform 0.4s; + transition-timing-function: ease; + pointer-events: none; +} + +.p-action-big-container.active { + -webkit-transform: translateY(0); + transform: translateY(0); + opacity: 1; + pointer-events: all; +} + + +.p-action-big-container.active .p-action-container { + backdrop-filter: saturate(180%) blur(10px); +} + +.p-action-big-container[aria-hidden="true"] .p-action--intern { + display: none; +} diff --git a/v3/internal/templates/ios/frontend/public/puppertino/css/buttons.css b/v3/internal/templates/ios/frontend/public/puppertino/css/buttons.css new file mode 100644 index 000000000..4950b0053 --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/puppertino/css/buttons.css @@ -0,0 +1,158 @@ +@charset "UTF-8"; +:root{ + --p-btn-border: #cacaca; + --p-btn-def-bg: #FFFFFF; + --p-btn-def-col: #000000; + --p-btn-dir-col: #242424; + --p-prim-text-col: #f5f5f5; + --p-btn-scope-unactive: #212136; + --p-btn-scope-action: #212136; +} + +.p-btn { + background: var(--p-btn-def-bg); + border: 1px solid var(--p-btn-border); + border-radius: 10px; + color: var(--p-btn-def-col); + display: inline-block; + font-family: -apple-system, "Inter", sans-serif; + font-size: 1.1rem; + margin: .7rem; + padding: .4rem 1.2rem; + text-decoration: none; + text-align: center; + box-shadow: 0 1px 0.375px rgba(0, 0, 0, 0.05), 0 0.25px 0.375px rgba(0, 0, 0, 0.15); + user-select: none; + cursor: pointer; +} +.p-btn:focus{ + outline: 2px solid #64baff; +} +.p-btn.p-btn-block{ + display: block; +} +.p-btn.p-btn-sm { + padding: .3rem 1.1rem; + font-size: 1rem; +} +.p-btn.p-btn-md { + padding: .8rem 2.4rem; + font-size: 1.6rem; +} +.p-btn.p-btn-lg { + padding: 1.2rem 2.8rem; + font-size: 1.8rem; +} +.p-btn-destructive{ + color: #FF3B30; +} +.p-btn-mob{ + padding: 10px 40px; + background: #227bec; + color: #fff; + border: 0; + box-shadow: inset 0 1px 1px rgb(255 255 255 / 41%), 0px 2px 3px -2px rgba(0,0,0,.3); +} +.p-btn[disabled], +.p-btn:disabled, +.p-btn-disabled{ + filter:contrast(0.5) grayscale(.5) opacity(.8); + cursor: not-allowed; + box-shadow: none; + pointer-events: none; +} + +.p-prim-col { + position: relative; + background: #007AFF; + border: none; + box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.41), 0px 2px 3px -2px rgba(0, 0, 0, 0.3); + color: var(--p-prim-text-col); + overflow: hidden; /* Ensure the ::before element doesn't overflow */ +} + +.p-prim-col:before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(180deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%); + opacity: 0.17; + pointer-events: none; +} + +.p-btn.p-prim-col:active { + background: #0f75f5; +} + +.p-btn-more::after { + content: "..."; +} + +.p-btn-round { + border: 0; + border-radius: 50px; + box-shadow: inset 0 1px 1px rgb(255 255 255 / 41%); + padding: 10px 30px; +} + +.p-btn-icon { + align-items: center; + background: var(--p-btn-def-bg); + border: 2px solid currentColor; + border-radius: 50%; + color: #0f75f5; + display: inline-flex; + font-weight: 900; + height: 40px; + width: 40px; + justify-content: center; + margin: 5px; + text-align: center; + text-decoration: none; + box-sizing: border-box; + user-select: none; + vertical-align: bottom; +} + +.p-btn-icon.p-btn-icon-no-border{ + border: 0px; +} + +.p-btn-scope { + background: #8e8e8e; + color: #fff; + margin: 5px; + padding: 2px 20px; + box-shadow: none; +} +.p-btn-scope-unactive { + background: transparent; + border-color: transparent; + color: var(--p-btn-scope-unactive); + transition: border-color 0.2s; +} +.p-btn-scope-unactive:hover { + border-color: var(--p-btn-border); +} + +.p-btn-scope-outline { + background: transparent; + color: var(--p-btn-scope-action); + box-shadow: none; +} + +.p-btn-outline { + background: none; + border-color: currentColor; + box-shadow: none; +} + +.p-btn-outline-dash { + background: none; + border-color: currentColor; + border-style: dashed; + box-shadow: none; +} diff --git a/v3/internal/templates/ios/frontend/public/puppertino/css/cards.css b/v3/internal/templates/ios/frontend/public/puppertino/css/cards.css new file mode 100644 index 000000000..b4fa2e397 --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/puppertino/css/cards.css @@ -0,0 +1,55 @@ +:root{ + --p-color-card: #1a1a1a; + --p-bg-card: #fff; + --p-bd-card: #c5c5c55e; +} +.p-card { + background: var(--p-bg-card); + border: 1px solid var(--p-bd-card); + color: var(--p-color-card); + display: block; + margin: 15px; + margin-left:7.5px; + margin-right:7.5px; + text-decoration: none; + border-radius: 25px; + padding: 20px 0px; + transition: .3s ease; + box-shadow: 0 0.5px 1px rgba(0, 0, 0, 0.1); +} +.p-card-image > img { + border-bottom: 3px solid var(--accent-article); + display: block; + margin: auto; + width: 100%; +} +.p-card-tags { + display: flex; + overflow: hidden; + position: relative; + width: 100%; +} +.p-card-tags::before { + background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 75%, white 100%); + content: ""; + height: 100%; + position: absolute; + right: 0; + top: 0; + width: 30%; +} +.p-card-title { + font-size: 2rem; + margin-bottom: 15px; + margin-top: 15px; +} +.p-card-content { + padding: 15px; + padding-top: 15px; +} +.p-card-text { + font-size: 17px; + margin-bottom: 10px; + margin-left: 10px; + margin-top: 0; +} diff --git a/v3/internal/templates/ios/frontend/public/puppertino/css/color_palette.css b/v3/internal/templates/ios/frontend/public/puppertino/css/color_palette.css new file mode 100644 index 000000000..33a66b91c --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/puppertino/css/color_palette.css @@ -0,0 +1,917 @@ +:root{ +--p-strawberry: #c6262e; +--p-strawberry-100: #ff8c82; +--p-strawberry-300: #ed5353; +--p-strawberry-500: #c6262e; +--p-strawberry-700: #a10705; +--p-strawberry-900: #7a0000; + +--p-orange: #f37329; +--p-orange-100: #ffc27d; +--p-orange-300: #ffa154; +--p-orange-500: #f37329; +--p-orange-700: #cc3b02; +--p-orange-900: #a62100; + + +--p-banana: #f9c440; +--p-banana-100: #fff394; +--p-banana-300: #ffe16b; +--p-banana-500: #f9c440; +--p-banana-700: #d48e15; +--p-banana-900: #ad5f00; + +--p-lime: #68b723; +--p-lime-100: #d1ff82; +--p-lime-300: #9bdb4d; +--p-lime-500: #68b723; +--p-lime-700: #3a9104; +--p-lime-900: #206b00; + +--p-mint: #28bca3; +--p-mint-100: #89ffdd; +--p-mint-300: #43d6b5; +--p-mint-500: #28bca3; +--p-mint-700: #0e9a83; +--p-mint-900: #007367; + + +--p-blueberry: #3689e6; +--p-blueberry-100: #8cd5ff; +--p-blueberry-300: #64baff; +--p-blueberry-500: #3689e6; +--p-blueberry-700: #0d52bf; +--p-blueberry-900: #002e99; + +--p-grape: #a56de2; +--p-grape-100: #e4c6fa; +--p-grape-300: #cd9ef7; +--p-grape-500: #a56de2; +--p-grape-700: #7239b3; +--p-grape-900: #452981; + +--p-bubblegum: #de3e80; +--p-bubblegum-100: #fe9ab8; +--p-bubblegum-300: #f4679d; +--p-bubblegum-500: #de3e80; +--p-bubblegum-700: #bc245d; +--p-bubblegum-900: #910e38; + + +--p-cocoa: #715344; +--p-cocoa-100: #a3907c; +--p-cocoa-300: #8a715e; +--p-cocoa-500: #715344; +--p-cocoa-700: #57392d; +--p-cocoa-900: #3d211b; + +--p-silver: #abacae; +--p-silver-100: #fafafa; +--p-silver-300: #d4d4d4; +--p-silver-500: #abacae; +--p-silver-700: #7e8087; +--p-silver-900: #555761; + +--p-slate: #485a6c; +--p-slate-100: #95a3ab; +--p-slate-300: #667885; +--p-slate-500: #485a6c; +--p-slate-700: #273445; +--p-slate-900: #0e141f; + + +--p-dark: #333; +--p-dark-100: #666; +--p-dark-300: #4d4d4d; +--p-dark-500: #333; +--p-dark-700: #1a1a1a; +--p-dark-900: #000; + + +--p-apple-red: rgb(255, 59 , 48); +--p-apple-red-dark: rgb(255, 69 , 58); +--p-apple-orange: rgb(255,149,0); +--p-apple-orange-dark: rgb(255,159,10); +--p-apple-yellow: rgb(255,204,0); +--p-apple-yellow-dark: rgb(255,214,10); +--p-apple-green: rgb(40,205,65); +--p-apple-green-dark: rgb(40,215,75); +--p-apple-mint: rgb(0,199,190); +--p-apple-mint-dark: rgb(102,212,207); +--p-apple-teal: rgb(89, 173, 196); +--p-apple-teal-dark: rgb(106, 196, 220); +--p-apple-cyan: rgb(85,190,240); +--p-apple-cyan-dark: rgb(90,200,245); +--p-apple-blue: rgb(0, 122, 255); +--p-apple-blue-dark: rgb(10, 132, 255); +--p-apple-indigo: rgb(88, 86, 214); +--p-apple-indigo-dark: rgb(94, 92, 230); +--p-apple-purple: rgb(175, 82, 222); +--p-apple-purple-dark: rgb(191, 90, 242); +--p-apple-pink: rgb(255, 45, 85); +--p-apple-pink-dark: rgb(255, 55, 95); +--p-apple-brown: rgb(162, 132, 94); +--p-apple-brown-dark: rgb(172, 142, 104); +--p-apple-gray: rgb(142, 142, 147); +--p-apple-gray-dark: rgb(152, 152, 157); + +} + + +/* +APPLE OFFICIAL COLORS +*/ + +.p-apple-red{ + background: rgb(255, 59 , 48); +} + +.p-apple-red-dark{ + background: rgb(255, 69 , 58); +} + +.p-apple-orange{ + background: rgb(255,149,0); +} + +.p-apple-orange-dark{ + background: rgb(255,159,10); +} + +.p-apple-yellow{ + background: rgb(255,204,0); +} + +.p-apple-yellow-dark{ + background: rgb(255,214,10); +} + +.p-apple-green{ + background: rgb(40,205,65); +} + +.p-apple-green-dark{ + background: rgb(40,215,75); +} + +.p-apple-mint{ + background: rgb(0,199,190); +} + +.p-apple-mint-dark{ + background: rgb(102,212,207); +} + +.p-apple-teal{ + background: rgb(89, 173, 196); +} + +.p-apple-teal-dark{ + background: rgb(106, 196, 220); +} + +.p-apple-cyan{ + background: rgb(85,190,240); +} + +.p-apple-cyan-dark{ + background: rgb(90,200,245); +} + +.p-apple-blue{ + background: rgb(0, 122, 255); +} + +.p-apple-blue-dark{ + background: rgb(10, 132, 255); +} + +.p-apple-indigo{ + background: rgb(88, 86, 214); +} + +.p-apple-indigo-dark{ + background: rgb(94, 92, 230); +} + +.p-apple-purple{ + background: rgb(175, 82, 222); +} + +.p-apple-purple-dark{ + background: rgb(191, 90, 242); +} + +.p-apple-pink{ + background: rgb(255, 45, 85); +} + +.p-apple-pink-dark{ + background: rgb(255, 55, 95); +} + +.p-apple-brown{ + background: rgb(162, 132, 94); +} + +.p-apple-brown-dark{ + background: rgb(172, 142, 104); +} + +.p-apple-gray{ + background: rgb(142, 142, 147); +} + +.p-apple-gray-dark{ + background: rgb(152, 152, 157); +} + +.p-apple-red-color{ + color: rgb(255, 59 , 48); +} + +.p-apple-red-dark-color{ + color: rgb(255, 69 , 58); +} + +.p-apple-orange-color{ + color: rgb(255,149,0); +} + +.p-apple-orange-dark-color{ + color: rgb(255,159,10); +} + +.p-apple-yellow-color{ + color: rgb(255,204,0); +} + +.p-apple-yellow-dark-color{ + color: rgb(255,214,10); +} + +.p-apple-green-color{ + color: rgb(40,205,65); +} + +.p-apple-green-dark-color{ + color: rgb(40,215,75); +} + +.p-apple-mint-color{ + color: rgb(0,199,190); +} + +.p-apple-mint-dark-color{ + color: rgb(102,212,207); +} + +.p-apple-teal-color{ + color: rgb(89, 173, 196); +} + +.p-apple-teal-dark-color{ + color: rgb(106, 196, 220); +} + +.p-apple-cyan-color{ + color: rgb(85,190,240); +} + +.p-apple-cyan-dark-color{ + color: rgb(90,200,245); +} + +.p-apple-blue-color{ + color: rgb(0, 122, 255); +} + +.p-apple-blue-dark-color{ + color: rgb(10, 132, 255); +} + +.p-apple-indigo-color{ + color: rgb(88, 86, 214); +} + +.p-apple-indigo-dark-color{ + color: rgb(94, 92, 230); +} + +.p-apple-purple-color{ + color: rgb(175, 82, 222); +} + +.p-apple-purple-dark-color{ + color: rgb(191, 90, 242); +} + +.p-apple-pink-color{ + color: rgb(255, 45, 85); +} + +.p-apple-pink-dark-color{ + color: rgb(255, 55, 95); +} + +.p-apple-brown-color{ + color: rgb(162, 132, 94); +} + +.p-apple-brown-dark-color{ + color: rgb(172, 142, 104); +} + +.p-apple-gray-color{ + color: rgb(142, 142, 147); +} + +.p-apple-gray-dark-color{ + color: rgb(152, 152, 157); +} + +.p-strawberry { + background: #c6262e; +} + +.p-strawberry-100 { + background: #ff8c82; +} + +.p-strawberry-300 { + background: #ed5353; +} + +.p-strawberry-500 { + background: #c6262e; +} + +.p-strawberry-700 { + background: #a10705; +} + +.p-strawberry-900 { + background: #7a0000; +} + +.p-orange { + background: #f37329; +} + +.p-orange-100 { + background: #ffc27d; +} + +.p-orange-300 { + background: #ffa154; +} + +.p-orange-500 { + background: #f37329; +} + +.p-orange-700 { + background: #cc3b02; +} + +.p-orange-900 { + background: #a62100; +} + +.p-banana { + background: #f9c440; +} + +.p-banana-100 { + background: #fff394; +} + +.p-banana-300 { + background: #ffe16b; +} + +.p-banana-500 { + background: #f9c440; +} + +.p-banana-700 { + background: #d48e15; +} + +.p-banana-900 { + background: #ad5f00; +} + +.p-lime { + background: #68b723; +} + +.p-lime-100 { + background: #d1ff82; +} + +.p-lime-300 { + background: #9bdb4d; +} + +.p-lime-500 { + background: #68b723; +} + +.p-lime-700 { + background: #3a9104; +} + +.p-lime-900 { + background: #206b00; +} + +.p-mint { + background: #28bca3; +} + +.p-mint-100 { + background: #89ffdd; +} + +.p-mint-300 { + background: #43d6b5; +} + +.p-mint-500 { + background: #28bca3; +} + +.p-mint-700 { + background: #0e9a83; +} + +.p-mint-900 { + background: #007367; +} + +.p-blueberry { + background: #3689e6; +} + +.p-blueberry-100 { + background: #8cd5ff; +} + +.p-blueberry-300 { + background: #64baff; +} + +.p-blueberry-500 { + background: #3689e6; +} + +.p-blueberry-700 { + background: #0d52bf; +} + +.p-blueberry-900 { + background: #002e99; +} + +.p-grape { + background: #a56de2; +} + +.p-grape-100 { + background: #e4c6fa; +} + +.p-grape-300 { + background: #cd9ef7; +} + +.p-grape-500 { + background: #a56de2; +} + +.p-grape-700 { + background: #7239b3; +} + +.p-grape-900 { + background: #452981; +} + +.p-bubblegum { + background: #de3e80; +} + +.p-bubblegum-100 { + background: #fe9ab8; +} + +.p-bubblegum-300 { + background: #f4679d; +} + +.p-bubblegum-500 { + background: #de3e80; +} + +.p-bubblegum-700 { + background: #bc245d; +} + +.p-bubblegum-900 { + background: #910e38; +} + +.p-cocoa { + background: #715344; +} + +.p-cocoa-100 { + background: #a3907c; +} + +.p-cocoa-300 { + background: #8a715e; +} + +.p-cocoa-500 { + background: #715344; +} + +.p-cocoa-700 { + background: #57392d; +} + +.p-cocoa-900 { + background: #3d211b; +} + +.p-silver { + background: #abacae; +} + +.p-silver-100 { + background: #fafafa; +} + +.p-silver-300 { + background: #d4d4d4; +} + +.p-silver-500 { + background: #abacae; +} + +.p-silver-700 { + background: #7e8087; +} + +.p-silver-900 { + background: #555761; +} + +.p-slate { + background: #485a6c; +} + +.p-slate-100 { + background: #95a3ab; +} + +.p-slate-300 { + background: #667885; +} + +.p-slate-500 { + background: #485a6c; +} + +.p-slate-700 { + background: #273445; +} + +.p-slate-900 { + background: #0e141f; +} + +.p-dark { + background: #333; +} + +.p-dark-100 { + background: #666; + /* hehe */ +} + +.p-dark-300 { + background: #4d4d4d; +} + +.p-dark-500 { + background: #333; +} + +.p-dark-700 { + background: #1a1a1a; +} + +.p-dark-900 { + background: #000; +} + +.p-white{ + background: #fff; +} + +.p-strawberry-color { + color: #c6262e; +} + +.p-strawberry-100-color { + color: #ff8c82; +} + +.p-strawberry-300-color { + color: #ed5353; +} + +.p-strawberry-500-color { + color: #c6262e; +} + +.p-strawberry-700-color { + color: #a10705; +} + +.p-strawberry-900-color { + color: #7a0000; +} + +.p-orange-color { + color: #f37329; +} + +.p-orange-100-color { + color: #ffc27d; +} + +.p-orange-300-color { + color: #ffa154; +} + +.p-orange-500-color { + color: #f37329; +} + +.p-orange-700-color { + color: #cc3b02; +} + +.p-orange-900-color { + color: #a62100; +} + +.p-banana-color { + color: #f9c440; +} + +.p-banana-100-color { + color: #fff394; +} + +.p-banana-300-color { + color: #ffe16b; +} + +.p-banana-500-color { + color: #f9c440; +} + +.p-banana-700-color { + color: #d48e15; +} + +.p-banana-900-color { + color: #ad5f00; +} + +.p-lime-color { + color: #68b723; +} + +.p-lime-100-color { + color: #d1ff82; +} + +.p-lime-300-color { + color: #9bdb4d; +} + +.p-lime-500-color { + color: #68b723; +} + +.p-lime-700-color { + color: #3a9104; +} + +.p-lime-900-color { + color: #206b00; +} + +.p-mint-color { + color: #28bca3; +} + +.p-mint-100-color { + color: #89ffdd; +} + +.p-mint-300-color { + color: #43d6b5; +} + +.p-mint-500-color { + color: #28bca3; +} + +.p-mint-700-color { + color: #0e9a83; +} + +.p-mint-900-color { + color: #007367; +} + +.p-blueberry-color { + color: #3689e6; +} + +.p-blueberry-100-color { + color: #8cd5ff; +} + +.p-blueberry-300-color { + color: #64baff; +} + +.p-blueberry-500-color { + color: #3689e6; +} + +.p-blueberry-700-color { + color: #0d52bf; +} + +.p-blueberry-900-color { + color: #002e99; +} + +.p-grape-color { + color: #a56de2; +} + +.p-grape-100-color { + color: #e4c6fa; +} + +.p-grape-300-color { + color: #cd9ef7; +} + +.p-grape-500-color { + color: #a56de2; +} + +.p-grape-700-color { + color: #7239b3; +} + +.p-grape-900-color { + color: #452981; +} + +.p-bubblegum-color { + color: #de3e80; +} + +.p-bubblegum-100-color { + color: #fe9ab8; +} + +.p-bubblegum-300-color { + color: #f4679d; +} + +.p-bubblegum-500-color { + color: #de3e80; +} + +.p-bubblegum-700-color { + color: #bc245d; +} + +.p-bubblegum-900-color { + color: #910e38; +} + +.p-cocoa-color { + color: #715344; +} + +.p-cocoa-100-color { + color: #a3907c; +} + +.p-cocoa-300-color { + color: #8a715e; +} + +.p-cocoa-500-color { + color: #715344; +} + +.p-cocoa-700-color { + color: #57392d; +} + +.p-cocoa-900-color { + color: #3d211b; +} + +.p-silver-color { + color: #abacae; +} + +.p-silver-100-color { + color: #fafafa; +} + +.p-silver-300-color { + color: #d4d4d4; +} + +.p-silver-500-color { + color: #abacae; +} + +.p-silver-700-color { + color: #7e8087; +} + +.p-silver-900-color { + color: #555761; +} + +.p-slate-color { + color: #485a6c; +} + +.p-slate-100-color { + color: #95a3ab; +} + +.p-slate-300-color { + color: #667885; +} + +.p-slate-500-color { + color: #485a6c; +} + +.p-slate-700-color { + color: #273445; +} + +.p-slate-900-color { + color: #0e141f; +} + +.p-dark-color { + color: #333; +} + +.p-dark-100-color { + color: #666; + /* hehe */ +} + +.p-dark-300-color { + color: #4d4d4d; +} + +.p-dark-500-color { + color: #333; +} + +.p-dark-700-color { + color: #1a1a1a; +} + +.p-dark-900-color { + color: #000; +} + +.p-white-color{ + color: #fff; +} diff --git a/v3/internal/templates/ios/frontend/public/puppertino/css/dark_mode.css b/v3/internal/templates/ios/frontend/public/puppertino/css/dark_mode.css new file mode 100644 index 000000000..3c5a03e80 --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/puppertino/css/dark_mode.css @@ -0,0 +1 @@ +/* Puppertino dark_mode placeholder - local vendored */ diff --git a/v3/internal/templates/ios/frontend/public/puppertino/css/forms.css b/v3/internal/templates/ios/frontend/public/puppertino/css/forms.css new file mode 100644 index 000000000..f2320ab1b --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/puppertino/css/forms.css @@ -0,0 +1,509 @@ +:root { + --primary-col:linear-gradient(to bottom, #4fc5fa 0%,#0f75f5 100%); + --primary-col-ac:#0f75f5; + --bg-color-input:#fff; + + --p-checkbox-gradient: linear-gradient(180deg, #4B91F7 0%, #367AF6 100%); + --p-checkbox-border: rgba(0, 0, 0, 0.2); + --p-checkbox-border-active: rgba(0, 0, 0, 0.12); + --p-checkbox-bg: transparent; + --p-checkbox-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.15), inset 0px 0px 2px rgba(0, 0, 0, 0.10); + + --p-input-bg:#fff; + --p-input-color: rgba(0,0,0,.85); + --p-input-color-plac:rgba(0,0,0,0.25); + + --p-input-color:#808080; + --p-input-bd:rgba(0,0,0,0.15); + --bg-hover-color:#f9f9f9; + --bg-front-col:#000; + --invalid-color:#d6513c; + --valid-color:#94d63c; +} + +.p-dark-mode{ + --p-checkbox-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.13) 100%); + --p-checkbox-shadow: 0px 0px 1px rgba(0, 0, 0, 0.25), inset 0px 0.5px 0px rgba(255, 255, 255, 0.15); + --p-checkbox-border: rgba(0, 0, 0, 0); + + --p-checkbox-gradient: linear-gradient(180deg, #3168DD 0%, #2C5FC8 100%); + --p-checkbox-border-active: rgba(0, 0, 0, 0); +} + +.p-form-select { + border-radius: 5px; + display: inline-block; + font-family: -apple-system, "Inter", sans-serif; + margin: 10px; + position: relative; +} + +.p-form-select > select:focus{ + outline: 2px solid #64baff; +} + +.p-form-select::after { + background: url("data:image/svg+xml,%3Csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 12'%3E%3Cpath d='M.288 4.117 3.16 1.18c.168-.168.336-.246.54-.246a.731.731 0 0 1 .538.246L7.108 4.12c.125.121.184.27.184.45 0 .359-.293.656-.648.656a.655.655 0 0 1-.48-.211L3.701 2.465l-2.469 2.55a.664.664 0 0 1-.48.212.656.656 0 0 1-.465-1.11Zm3.41 7.324a.73.73 0 0 0 .54-.246l2.87-2.941a.601.601 0 0 0 .184-.45.656.656 0 0 0-.648-.656.677.677 0 0 0-.48.211L3.701 9.91 1.233 7.36a.68.68 0 0 0-.48-.212.656.656 0 0 0-.465 1.11l2.871 2.937c.172.168.336.246.54.246Z' fill='white' style='mix-blend-mode:luminosity'/%3E%3C/svg%3E"), #017AFF; + background-size: 100% 75%; + background-position: center; + background-repeat: no-repeat; + border-radius: 5px; + bottom: 0; + content: ""; + display: block; + height: 80%; + pointer-events: none; + position: absolute; + right: 3%; + top: 10%; + width: 20px; +} + +.p-form-select > select { + -webkit-appearance: none; + appearance: none; + background: var(--p-input-bg); + border: 1px solid var(--p-input-bd); + border-radius: 5px; + font-size: 14px; + margin: 0; + outline: none; + padding: 5px 35px 5px 10px; + position: relative; + width: 100%; + color: var(--p-input-color); +} + +.p-form-text:invalid, +.p-form-text-alt:invalid{ + border-color: var(--invalid-color); +} + +.p-form-text:valid, +.p-form-text-alt:valid{ + border-color: var(--valid-color); +} + +.p-form-text:placeholder-shown, +.p-form-text-alt:placeholder-shown{ + border-color: var(--p-input-bd); +} + +.p-form-text { + color: var(--p-input-color); + -webkit-appearance: none; + appearance: none; + background: var(--p-input-bg); + border: 1px solid var(--p-input-bd); + border-radius: 5px; + font-family: -apple-system, "Inter", sans-serif; + font-size: 13px; + margin: 10px; + outline: 0; + padding: 3px 7px; + resize: none; + transition: border-color 200ms; + box-shadow: 0px 0.5px 2.5px rgba(0,0,0,.3), 0px 0px 0px rgba(0,0,0,.1); +} + +.p-form-text-alt { + color: var(--p-input-color); + -webkit-appearance: none; + appearance: none; + box-shadow: none; + background: var(--p-input-bg); + border: 0px; + border-bottom: 2px solid var(--p-input-bd); + padding: 10px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + margin: 10px; +} + + + +.p-form-text-alt::placeholder, +.p-form-text::placeholder +{ + color: var(--p-input-color-plac); +} + +.p-form-text:active, +.p-form-text:focus +{ + outline: 3px solid rgb(0 122 255 / 50%); +} + +.p-form-text-alt:focus { + outline: 0; + outline: 3px solid rgb(0 122 255 / 50%); + border-color: #3689e6; +} + +.p-form-no-validate:valid, +.p-form-no-validate:invalid{ + border-color: var(--p-input-bd); + color: var(--p-input-color)!important; +} + +.p-form-text:focus { + border-color: rgb(0 122 255); +} + +textarea.p-form-text { + -webkit-appearance: none; + appearance: none; + height: 100px; +} + +.p-form-truncated { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.p-form-text[type=password] { + font-family: caption; +} + +.p-form-label, +.p-form-radio-cont, +.p-form-checkbox-cont, +.p-form-label-inline { + font-family: -apple-system, "Inter", sans-serif; +} + +.p-form-label, .p-form-label-inline { + display: inline-block; +} + +.p-form-label{ + font-size: 11px; +} + +.p-form-label-inline { + background: var(--p-input-bg); + padding: 5px; + border-bottom: 2px solid var(--p-input-bd); + color: #656565; + font-weight: 500; + transition: .3s; +} + +.p-form-label-inline:focus-within { + color: #3689e6; + border-color: #3689e6; +} + +.p-form-label-inline > .p-form-text-alt { + border-bottom: 0px; + padding: 0; + outline: 0; + background: var(--p-input-bg); + +} + +.p-form-label-inline > .p-form-text-alt:-webkit-autofill{ + background: var(--p-input-bg); + -webkit-box-shadow: 0 0 0 30px rgba(0,0,0,0) inset !important; +} + +.p-form-label-inline > .p-form-text-alt:invalid { + color: var(--invalid-color); +} + +.p-form-label-inline > .p-form-text-alt:valid { + color: #3689e6; +} + +.p-form-label-inline > .p-form-text-alt:focus{ + color: var(--p-input-color); +} + +.p-form-radio-cont, +.p-form-checkbox-cont { + align-items: center; + display: inline-flex; + cursor: pointer; + margin: 0 10px; + user-select: none; +} + +.p-form-radio-cont > input + span, +.p-form-checkbox-cont > input + span { + background: var(--p-input-bg); + border: 1px solid var(--p-input-bd); + border-radius: 50%; + display: inline-block; + height: 20px; + margin-right: 5px; + position: relative; + transition: 0.2s; + width: 20px; +} + +.p-form-radio-cont > input + span{ + box-shadow: inset 0px 1px 2px rgba(0,0,0,0.10), inset 0px 0px 2px rgba(0,0,0,0.10); +} + +.p-form-radio-cont > input:focus + span, +.p-form-checkbox-cont > input:focus + span{ + outline: 3px solid rgb(0 122 255 / 50%); +} + +.p-form-radio-cont:hover > input + span{ + background: #f9f9f9; +} + +.p-form-radio-cont > input, +.p-form-checkbox-cont > input { + opacity: 0; + pointer-events: none; + position: absolute; +} + +.p-form-radio-cont > input + span::after { + background: #fff; + border-radius: 50%; + content: ""; + display: block; + height: 30%; + left: calc(50% - 15%); + opacity: 0; + position: absolute; + top: calc(50% - 15%); + transform: scale(2); + transition: opacity 0.2s, transform 0.3s; + width: 30%; +} + +.p-form-radio-cont > input:checked + span { + background: #0f75f5; + box-shadow: 0px 1px 2.5px 1px rgba(0, 122, 255, 0.24), inset 0px 0px 0px 0.5px rgba(0, 122, 255, 0.12); +} + +.p-form-radio-cont > input:checked + span::after { + opacity: 1; + transform: scale(1); +} + +.p-form-checkbox-cont > input + span { + border-radius: 5px; + box-shadow: var(--p-checkbox-shadow); + border: 0.5px solid var(--p-checkbox-border); + background: var(--p-checkbox-bg) +} + +.p-form-checkbox-cont > input:checked + span { + background: var(--p-checkbox-gradient); + border: 0.5px solid var(--p-checkbox-border-active); + box-shadow: 0px 1px 2.5px rgba(0, 122, 255, 0.24), 0px 0px 0px 0.5px rgba(0, 122, 255, 0.12); +} + +.p-form-checkbox-cont > input + span::before{ + content: ""; + display: block; + height: 100%; + width: 100%; + position: absolute; + left: 0%; + top: 0%; + opacity: 0; + transition: opacity 0.2s; + background-image: url("data:image/svg+xml,%3Csvg width='10' height='8' viewBox='0 0 10 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.10791 7.81299C3.83545 7.81299 3.6084 7.70459 3.42676 7.48779L1.15918 4.74121C1.08008 4.65039 1.02441 4.5625 0.992188 4.47754C0.959961 4.39258 0.943848 4.30469 0.943848 4.21387C0.943848 4.00586 1.0127 3.83447 1.15039 3.69971C1.29102 3.56201 1.4668 3.49316 1.67773 3.49316C1.91211 3.49316 2.10693 3.58838 2.26221 3.77881L4.10791 6.04297L7.68066 0.368652C7.77148 0.230957 7.86523 0.134277 7.96191 0.0786133C8.06152 0.0200195 8.18311 -0.00927734 8.32666 -0.00927734C8.5376 -0.00927734 8.71191 0.0581055 8.84961 0.192871C8.9873 0.327637 9.05615 0.497559 9.05615 0.702637C9.05615 0.778809 9.04297 0.85791 9.0166 0.939941C8.99023 1.02197 8.94922 1.10693 8.89355 1.19482L4.80225 7.45703C4.64111 7.69434 4.40967 7.81299 4.10791 7.81299Z' fill='white'/%3E%3C/svg%3E%0A"); + background-size: 70%; + background-position: center; + background-repeat: no-repeat; +} + +.p-form-checkbox-cont > input + span::after{ + content: ''; + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + z-index: 9; +} + +.p-form-checkbox-cont > input + span:active::after{ + border-radius: 5px; + backdrop-filter: brightness(1.2); +} + +.p-form-checkbox-cont > input:checked + span::before{ + opacity: 1; +} + + +.p-form-checkbox-cont > input[disabled] + span, +.p-form-radio-cont > input[disabled] ~ span +{ + opacity: .7; + cursor: not-allowed; +} + +.p-form-button { + -webkit-appearance: none; + appearance: none; + background: #fff; + border: 1px solid var(--p-input-bd); + border-radius: 5px; + color: #333230; + display: inline-block; + font-size: 17px; + margin: 10px; + padding: 5px 20px; + text-decoration: none; +} + +.p-form-send { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border: 0; + color: #fff; +} + +.p-form-send:active { + background: #0f75f5; +} + +.p-form-invalid, +.p-form-invalid:placeholder-shown, +.p-form-invalid:valid, +.p-form-invalid:invalid { + border-color: var(--invalid-color); +} + +.p-form-valid, +.p-form-valid:placeholder-shown, +.p-form-valid:valid, +.p-form-valid:invalid { + border-color: var(--valid-color); +} + +.p-form-switch { + --width: 80px; + cursor: pointer; + display: inline-block; +} + +.p-form-switch > input:checked + span::after { + left: calc(100% - calc(var(--width) / 1.8)); +} + +.p-form-switch > input:checked + span { + background: #60c35b; +} + +.p-form-switch > span { + background: #e0e0e0; + border: 1px solid #d3d3d3; + border-radius: 500px; + display: block; + height: calc(var(--width) / 1.6); + position: relative; + transition: all 0.2s; + width: var(--width); +} + +.p-form-switch > span::after { + background: #f9f9f9; + border-radius: 50%; + border: 0.5px solid rgba(0, 0, 0, 0.101987); + box-shadow: 0px 3px 1px rgba(0, 0, 0, 0.1), 0px 1px 1px rgba(0, 0, 0, 0.16), 0px 3px 8px rgba(0, 0, 0, 0.15); + box-sizing: border-box; + content: ""; + height: 84%; + left: 3%; + position: absolute; + top: 6.5%; + transition: all 0.2s; + width: 52.5%; +} + +.p-form-switch > input { + display: none; +} + +.p-chip input{ + opacity: 0; + pointer-events: none; + position: absolute; +} + +.p-chip span{ + padding: .8rem 1rem; + border-radius: 1.6rem; + display:inline-block; + margin:10px; + background: #e4e4e4ca; + color: #3689e6; + transition: .3s; + user-select: none; + cursor:pointer; + font-family: -apple-system, "Inter", sans-serif; + font-size: 1rem; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -moz-tap-highlight-color: rgba(0, 0, 0, 0); + text-align:center; +} + +.p-chip:focus-within span{ + outline: 2px solid #64baff; +} + +.p-chip svg{ + display:block; + margin:auto; +} + + +.p-chip input:checked + span{ + background: #3689e6; + color:#fff; +} + +.p-chip-outline span, .p-chip-outline-to-bg span{ + background: transparent; + color: #3e3e3e; + border: 1px solid currentColor; +} + +.p-chip-outline input:checked + span{ + background: transparent; + color: #3689e6; +} + +.p-chip-radius-b span{ + border-radius: 5px; +} + +.p-chip-dark span{ + color: #3e3e3e; +} + +.p-chip-dark input:checked + span{ + background: #3e3e3e; +} + +.p-chip input:disabled + span, +.p-chip input[disabled] + span{ + opacity: .5; + cursor: not-allowed; +} + +.p-chip-big span{ + font-size: 1.3rem; + padding: 1.5rem; + min-width: 80px; +} + +.p-form-checkbox-cont[disabled], +.p-form-label[disabled], +.p-form-text[disabled], +.p-form-text-alt[disabled], +.p-form-select[disabled], +.p-form-radio-cont[disabled]{ + filter: grayscale(1) opacity(.3); + pointer-events: none; +} diff --git a/v3/internal/templates/ios/frontend/public/puppertino/css/layout.css b/v3/internal/templates/ios/frontend/public/puppertino/css/layout.css new file mode 100644 index 000000000..1f9b36845 --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/puppertino/css/layout.css @@ -0,0 +1,45 @@ +.p-large-title{ + font-size: 2.75rem; +} + +.p-layout h1 { + font-size: 2.25rem; +} + +.p-layout h2 { + font-size: 1.75rem; +} + +.p-layout h3 { + font-size: 1.58rem; +} + +.p-headline { + font-size: 1.34rem; + font-weight: bold; +} + +.p-layout p { + font-size: 1.15rem; +} + +.p-layout .link, +.p-layout input { + font-size: 0.813rem; +} + +.p-callout { + font-size: 1.14rem; +} + +.p-subhead { + font-size: 1.167rem; +} + +.p-footnote { + font-size: 1.07rem; +} + +.p-caption { + font-size: 0.91rem; +} diff --git a/v3/internal/templates/ios/frontend/public/puppertino/css/modals.css b/v3/internal/templates/ios/frontend/public/puppertino/css/modals.css new file mode 100644 index 000000000..4d718c4f7 --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/puppertino/css/modals.css @@ -0,0 +1 @@ +/* Puppertino modals placeholder - local vendored */ diff --git a/v3/internal/templates/ios/frontend/public/puppertino/css/newfull.css b/v3/internal/templates/ios/frontend/public/puppertino/css/newfull.css new file mode 100644 index 000000000..622a2f364 --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/puppertino/css/newfull.css @@ -0,0 +1,11 @@ +@import url('actions.css'); +@import url('buttons.css'); +@import url('layout.css'); +@import url('cards.css'); +@import url('color_palette.css'); +@import url('forms.css'); +@import url('modals.css'); +@import url('segmented-controls.css'); +@import url('shadows.css'); +@import url('tabs.css'); +@import url('dark_mode.css'); diff --git a/v3/internal/templates/ios/frontend/public/puppertino/css/segmented-controls.css b/v3/internal/templates/ios/frontend/public/puppertino/css/segmented-controls.css new file mode 100644 index 000000000..22819fd5f --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/puppertino/css/segmented-controls.css @@ -0,0 +1 @@ +/* Puppertino segmented-controls placeholder - local vendored */ diff --git a/v3/internal/templates/ios/frontend/public/puppertino/css/shadows.css b/v3/internal/templates/ios/frontend/public/puppertino/css/shadows.css new file mode 100644 index 000000000..060a61658 --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/puppertino/css/shadows.css @@ -0,0 +1 @@ +/* Puppertino shadows placeholder - local vendored */ diff --git a/v3/internal/templates/ios/frontend/public/puppertino/css/tabs.css b/v3/internal/templates/ios/frontend/public/puppertino/css/tabs.css new file mode 100644 index 000000000..61d1487ca --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/puppertino/css/tabs.css @@ -0,0 +1 @@ +/* Puppertino tabs placeholder - local vendored */ diff --git a/v3/internal/templates/ios/frontend/public/puppertino/puppertino.css b/v3/internal/templates/ios/frontend/public/puppertino/puppertino.css new file mode 100644 index 000000000..905da220e --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/puppertino/puppertino.css @@ -0,0 +1,1774 @@ +@charset "UTF-8"; +.p-btn { + background: #fff; + border: 1px solid #cacaca; + border-radius: 5px; + color: #333230; + display: inline-block; + font-family: -apple-system, "Inter", sans-serif; + font-size: 17px; + margin: 10px; + padding: 5px 20px; + text-decoration: none; + /* text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); */ +} +.p-btn-mob{ + padding: 10px 40px; + background: #0f75f5; + color: #fff; +} +.p-btn[disabled] { + background: #d3d3d3; + color: #555; + cursor: not-allowed; +} +.p-btn:disabled { + background: #d3d3d3; + color: #555; + cursor: not-allowed; +} +.p-btn-disabled { + background: #d3d3d3; + color: #555; + cursor: not-allowed; +} + +.p-prim-col { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border: 0; + color: #fff; +} + +.p-btn.p-prim-col:active { + background: #0f75f5; +} + +.p-btn-more::after { + content: "..."; +} + +.p-btn-round { + border: 0; + border-radius: 50px; + padding: 10px 30px; +} + +.p-btn-icon { + align-items: center; + background: #fff; + border: 2px solid currentColor; + border-radius: 50%; + box-shadow: 0 3px 10px -8px #000; + color: #0f75f5; + display: inline-flex; + font-weight: 900; + height: 36px; + justify-content: center; + margin: 5px; + text-align: center; + text-decoration: none; + width: 36px; +} + +.p-btn-scope { + background: #8e8e8e; + color: #fff; + margin: 5px; + padding: 2px 20px; +} +.p-btn-scope-unactive { + background: transparent; + border-color: transparent; + color: #212136; + transition: border-color 0.2s; +} +.p-btn-scope-unactive:hover { + border-color: #cacaca; +} +.p-btn-scope-disabled { + background: transparent; + color: #8e8e8e; + cursor: not-allowed; +} +.p-btn-scope-outline { + background: transparent; + color: #212136; +} + +.p-btn-scope-outline { + background: transparent; + color: #212136; +} + +.p-btn-outline { + background: none; + border-color: currentColor; +} + +.p-btn-outline-dash { + background: none; + border-color: currentColor; + border-style: dashed; +} + +.p-btn-direction { + color: #212136; + padding: 5px; + text-decoration: none; +} + +.p-btn-direction.p-btn-d-back::before { + content: "❬"; +} + +.p-btn-direction.p-btn-d-next::after { + content: "❭"; +} + +@media (max-width: 576px) { + .p-btn-big-sm { + border: 0; + border-radius: 0%; + bottom: 0; + font-size: 50px; + left: 0; + margin: 0; + padding: 10px 0; + position: fixed; + text-align: center; + width: 100%; + } +} + +/*END OF BUTTONS*/ + +.p-card { + background: rgba(255, 255, 255, 0.3); + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 3px; + box-shadow: 0 8px 10px -8px rgba(0, 0, 0, 0.1); + color: #000; + display: block; + margin-top: 30px; + text-decoration: none; +} +.p-card-image > img { + border-bottom: 3px solid var(--accent-article); + display: block; + margin: auto; + width: 100%; +} +.p-card-tags { + display: flex; + overflow: hidden; + position: relative; + width: 100%; +} +.p-card-tags::before { + background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 75%, white 100%); + content: ""; + height: 100%; + position: absolute; + right: 0; + top: 0; + width: 30%; +} +.p-card-tags span, +.p-card-tags a { + border: 1px solid #252525; + border-radius: 50px; + color: #252525; + margin: 5px; + padding: 5px 15px; + text-decoration: none; + transition: all 0.2s; +} +.p-card-tags a:hover { + background: #252525; + color: #000; +} +.p-card-title { + font-size: 2rem; + margin-bottom: 15px; + margin-top: 10px; +} +.p-card-content { + padding: 15px; + padding-top: 5px; +} +.p-card-text { + font-size: 17px; + margin-bottom: 10px; + margin-left: 10px; + margin-top: 0; +} + + +/* END OF CARDS*/ + +.p-strawberry { + background: #c6262e; +} + +.p-strawberry-100 { + background: #ff8c82; +} + +.p-strawberry-300 { + background: #ed5353; +} + +.p-strawberry-500 { + background: #c6262e; +} + +.p-strawberry-700 { + background: #a10705; +} + +.p-strawberry-900 { + background: #7a0000; +} + +.p-orange { + background: #f37329; +} + +.p-orange-100 { + background: #ffc27d; +} + +.p-orange-300 { + background: #ffa154; +} + +.p-orange-500 { + background: #f37329; +} + +.p-orange-700 { + background: #cc3b02; +} + +.p-orange-900 { + background: #a62100; +} + +.p-banana { + background: #f9c440; +} + +.p-banana-100 { + background: #fff394; +} + +.p-banana-300 { + background: #ffe16b; +} + +.p-banana-500 { + background: #f9c440; +} + +.p-banana-700 { + background: #d48e15; +} + +.p-banana-900 { + background: #ad5f00; +} + +.p-lime { + background: #68b723; +} + +.p-lime-100 { + background: #d1ff82; +} + +.p-lime-300 { + background: #9bdb4d; +} + +.p-lime-500 { + background: #68b723; +} + +.p-lime-700 { + background: #3a9104; +} + +.p-lime-900 { + background: #206b00; +} + +.p-mint { + background: #28bca3; +} + +.p-mint-100 { + background: #89ffdd; +} + +.p-mint-300 { + background: #43d6b5; +} + +.p-mint-500 { + background: #28bca3; +} + +.p-mint-700 { + background: #0e9a83; +} + +.p-mint-900 { + background: #007367; +} + +.p-blueberry { + background: #3689e6; +} + +.p-blueberry-100 { + background: #8cd5ff; +} + +.p-blueberry-300 { + background: #64baff; +} + +.p-blueberry-500 { + background: #3689e6; +} + +.p-blueberry-700 { + background: #0d52bf; +} + +.p-blueberry-900 { + background: #002e99; +} + +.p-grape { + background: #a56de2; +} + +.p-grape-100 { + background: #e4c6fa; +} + +.p-grape-300 { + background: #cd9ef7; +} + +.p-grape-500 { + background: #a56de2; +} + +.p-grape-700 { + background: #7239b3; +} + +.p-grape-900 { + background: #452981; +} + +.p-bubblegum { + background: #de3e80; +} + +.p-bubblegum-100 { + background: #fe9ab8; +} + +.p-bubblegum-300 { + background: #f4679d; +} + +.p-bubblegum-500 { + background: #de3e80; +} + +.p-bubblegum-700 { + background: #bc245d; +} + +.p-bubblegum-900 { + background: #910e38; +} + +.p-cocoa { + background: #715344; +} + +.p-cocoa-100 { + background: #a3907c; +} + +.p-cocoa-300 { + background: #8a715e; +} + +.p-cocoa-500 { + background: #715344; +} + +.p-cocoa-700 { + background: #57392d; +} + +.p-cocoa-900 { + background: #3d211b; +} + +.p-silver { + background: #abacae; +} + +.p-silver-100 { + background: #fafafa; +} + +.p-silver-300 { + background: #d4d4d4; +} + +.p-silver-500 { + background: #abacae; +} + +.p-silver-700 { + background: #7e8087; +} + +.p-silver-900 { + background: #555761; +} + +.p-slate { + background: #485a6c; +} + +.p-slate-100 { + background: #95a3ab; +} + +.p-slate-300 { + background: #667885; +} + +.p-slate-500 { + background: #485a6c; +} + +.p-slate-700 { + background: #273445; +} + +.p-slate-900 { + background: #0e141f; +} + +.p-dark { + background: #333; +} + +.p-dark-100 { + background: #666; + /* hehe */ +} + +.p-dark-300 { + background: #4d4d4d; +} + +.p-dark-500 { + background: #333; +} + +.p-dark-700 { + background: #1a1a1a; +} + +.p-dark-900 { + background: #000; +} + +.p-strawberry-color { + color: #c6262e; +} + +.p-strawberry-100-color { + color: #ff8c82; +} + +.p-strawberry-300-color { + color: #ed5353; +} + +.p-strawberry-500-color { + color: #c6262e; +} + +.p-strawberry-700-color { + color: #a10705; +} + +.p-strawberry-900-color { + color: #7a0000; +} + +.p-orange-color { + color: #f37329; +} + +.p-orange-100-color { + color: #ffc27d; +} + +.p-orange-300-color { + color: #ffa154; +} + +.p-orange-500-color { + color: #f37329; +} + +.p-orange-700-color { + color: #cc3b02; +} + +.p-orange-900-color { + color: #a62100; +} + +.p-banana-color { + color: #f9c440; +} + +.p-banana-100-color { + color: #fff394; +} + +.p-banana-300-color { + color: #ffe16b; +} + +.p-banana-500-color { + color: #f9c440; +} + +.p-banana-700-color { + color: #d48e15; +} + +.p-banana-900-color { + color: #ad5f00; +} + +.p-lime-color { + color: #68b723; +} + +.p-lime-100-color { + color: #d1ff82; +} + +.p-lime-300-color { + color: #9bdb4d; +} + +.p-lime-500-color { + color: #68b723; +} + +.p-lime-700-color { + color: #3a9104; +} + +.p-lime-900-color { + color: #206b00; +} + +.p-mint-color { + color: #28bca3; +} + +.p-mint-100-color { + color: #89ffdd; +} + +.p-mint-300-color { + color: #43d6b5; +} + +.p-mint-500-color { + color: #28bca3; +} + +.p-mint-700-color { + color: #0e9a83; +} + +.p-mint-900-color { + color: #007367; +} + +.p-blueberry-color { + color: #3689e6; +} + +.p-blueberry-100-color { + color: #8cd5ff; +} + +.p-blueberry-300-color { + color: #64baff; +} + +.p-blueberry-500-color { + color: #3689e6; +} + +.p-blueberry-700-color { + color: #0d52bf; +} + +.p-blueberry-900-color { + color: #002e99; +} + +.p-grape-color { + color: #a56de2; +} + +.p-grape-100-color { + color: #e4c6fa; +} + +.p-grape-300-color { + color: #cd9ef7; +} + +.p-grape-500-color { + color: #a56de2; +} + +.p-grape-700-color { + color: #7239b3; +} + +.p-grape-900-color { + color: #452981; +} + +.p-bubblegum-color { + color: #de3e80; +} + +.p-bubblegum-100-color { + color: #fe9ab8; +} + +.p-bubblegum-300-color { + color: #f4679d; +} + +.p-bubblegum-500-color { + color: #de3e80; +} + +.p-bubblegum-700-color { + color: #bc245d; +} + +.p-bubblegum-900-color { + color: #910e38; +} + +.p-cocoa-color { + color: #715344; +} + +.p-cocoa-100-color { + color: #a3907c; +} + +.p-cocoa-300-color { + color: #8a715e; +} + +.p-cocoa-500-color { + color: #715344; +} + +.p-cocoa-700-color { + color: #57392d; +} + +.p-cocoa-900-color { + color: #3d211b; +} + +.p-silver-color { + color: #abacae; +} + +.p-silver-100-color { + color: #fafafa; +} + +.p-silver-300-color { + color: #d4d4d4; +} + +.p-silver-500-color { + color: #abacae; +} + +.p-silver-700-color { + color: #7e8087; +} + +.p-silver-900-color { + color: #555761; +} + +.p-slate-color { + color: #485a6c; +} + +.p-slate-100-color { + color: #95a3ab; +} + +.p-slate-300-color { + color: #667885; +} + +.p-slate-500-color { + color: #485a6c; +} + +.p-slate-700-color { + color: #273445; +} + +.p-slate-900-color { + color: #0e141f; +} + +.p-dark-color { + color: #333; +} + +.p-dark-100-color { + color: #666; + /* hehe */ +} + +.p-dark-300-color { + color: #4d4d4d; +} + +.p-dark-500-color { + color: #333; +} + +.p-dark-700-color { + color: #1a1a1a; +} + +.p-dark-900-color { + color: #000; +} + +/* END OF COLORS */ + +:root { + --primary-col:linear-gradient(to bottom, #4fc5fa 0%,#0f75f5 100%); + --primary-col-ac:#0f75f5; + --bg-color:#fff; + --bg-hover-color:#f9f9f9; + --bg-front-col:#000; + --invalid-color:#d6513c; + --valid-color:#94d63c; +} + +.p-form-select { + border-radius: 5px; + display: inline-block; + font-family: -apple-system, "Inter", sans-serif; + margin: 10px; + position: relative; +} + +.p-form-select::before { + border-color: #fff transparent transparent; + border-style: solid; + border-width: 5px; + content: ""; + pointer-events: none; + position: absolute; + right: 5px; + top: calc(50% - 3px); + z-index: 3; +} + +.p-form-select::after { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border-bottom-right-radius: 5px; + border-top-right-radius: 5px; + bottom: 0; + content: ""; + display: block; + height: 100%; + pointer-events: none; + position: absolute; + right: 0; + top: 0; + width: 20px; +} + +.p-form-select > select { + -webkit-appearance: none; + background: #fff; + border: 1px solid #cacaca; + border-radius: 5px; + font-size: 14px; + margin: 0; + outline: none; + padding: 5px 30px 5px 10px; + position: relative; + width: 100%; +} + +.p-form-text:invalid, +.p-form-text-alt:invalid, +.p-form-select > select:invalid { + border-color: var(--invalid-color); +} + +.p-form-text:valid, +.p-form-text-alt:valid, +.p-form-select > select:valid { + border-color: var(--valid-color); +} + +.p-form-text:placeholder-shown, +.p-form-text-alt:placeholder-shown, +.p-form-select > select:placeholder-shown { + border-color: #cacaca; +} + +.p-form-text { + -webkit-appearance: none; + box-shadow: none; + background: #fff; + border: 1px solid #cacaca; + border-radius: 5px; + font-family: -apple-system, "Inter", sans-serif; + margin: 10px; + outline: 0; + padding: 5px; + resize: none; + transition: border-color 200ms; +} + +.p-form-text-alt { + -webkit-appearance: none; + box-shadow: none; + background: #fff; + border: 0px; + border-bottom: 2px solid #cacaca; + padding: 10px; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + margin: 10px; +} + +.p-form-text-alt::placeholder { + color: #cacaca; +} + +.p-form-text-alt:focus { + outline: 3px solid #bed8f9; +} + +.p-form-no-validate:valid, +.p-form-no-validate:invalid, +.p-form-no-validate > select:valid, +.p-form-no-validate > select:invalid { + border-color: #cacaca; +} + +.p-form-text:focus { + border-color: #0f75f5; +} + +textarea.p-form-text { + -webkit-appearance: none; + height: 100px; +} + +.p-form-truncated { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.p-form-text[type=password] { + font-family: caption; +} + +.p-form-label, +.p-form-radio-cont, +.p-form-checkbox-cont { + font-family: -apple-system, "Inter", sans-serif; +} + +.p-form-label { + display: block; +} + +.p-form-radio-cont, +.p-form-checkbox-cont { + align-items: center; + display: inline-flex; + margin: 0 10px; +} + +.p-form-radio-cont > input + span, +.p-form-checkbox-cont > input + span { + background: #fff; + border: 1px solid #cacaca; + border-radius: 50%; + cursor: pointer; + display: inline-block; + height: 20px; + margin-right: 5px; + position: relative; + transition: background 0.2s; + width: 20px; +} + +.p-form-radio-cont > input + span:hover { + background: #f9f9f9; +} + +.p-form-radio-cont > input, +.p-form-checkbox-cont > input { + opacity: 0; + pointer-events: none; + position: absolute; +} + +.p-form-radio-cont > input + span::after { + background: #fff; + border-radius: 50%; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + content: ""; + display: block; + height: 30%; + left: calc(50% - 15%); + opacity: 0; + position: absolute; + top: calc(50% - 15%); + transform: scale(2); + transition: opacity 0.2s, transform 0.3s; + width: 30%; +} + +.p-form-radio-cont > input:checked + span { + background: #0f75f5; +} + +.p-form-radio-cont > input:checked + span::after { + opacity: 1; + transform: scale(1); +} + +.p-form-checkbox-cont > input + span { + border-radius: 5px; +} + +.p-form-checkbox-cont > input:checked + span { + background: #0f75f5; +} + +.p-form-checkbox-cont > input + span::before, +.p-form-checkbox-cont > input + span::after { + background: #fff; + border-radius: 20px; + content: ""; + display: block; + height: 8%; + position: absolute; +} + +.p-form-checkbox-cont > input + span::before { + right: 30%; + top: 15%; + transform: rotate(-65deg); + transform-origin: top right; + width: 70%; +} + +.p-form-checkbox-cont > input + span::after { + left: 30%; + top: 43%; + transform: rotate(60deg); + transform-origin: top left; + width: 40%; +} + +.p-form-button { + -webkit-appearance: none; + background: #fff; + border: 1px solid #cacaca; + border-radius: 5px; + color: #333230; + display: inline-block; + font-size: 17px; + margin: 10px; + padding: 5px 20px; + text-decoration: none; + /* text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); */ +} + +.p-form-send { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border: 0; + color: #fff; +} + +.p-form-send:active { + background: #0f75f5; +} + +.p-form-invalid, +.p-form-invalid:placeholder-shown, +.p-form-invalid:valid, +.p-form-invalid:invalid { + border-color: var(--invalid-color); +} + +.p-form-valid, +.p-form-valid:placeholder-shown, +.p-form-valid:valid, +.p-form-valid:invalid { + border-color: var(--valid-color); +} + +.p-form-switch { + --width: 80px; + cursor: pointer; + display: inline-block; +} + +.p-form-switch > input:checked + span::after { + left: calc(100% - calc(var(--width) / 2.1)); +} + +.p-form-switch > input:checked + span { + background: #60c35b; +} + +.p-form-switch > span { + background: #e0e0e0; + border: 1px solid #d3d3d3; + border-radius: 500px; + display: block; + height: calc(var(--width) / 2); + overflow: hidden; + position: relative; + transition: all 0.2s; + width: var(--width); +} + +.p-form-switch > span::after { + background: #f9f9f9; + border-radius: 50%; + box-shadow: 0px 3px 1px rgba(0, 0, 0, 0.1), 0px 1px 1px rgba(0, 0, 0, 0.16), 0px 3px 8px rgba(0, 0, 0, 0.15); + content: ""; + height: 90%; + left: 3%; + position: absolute; + top: 4.5%; + transition: all 0.2s; + width: 45%; +} + +.p-form-switch > input { + display: none; +} + +input[type=range].p-form-range { + width: 100%; + margin: 11.5px 0; + background-color: transparent; + -webkit-appearance: none; +} +input[type=range].p-form-range:focus { + outline: none; +} +input[type=range].p-form-range::-webkit-slider-runnable-track { + background: #cacaca; + border: 0; + width: 100%; + height: 2px; + cursor: pointer; +} +input[type=range].p-form-range::-webkit-slider-thumb { + margin-top: -11.5px; + width: 25px; + height: 25px; + background: #ffffff; + border: 1px solid rgba(115, 115, 115, 0.6); + border-radius: 30px; + cursor: pointer; + box-shadow: 0 3px 1px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.16), 0 3px 8px rgba(0, 0, 0, 0.15); + -webkit-appearance: none; +} +input[type=range].p-form-range:focus::-webkit-slider-runnable-track { + background: #d7d7d7; +} +input[type=range].p-form-range::-moz-range-track { + background: #cacaca; + border: 0; + width: 100%; + height: 2px; + cursor: pointer; +} +input[type=range].p-form-range::-moz-range-thumb { + width: 25px; + height: 25px; + background: #ffffff; + border: 1px solid rgba(115, 115, 115, 0.6); + border-radius: 30px; + box-shadow: 0 3px 1px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.16), 0 3px 8px rgba(0, 0, 0, 0.15); + cursor: pointer; +} +input[type=range].p-form-range::-ms-track { + background: transparent; + border-color: transparent; + border-width: 26.5px 0; + color: transparent; + width: 100%; + height: 2px; + cursor: pointer; +} +input[type=range].p-form-range::-ms-fill-lower { + background: #bdbdbd; + border: 0; +} +input[type=range].p-form-range::-ms-fill-upper { + background: #cacaca; + border: 0; +} +input[type=range].p-form-range::-ms-thumb { + width: 25px; + height: 25px; + background: #ffffff; + border: 1px solid rgba(115, 115, 115, 0.6); + border-radius: 30px; + cursor: pointer; + box-shadow: 0 3px 1px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.16), 0 3px 8px rgba(0, 0, 0, 0.15); + margin-top: 0px; + /*Needed to keep the Edge thumb centred*/ +} +input[type=range].p-form-range:focus::-ms-fill-lower { + background: #cacaca; +} +input[type=range].p-form-range:focus::-ms-fill-upper { + background: #d7d7d7; +} +/*TODO: Use one of the selectors from https://stackoverflow.com/a/20541859/7077589 and figure out +how to remove the virtical space around the range input in IE*/ +@supports (-ms-ime-align:auto) { + /* Pre-Chromium Edge only styles, selector taken from hhttps://stackoverflow.com/a/32202953/7077589 */ + input[type=range].p-form-range { + margin: 0; + /*Edge starts the margin from the thumb, not the track as other browsers do*/ + } +} + + +/* END OF FORMS */ + +.p-layout .p-large-title { + font-size: 2.75rem; +} + +.p-layout h1 { + font-size: 2.25rem; +} + +.p-layout h2 { + font-size: 1.75rem; +} + +.p-layout h3 { + font-size: 1.58rem; +} + +.p-layout .p-headline { + font-size: 1.34rem; + font-weight: bold; +} + +.p-layout p { + font-size: 1.15rem; +} + +.p-layout a, +.p-layout input { + font-size: 1.14rem; +} + +.p-layout .p-callout { + font-size: 1.14rem; +} + +.p-layout .p-subhead { + font-size: 1.167rem; +} + +.p-layout .p-footnote { + font-size: 1.07rem; +} + +.p-layout .p-caption { + font-size: 0.91rem; +} + +/* END OF LAYOUT */ + +:root { + --font: -apple-system, "Inter", sans-serif; + --bg-hover-color: #f9f9f9; + --primary-col-ac: #0f75f5; +} + +.p-modal-opened { + overflow: hidden; +} + +.p-modal-background { + background: rgba(0, 0, 0, 0.3); + height: 100vh; + left: 0; + opacity: 0; + pointer-events: none; + position: fixed; + top: 0; + transition: all 0.3s; + width: 100vw; + z-index: 5; +} + +.p-modal { + background: rgba(255, 255, 255, 0.85); + border-radius: 20px; + top: calc(50% - 20vh); + bottom: unset; + box-shadow: 0 10px 20px -15px; + font-family: var(--font); + left: calc(50% - 20vw); + opacity: 0; + overflow: hidden; + pointer-events: none; + position: fixed; + text-align: center; + transform: scale(1.5); + transition: opacity 0.3s, transform 0.3s; + width: 40vw; + z-index: 9; +} + +.p-modal.active { + backdrop-filter: saturate(180%) blur(10px); + opacity: 1; + pointer-events: auto; + transform: scale(1); +} + +.p-modal-button-container { + border-radius: 20px; + display: flex; +} + +.p-modal-button-container > a { + border-top: 1px solid rgba(0, 0, 0, 0.1); + color: var(--primary-col-ac); + padding: 30px 0%; + text-decoration: none; + width: 100%; +} + +.p-modal-button-container > a:nth-child(2), +.p-modal-button-container > a:nth-child(3) { + border-left: 1px solid rgba(0, 0, 0, 0.1); +} + +.nowactive { + opacity: 1; + pointer-events: auto; +} + +.p-modal p { + padding: 0% 5%; +} + +@supports not (backdrop-filter: blur(5px)) { + .p-modal { + background: #fff; + } +} +@media (max-width: 568px) { + .p-modal { + bottom: 20%; + left: 15%; + top: unset; + width: 70vw; + } + + .p-modal p { + font-size: 15px; + padding: 0% 10%; + } + + .p-modal-button-container { + display: block; + } + + .p-modal-button-container > a { + border-left: 0 !important; + display: block; + padding: 2vh 0%; + } +} + +/* END OF MODALS */ + +.p-segmented-controls { + --color-segmented: #3689e6; + --color-lighter-segment: #d2e3f9; + background: #fff; + border: 1px solid var(--color-segmented); + border-radius: 5px; + display: flex; + flex-wrap: wrap; + font-family: -apple-system, "Inter", sans-serif; + margin-top: 10px; + overflow: hidden; + width: 100%; +} +.p-segmented-controls a { + color: var(--color-segmented); + flex: auto; + padding: 10px; + text-align: center; + text-decoration: none; + transition: all 0.5s; +} +.p-segmented-controls a.active { + background: var(--color-segmented); + color: #fff; +} +.p-segmented-controls a:not(:first-child) { + border-left: 1px solid currentColor; +} + +.p-segmented-radius { + border-radius: 30px; +} + +.p-segmented-internal-radius a, +.p-segmented-internal-radius a:not(:first-child) { + border: 0; + border-radius: 30px; +} + +.p-segmented-controls-alt a:not(:first-child) { + border: 0; +} +.p-segmented-controls-alt a:not(:first-child).active { + background: var(--color-lighter-segment); + color: var(--color-segmented); + font-weight: bold; +} + +.p-segmented-outline { + border: 2px solid var(--color-segmented); +} +.p-segmented-outline a:not(:first-child) { + border-left: 2px solid var(--color-segmented); +} + +.p-segmented-controls-outline-alt a:not(:first-child) { + border: 2px solid transparent; +} + +.p-segmented-controls-outline-alt { + border-radius: 30px; +} +.p-segmented-controls-outline-alt a { + border: 2px solid transparent; + border-radius: 30px; +} +.p-segmented-controls-outline-alt a.active { + background: #fff; + border-color: var(--color-segmented); + border-radius: 30px; + color: var(--color-segmented); + font-weight: bold; +} + +.p-segmented-grey { + --color-segmented: #555761; + --color-lighter-segment: #d4d4d4; +} + +/* END OF SEGMENTED CONTROLS */ + +.p-shadow-1 { + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); +} + +.p-shadow-2 { + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); +} + +.p-shadow-3 { + box-shadow: 0 10px 18px rgba(0, 0, 0, 0.3); +} + +.p-shadow-4 { + box-shadow: 0 25px 30px rgba(0, 0, 0, 0.2); +} + +.p-to-shadow-4, +.p-to-shadow-3, +.p-to-shadow-2, +.p-to-shadow-1 { + transition-timing-function: ease; + transition: box-shadow 0.5s; +} + +.p-to-shadow-1:hover { + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);> +} + +.p-to-shadow-2:hover { + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); +} + +.p-to-shadow-3:hover { + box-shadow: 0 10px 18px rgba(0, 0, 0, 0.3); +} + +.p-to-shadow-4:hover { + box-shadow: 0 25px 30px rgba(0, 0, 0, 0.2); +} + + +/* END OF SHADOWS */ + +.p-tabs-container { + background: #e3e3e3; + border: 1px solid #e0e0e0; + padding: 1em; +} + +.p-tabs-container.p-light { + background: none; + border: none; +} + +.p-tabs-container.p-light .p-panels { + margin-top: 0; + border-radius: 0; + padding: 0; +} + +.p-tabs { + display: flex; + justify-content: center; +} + +.p-tabs > :nth-of-type(1) { + border-radius: 5px 0 0 5px; +} + +.p-tabs > :last-child { + border-radius: 0 5px 5px 0; +} + +.p-tab { + margin: 0; + padding: 5px 35px; + background: #fff; + color: #333230; + text-decoration: none; + /* text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); */ + border: 1px solid #cacaca; + display: inline-block; + font-size: 17px; + font-family: -apple-system, "Inter", sans-serif; + cursor: pointer; +} + +.p-tab:focus { + outline: 0; +} + +.p-is-active { + background: linear-gradient(to bottom, #4fc5fa 0%, #0f75f5 100%); + border: 0; + color: #fff; +} + +.p-panels { + margin-top: 1em; + background: #fff; + border-radius: 3px; + position: relative; + padding: 0.8em; + overflow: hidden; +} + +.p-panel.p-is-active { + opacity: 1; + pointer-events: all; + background: none; + color: inherit; + position: static; +} + +.p-panel { + position: absolute; + opacity: 0; + pointer-events: none; +} + +@media (max-width: 768px) { + .p-tabs { + overflow: auto; + } + .p-tab { + font-size: 0.8em; + padding: 5px 28px; + } + .p-tabs-container { + padding: 0.8em; + } + + .p-panels { + padding: 0.8em; + } +} + +@media screen and (max-width: 496px) { + .p-tab { + text-align: center; + padding: 5px 18px; + } + .p-tabs-container { + padding: 0.5em; + } + + .p-panels { + padding: 0.5em; + margin-top: 0.5em; + } +} + +@media screen and (max-width: 378px) { + .p-tab { + text-align: center; + padding: 5px 10px; + } + .p-tabs-container { + padding: 0.5em; + } + + .p-panels { + padding: 0.5em; + margin-top: 0.5; + } +} + +.p-mobile-tabs { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + padding: 15px 0px; + border-top: 1px solid #949494; + background: rgba(202, 202, 202, 0.8); + backdrop-filter: blur(10px); + display: flex; + font-family: -apple-system, "Inter", sans-serif; +} + +.p-mobile-tabs > div { + flex: auto; + text-align: center; +} + +.p-mobile-tabs a { + text-decoration: none; + color: #555; + transition: color 0.5s; + display: inline-block; + font-size: 0.8rem; +} + +.p-mobile-tabs a.active { + color: #0f75f5; + font-weight: 600; +} + +.p-mobile-tabs svg { + display: block; + margin: auto; + margin-bottom: 0.2rem; +} + +.p-mobile-tabs--content { + display: none; +} + +.p-mobile-tabs--content.active { + display: block; +} + +/* END OF TABS */ + + +.p-action-background{ + background: rgba(0, 0, 0, 0.3); + height: 100vh; + left: 0; + opacity: 0; + pointer-events: none; + position: fixed; + top: 0; + transition: all 0.3s; + width: 100vw; + z-index: 5; +} + +.p-action-background.nowactive { + opacity: 1; + pointer-events: auto; +} + + +.p-action-big-container{ + position:fixed; + width: 100%; + box-sizing: border-box; + padding: 1rem 5vw; + bottom:0; +} + +.p-action-container{ + background: rgba(255, 255, 255, 0.8); + backdrop-filter: blur(10px); + display:block; + margin:auto; + margin-bottom: 10px; + border-radius: 10px; +} + +.p-action-big-container .p-action-container:first-child{ + margin-bottom:10px; +} + +.p-action--intern{ + display:block; + margin:auto; + text-align:center; + padding: 15px 0; + border-bottom: 1px solid #bfbfbf; + font-weight: 500; + color: #0f75f5; + text-decoration:none; +} + +.p-action-destructive{ + color: #c6262e; +} + +.p-action-neutral{ + color: #555761; +} + +.p-action-cancel, .p-action-container a:last-child{ + border-bottom:none; +} + +.p-action-cancel{ + font-weight:bold; +} + +.p-action-icon{ + position:relative; +} +.p-action-icon svg, .p-action-icon img{ + position:absolute; + left:5%; + top:50%; + transform:translateY(-50%); +} + +.p-action-icon-inline{ + text-align: left; + display: flex; + align-items: center; +} + +.p-action-icon-inline svg, .p-action-icon-inline img{ + margin-left: 5%; + margin-right: 3%; +} + +.p-action-title{ + padding: 30px 15px; + border-bottom: 1px solid #bfbfbf; +} + +.p-action-title--intern,.p-action-text{ + margin:0; + color:#555761; +} + +.p-action-title--intern{ + margin-bottom: .3rem; +} + +@supports not (backdrop-filter: blur(10px)) { + .p-action-container { + background: rgba(255,255,255,.95); + } +} + +.p-action-big-container{ + -webkit-transform: translateY(30%); + transform: translateY(30%); + opacity: 0; + transition: opacity 0.4s, transform 0.4s; + transition-timing-function: ease-in-out; +} + +.p-action-big-container.active { +-webkit-transform: translateY(0); + transform: translateY(0); + opacity: 1; +} + + +/* END OF ACTIONS */ diff --git a/v3/internal/templates/ios/frontend/public/style.css b/v3/internal/templates/ios/frontend/public/style.css new file mode 100644 index 000000000..72620de1c --- /dev/null +++ b/v3/internal/templates/ios/frontend/public/style.css @@ -0,0 +1,261 @@ +:root { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, + sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + /* Desktop defaults (mobile overrides below) */ + --bg: #1C222F; /* rgb(28,34,47) */ + --fg: rgba(255,255,255,0.88); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +*, *::before, *::after { + box-sizing: border-box; +} + +/* Prefer system fonts on mobile; remove custom font to reduce bundle size */ + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +/* Remove generic button styling to allow Puppertino .btn to control buttons */ + +.result { + height: 20px; + line-height: 20px; +} + +html, +body { + height: 100%; + width: 100%; + overflow-x: hidden; /* prevent horizontal overflow */ + overflow-y: auto; /* allow vertical scroll if needed */ +} + +body { + margin: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-width: 320px; + /* Use small viewport units to avoid iOS Safari URL bar issues */ + min-height: 100svh; + height: auto; /* avoid forcing overflow */ + /* Equal responsive spacing top & bottom */ + padding-block: clamp(8px, 4vh, 48px); + color: var(--fg); + background-color: var(--bg); +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + /* Responsive spacing between elements */ + gap: clamp(8px, 2vh, 24px); + width: 100%; + max-width: 480px; + padding-inline: 16px; +} + +h1 { + /* Responsive heading size */ + font-size: clamp(1.6rem, 6vw, 3.2rem); + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + /* Responsive inner padding: horizontal only, no extra top/bottom */ + padding: 0 clamp(12px, 4vw, 32px); + text-align: center; +} + +.logo { + /* Consistent visual size across images: fix height, auto width */ + height: clamp(80px, 18vh, 140px); + width: auto; + max-width: 80vw; + padding: 0.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +/* Mobile-specific light mode */ +@media (max-width: 768px) and (prefers-color-scheme: light) { + :root { + --fg: rgba(255,255,255,0.88); + --bg: #1C222F; /* rgb(28,34,47) */ + } + + a:hover { + color: #747bff; + } + + /* allow Puppertino to style .btn */ + + .input-box .input { + color: #111827; + background-color: #f3f4f6; + border: 1px solid #e5e7eb; /* show border in light mode */ + border-radius: 8px; + } + + button:hover { + border-color: #d1d5db; /* slate-300 */ + } + + .input-box .input:focus { + border-color: #9ca3af; /* gray-400 */ + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); /* subtle focus ring */ + } +} + +/* let Puppertino handle .btn hover */ + +.input-box .input { + border: 1px solid transparent; /* default; themed in media queries */ + border-radius: 8px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + background-color: rgba(255, 255, 255, 1); + outline: 2px solid transparent; + outline-offset: 2px; +} + +/* Mobile-specific dark mode */ +@media (max-width: 768px) and (prefers-color-scheme: dark) { + :root { + color: rgba(255, 255, 255, 0.88); + --fg: rgba(255, 255, 255, 0.88); + --bg: #1C222F; /* rgb(28,34,47) */ + } + + a { + color: #8ea2ff; + } + + a:hover { + color: #aab6ff; + } + + /* allow Puppertino to style .btn in dark mode */ + + .input-box .input { + background-color: #111827; /* gray-900 */ + color: #e5e7eb; + caret-color: #ffffff; + border: 1px solid #374151; /* slate-700 */ + } + + .input-box .input:hover, + .input-box .input:focus { + background-color: #0b1220; + border-color: #4b5563; /* slate-600 */ + } + + /* allow Puppertino to handle active state */ +} + +/* Mobile baseline overrides (apply to both light and dark) */ +@media (max-width: 768px) { + /* Prevent iOS zoom on focus */ + input, textarea, select, button { font-size: 16px; } + + /* let Puppertino define .btn sizing */ + + /* Align input with button and center text nicely */ + .input-box { + display: flex; + align-items: center; + gap: 8px; + } + + .input-box .input { + height: 36px; /* slightly shorter to match button */ + line-height: 1.2; + padding: 0 10px; + } + + /* Lock viewport to device height and remove overflow on mobile */ + html, body { + height: 100dvh; + min-height: 100dvh; + overflow: hidden; + overscroll-behavior: none; /* disable scroll chaining/bounce */ + -webkit-overflow-scrolling: auto; /* avoid momentum scrolling */ + } + + body { + padding-block: 0; /* avoid extra height from block padding */ + padding-top: env(safe-area-inset-top); + padding-bottom: env(safe-area-inset-bottom); + height: 100dvh; + min-height: 100dvh; + position: fixed; /* lock body to viewport */ + inset: 0; /* fill viewport */ + } + + .container { + min-height: calc(100dvh - env(safe-area-inset-top) - env(safe-area-inset-bottom)); + justify-content: center; /* vertical center without needing extra padding */ + } +} \ No newline at end of file diff --git a/v3/internal/templates/ios/frontend/public/wails.png b/v3/internal/templates/ios/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/ios/frontend/public/wails.png differ diff --git a/v3/internal/templates/ios/template.json b/v3/internal/templates/ios/template.json new file mode 100644 index 000000000..9780ef660 --- /dev/null +++ b/v3/internal/templates/ios/template.json @@ -0,0 +1,9 @@ +{ + "name": "iOS Vanilla (Puppertino) + Vite", + "shortname": "ios", + "author": "Lea Anthony", + "description": "Vanilla + Vite with iOS-friendly styling and bundled Puppertino", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/lit-ts/frontend/.gitignore b/v3/internal/templates/lit-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/lit-ts/frontend/index.html b/v3/internal/templates/lit-ts/frontend/index.html new file mode 100644 index 000000000..d01a04294 --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/index.html @@ -0,0 +1,16 @@ + + + + + + + Wails + Lit + TS + + + + + +

        Wails + Lit

        +
        + + diff --git a/v3/internal/templates/lit-ts/frontend/package.json b/v3/internal/templates/lit-ts/frontend/package.json new file mode 100644 index 000000000..d208c3fbd --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "tsc && vite build --minify false --mode development", + "build": "tsc && vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "lit": "^3.1.0" + }, + "devDependencies": { + "typescript": "^5.2.2", + "vite": "^5.0.0" + } +} diff --git a/v3/internal/templates/lit-ts/frontend/public/Inter-Medium.ttf b/v3/internal/templates/lit-ts/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/lit-ts/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/lit-ts/frontend/public/lit.svg b/v3/internal/templates/lit-ts/frontend/public/lit.svg new file mode 100644 index 000000000..4a9c1fe66 --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/public/lit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/lit-ts/frontend/public/style.css b/v3/internal/templates/lit-ts/frontend/public/style.css new file mode 100644 index 000000000..10550a378 --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/public/style.css @@ -0,0 +1,58 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} \ No newline at end of file diff --git a/v3/internal/templates/lit-ts/frontend/public/wails.png b/v3/internal/templates/lit-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/lit-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/lit-ts/frontend/src/my-element.ts.tmpl b/v3/internal/templates/lit-ts/frontend/src/my-element.ts.tmpl new file mode 100644 index 000000000..974f812fd --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/src/my-element.ts.tmpl @@ -0,0 +1,174 @@ +import {css, html, LitElement} from 'lit' +import {customElement, property} from 'lit/decorators.js' +import {Events} from "@wailsio/runtime"; +import {GreetService} from '../bindings/{{js .ModulePath}}'; + +/** + * An example element. + * + * @slot - This element has a slot + * @csspart button - The button + */ +@customElement('my-element') +export class MyElement extends LitElement { + + @property() + result: string = 'Please enter your name below 👇' + + @property() + time: string = 'Listening for Time event...' + + @property() + name: string = ''; + + constructor() { + super(); + Events.On('time', (timeValue: { data: string }) => { + this.time = timeValue.data; + }); + } + + + doGreet() { + let name = this.name; + if (!name) { + name = 'anonymous'; + } + GreetService.Greet(name).then((resultValue: string) => { + this.result = resultValue; + }).catch((err: Error) => { + console.log(err); + }); + } + + render() { + return html` +
        + + +
        ${this.result}
        +
        +
        + this.name = (e.target as HTMLInputElement).value} type="text" + autocomplete="off"/> + +
        +
        + +
        + ` + } + + + static styles = css` + :host { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; + } + + h3 { + font-size: 3em; + line-height: 1.1; + } + + a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; + } + + a:hover { + color: #535bf2; + } + + button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; + } + + .container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + + .logo { + height: 6em; + padding: 1.5em; + will-change: filter; + } + + .logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); + } + + .logo.lit:hover { + filter: drop-shadow(0 0 2em #325cffaa); + } + + .result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; + } + + .footer { + margin-top: 1rem; + align-content: center; + text-align: center; + } + + .input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; + } + + .input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; + } + + .input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); + } + + .input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + 'my-element': MyElement + } +} diff --git a/v3/internal/templates/lit-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/lit-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/lit-ts/frontend/tsconfig.json b/v3/internal/templates/lit-ts/frontend/tsconfig.json new file mode 100644 index 000000000..6badd7348 --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "bindings"] +} diff --git a/v3/internal/templates/lit-ts/frontend/vite.config.ts b/v3/internal/templates/lit-ts/frontend/vite.config.ts new file mode 100644 index 000000000..56f2d6a8a --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [wails("./bindings")], +}); diff --git a/v3/internal/templates/lit-ts/template.json b/v3/internal/templates/lit-ts/template.json new file mode 100644 index 000000000..ea0bf57f9 --- /dev/null +++ b/v3/internal/templates/lit-ts/template.json @@ -0,0 +1,9 @@ +{ + "name": "Lit + Vite (Typescript)", + "shortname": "lit-ts", + "author": "Lea Anthony", + "description": "Lit + TS + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/lit/frontend/.gitignore b/v3/internal/templates/lit/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/lit/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/lit/frontend/index.html b/v3/internal/templates/lit/frontend/index.html new file mode 100644 index 000000000..7993cbcef --- /dev/null +++ b/v3/internal/templates/lit/frontend/index.html @@ -0,0 +1,16 @@ + + + + + + + Wails + Lit + + + + + +

        Wails + Lit

        +
        + + diff --git a/v3/internal/templates/lit/frontend/package.json b/v3/internal/templates/lit/frontend/package.json new file mode 100644 index 000000000..ec30e751a --- /dev/null +++ b/v3/internal/templates/lit/frontend/package.json @@ -0,0 +1,19 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "lit": "^3.1.0" + }, + "devDependencies": { + "vite": "^5.0.0" + } +} diff --git a/v3/internal/templates/lit/frontend/public/Inter-Medium.ttf b/v3/internal/templates/lit/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/lit/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/lit/frontend/public/lit.svg b/v3/internal/templates/lit/frontend/public/lit.svg new file mode 100644 index 000000000..4a9c1fe66 --- /dev/null +++ b/v3/internal/templates/lit/frontend/public/lit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/lit/frontend/public/style.css b/v3/internal/templates/lit/frontend/public/style.css new file mode 100644 index 000000000..10550a378 --- /dev/null +++ b/v3/internal/templates/lit/frontend/public/style.css @@ -0,0 +1,58 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} \ No newline at end of file diff --git a/v3/internal/templates/lit/frontend/public/wails.png b/v3/internal/templates/lit/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/lit/frontend/public/wails.png differ diff --git a/v3/internal/templates/lit/frontend/src/my-element.js.tmpl b/v3/internal/templates/lit/frontend/src/my-element.js.tmpl new file mode 100644 index 000000000..db7763268 --- /dev/null +++ b/v3/internal/templates/lit/frontend/src/my-element.js.tmpl @@ -0,0 +1,159 @@ +import {css, html, LitElement} from 'lit' +import {Events} from "@wailsio/runtime"; +import {GreetService} from "../bindings/{{js .ModulePath}}"; + +export class MyElement extends LitElement { + static properties = { + name: {type: String}, + result: {type: String}, + time: {type: String}, + }; + + constructor() { + super(); + this.name = ''; + this.result = 'Please enter your name below 👇'; + this.time = 'Listening for Time event...'; + Events.On('time', (timeValue) => { + this.time = timeValue.data; + }); + } + + doGreet() { + let name = this.name; + if (!name) { + name = 'anonymous'; + } + GreetService.Greet(name).then((resultValue) => { + this.result = resultValue; + }).catch((err) => { + console.log(err); + }); + } + + render() { + return html` +
        + + +
        ${this.result}
        +
        +
        + this.name = e.target.value} type="text" + autocomplete="off"/> + +
        +
        + +
        + `; + } + + static styles = css` + :host { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; + } + + h3 { + font-size: 3em; + line-height: 1.1; + } + + a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; + } + + a:hover { + color: #535bf2; + } + + button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; + } + + .container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + + .logo { + height: 6em; + padding: 1.5em; + will-change: filter; + } + + .logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); + } + + .logo.lit:hover { + filter: drop-shadow(0 0 2em #325cffaa); + } + + .result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; + } + + .footer { + margin-top: 1rem; + align-content: center; + text-align: center; + } + + .input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; + } + + .input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; + } + + .input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); + } + + .input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); + } + `; +} + +window.customElements.define('my-element', MyElement); diff --git a/v3/internal/templates/lit/frontend/src/vite-env.d.ts b/v3/internal/templates/lit/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/lit/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/lit/frontend/tsconfig.json b/v3/internal/templates/lit/frontend/tsconfig.json new file mode 100644 index 000000000..f103fe3a9 --- /dev/null +++ b/v3/internal/templates/lit/frontend/tsconfig.json @@ -0,0 +1,25 @@ +/** + * This file tells your IDE where the root of your JavaScript project is, and sets some + * options that it can use to provide autocompletion and other features. + */ +{ + "compilerOptions": { + "allowJs": true, + "moduleResolution": "bundler", + /** + * The target and module can be set to ESNext to allow writing modern JavaScript, + * and Vite will compile down to the level of "build.target" specified in the vite config file. + * Builds will error if you use a feature that cannot be compiled down to the target level. + */ + "target": "ESNext", + "module": "ESNext", + "resolveJsonModule": true, + /** + * Enable checkJs if you'd like type checking in `.js` files. + */ + "checkJs": false, + "strict": true, + "skipLibCheck": true, + }, + "include": ["src", "bindings"] +} diff --git a/v3/internal/templates/lit/frontend/vite.config.js b/v3/internal/templates/lit/frontend/vite.config.js new file mode 100644 index 000000000..56f2d6a8a --- /dev/null +++ b/v3/internal/templates/lit/frontend/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [wails("./bindings")], +}); diff --git a/v3/internal/templates/lit/template.json b/v3/internal/templates/lit/template.json new file mode 100644 index 000000000..9c3ba3c61 --- /dev/null +++ b/v3/internal/templates/lit/template.json @@ -0,0 +1,9 @@ +{ + "name": "Lit + Vite", + "shortname": "lit", + "author": "Lea Anthony", + "description": "Lit + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/preact-ts/frontend/.gitignore b/v3/internal/templates/preact-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/preact-ts/frontend/index.html b/v3/internal/templates/preact-ts/frontend/index.html new file mode 100644 index 000000000..f4addcc25 --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + Wails + Preact + + +
        + + + diff --git a/v3/internal/templates/preact-ts/frontend/package.json b/v3/internal/templates/preact-ts/frontend/package.json new file mode 100644 index 000000000..b5dd75296 --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "tsc && vite build --minify false --mode development", + "build": "tsc && vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "preact": "^10.19.3" + }, + "devDependencies": { + "@preact/preset-vite": "^2.7.0", + "typescript": "^5.2.2", + "vite": "^5.0.8" + } +} diff --git a/v3/internal/templates/preact-ts/frontend/public/Inter-Medium.ttf b/v3/internal/templates/preact-ts/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/preact-ts/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/preact-ts/frontend/public/preact.svg b/v3/internal/templates/preact-ts/frontend/public/preact.svg new file mode 100644 index 000000000..908f17def --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/public/preact.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/preact-ts/frontend/public/style.css b/v3/internal/templates/preact-ts/frontend/public/style.css new file mode 100644 index 000000000..c4f073382 --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/public/style.css @@ -0,0 +1,158 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.preact:hover { + filter: drop-shadow(0 0 2em #673ab8aa); +} + + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/preact-ts/frontend/public/wails.png b/v3/internal/templates/preact-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/preact-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/preact-ts/frontend/src/app.tsx.tmpl b/v3/internal/templates/preact-ts/frontend/src/app.tsx.tmpl new file mode 100644 index 000000000..f8e992d34 --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/src/app.tsx.tmpl @@ -0,0 +1,55 @@ +import {useEffect, useState} from 'preact/hooks' +import {Events} from "@wailsio/runtime"; +import {GreetService} from "../bindings/{{js .ModulePath}}"; + +export function App() { + const [name, setName] = useState(''); + const [result, setResult] = useState('Please enter your name below 👇'); + const [time, setTime] = useState('Listening for Time event...'); + + const doGreet = (): void => { + let localName = name; + if (!localName) { + localName = 'anonymous'; + } + GreetService.Greet(localName).then((resultValue: string) => { + setResult(resultValue); + }).catch((err: any) => { + console.log(err); + }); + } + + useEffect(() => { + Events.On('time', (timeValue: any) => { + setTime(timeValue.data); + }); + }, []); + + return ( + <> +
        + +

        Wails + Preact

        +
        {result}
        +
        +
        + setName(e.currentTarget.value)} + type="text" autocomplete="off"/> + +
        +
        +
        +

        Click on the Wails logo to learn more

        +

        {time}

        +
        +
        + + ) +} diff --git a/v3/internal/templates/preact-ts/frontend/src/main.tsx b/v3/internal/templates/preact-ts/frontend/src/main.tsx new file mode 100644 index 000000000..2af1859fe --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/src/main.tsx @@ -0,0 +1,4 @@ +import { render } from 'preact' +import { App } from './app' + +render(, document.getElementById('app') as HTMLElement) diff --git a/v3/internal/templates/preact-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/preact-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/preact-ts/frontend/tsconfig.json b/v3/internal/templates/preact-ts/frontend/tsconfig.json new file mode 100644 index 000000000..f109102b4 --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + }, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "bindings"], +} diff --git a/v3/internal/templates/preact-ts/frontend/vite.config.ts b/v3/internal/templates/preact-ts/frontend/vite.config.ts new file mode 100644 index 000000000..51b2dce5c --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; +import preact from "@preact/preset-vite"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact(), wails("./bindings")], +}); diff --git a/v3/internal/templates/preact-ts/template.json b/v3/internal/templates/preact-ts/template.json new file mode 100644 index 000000000..e2b867ebc --- /dev/null +++ b/v3/internal/templates/preact-ts/template.json @@ -0,0 +1,9 @@ +{ + "name": "Preact + Vite (Typescript)", + "shortname": "preact-ts", + "author": "Lea Anthony", + "description": "Preact + TS + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/preact/frontend/.gitignore b/v3/internal/templates/preact/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/preact/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/preact/frontend/index.html b/v3/internal/templates/preact/frontend/index.html new file mode 100644 index 000000000..3657fd5ef --- /dev/null +++ b/v3/internal/templates/preact/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + Wails + Preact + + +
        + + + diff --git a/v3/internal/templates/preact/frontend/package.json b/v3/internal/templates/preact/frontend/package.json new file mode 100644 index 000000000..863d1fc23 --- /dev/null +++ b/v3/internal/templates/preact/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "preact": "^10.19.3" + }, + "devDependencies": { + "@preact/preset-vite": "^2.7.0", + "vite": "^5.0.8" + } +} diff --git a/v3/internal/templates/preact/frontend/public/Inter-Medium.ttf b/v3/internal/templates/preact/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/preact/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/preact/frontend/public/preact.svg b/v3/internal/templates/preact/frontend/public/preact.svg new file mode 100644 index 000000000..908f17def --- /dev/null +++ b/v3/internal/templates/preact/frontend/public/preact.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/preact/frontend/public/style.css b/v3/internal/templates/preact/frontend/public/style.css new file mode 100644 index 000000000..c4f073382 --- /dev/null +++ b/v3/internal/templates/preact/frontend/public/style.css @@ -0,0 +1,158 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.preact:hover { + filter: drop-shadow(0 0 2em #673ab8aa); +} + + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/preact/frontend/public/wails.png b/v3/internal/templates/preact/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/preact/frontend/public/wails.png differ diff --git a/v3/internal/templates/preact/frontend/src/app.jsx.tmpl b/v3/internal/templates/preact/frontend/src/app.jsx.tmpl new file mode 100644 index 000000000..45102e3ef --- /dev/null +++ b/v3/internal/templates/preact/frontend/src/app.jsx.tmpl @@ -0,0 +1,52 @@ +import { useState, useEffect } from 'preact/hooks' +import {Events} from "@wailsio/runtime"; +import {GreetService} from "../bindings/{{js .ModulePath}}"; + +export function App() { + const [name, setName] = useState(''); + const [result, setResult] = useState('Please enter your name below 👇'); + const [time, setTime] = useState('Listening for Time event...'); + + const doGreet = () => { + let localName = name; + if (!localName) { + localName = 'anonymous'; + } + GreetService.Greet(localName).then((resultValue) => { + setResult(resultValue); + }).catch((err) => { + console.log(err); + }); + } + + useEffect(() => { + Events.On('time', (timeValue) => { + setTime(timeValue.data); + }); + }, []); + + return ( +
        + +

        Wails + Preact

        +
        {result}
        +
        +
        + setName(e.target.value)} type="text" autoComplete="off"/> + +
        +
        +
        +

        Click on the Wails logo to learn more

        +

        {time}

        +
        +
        + ) +} diff --git a/v3/internal/templates/preact/frontend/src/main.jsx b/v3/internal/templates/preact/frontend/src/main.jsx new file mode 100644 index 000000000..5867d2a14 --- /dev/null +++ b/v3/internal/templates/preact/frontend/src/main.jsx @@ -0,0 +1,4 @@ +import { render } from 'preact' +import { App } from './app' + +render(, document.getElementById('app')) diff --git a/v3/internal/templates/preact/frontend/src/vite-env.d.ts b/v3/internal/templates/preact/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/preact/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/preact/frontend/tsconfig.json b/v3/internal/templates/preact/frontend/tsconfig.json new file mode 100644 index 000000000..2be606f10 --- /dev/null +++ b/v3/internal/templates/preact/frontend/tsconfig.json @@ -0,0 +1,25 @@ +/** + * This file tells your IDE where the root of your JavaScript project is, and sets some + * options that it can use to provide autocompletion and other features. + */ +{ + "compilerOptions": { + "allowJs": true, + "moduleResolution": "bundler", + /** + * The target and module can be set to ESNext to allow writing modern JavaScript, + * and Vite will compile down to the level of "build.target" specified in the vite config file. + * Builds will error if you use a feature that cannot be compiled down to the target level. + */ + "target": "ESNext", + "module": "ESNext", + "resolveJsonModule": true, + /** + * Enable checkJs if you'd like type checking in `.js(x)` files. + */ + "checkJs": false, + "strict": true, + "skipLibCheck": true, + }, + "include": ["src", "bindings"] +} diff --git a/v3/internal/templates/preact/frontend/vite.config.js b/v3/internal/templates/preact/frontend/vite.config.js new file mode 100644 index 000000000..51b2dce5c --- /dev/null +++ b/v3/internal/templates/preact/frontend/vite.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; +import preact from "@preact/preset-vite"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact(), wails("./bindings")], +}); diff --git a/v3/internal/templates/preact/template.json b/v3/internal/templates/preact/template.json new file mode 100644 index 000000000..c2e58d779 --- /dev/null +++ b/v3/internal/templates/preact/template.json @@ -0,0 +1,9 @@ +{ + "name": "Preact + Vite", + "shortname": "preact", + "author": "Lea Anthony", + "description": "Preact + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/qwik-ts/frontend/.gitignore b/v3/internal/templates/qwik-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/qwik-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/qwik-ts/frontend/index.html b/v3/internal/templates/qwik-ts/frontend/index.html new file mode 100644 index 000000000..b45ac182d --- /dev/null +++ b/v3/internal/templates/qwik-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Qwik + + +
        + + + diff --git a/v3/internal/templates/qwik-ts/frontend/package.json b/v3/internal/templates/qwik-ts/frontend/package.json new file mode 100644 index 000000000..b3f359a6b --- /dev/null +++ b/v3/internal/templates/qwik-ts/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "quik-ts-latest", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "tsc && vite build --minify false --mode development", + "build": "tsc && vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@builder.io/qwik": "^1.3.0", + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "typescript": "^5.2.2", + "vite": "^5.0.8" + } +} diff --git a/v3/internal/templates/qwik-ts/frontend/public/Inter-Medium.ttf b/v3/internal/templates/qwik-ts/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/qwik-ts/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/qwik-ts/frontend/public/qwik.svg b/v3/internal/templates/qwik-ts/frontend/public/qwik.svg new file mode 100644 index 000000000..08a46e2da --- /dev/null +++ b/v3/internal/templates/qwik-ts/frontend/public/qwik.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/v3/internal/templates/qwik-ts/frontend/public/style.css b/v3/internal/templates/qwik-ts/frontend/public/style.css new file mode 100644 index 000000000..c1d8d1a2e --- /dev/null +++ b/v3/internal/templates/qwik-ts/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.qwik:hover { + filter: drop-shadow(0 0 2em #673ab8aa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/qwik-ts/frontend/public/wails.png b/v3/internal/templates/qwik-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/qwik-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/qwik-ts/frontend/src/app.tsx.tmpl b/v3/internal/templates/qwik-ts/frontend/src/app.tsx.tmpl new file mode 100644 index 000000000..89f38eb25 --- /dev/null +++ b/v3/internal/templates/qwik-ts/frontend/src/app.tsx.tmpl @@ -0,0 +1,54 @@ +import { component$, useSignal, useVisibleTask$ } from '@builder.io/qwik' +import {Events, WML} from "@wailsio/runtime"; +import {GreetService} from "../bindings/{{js .ModulePath}}"; + +export const App = component$(() => { + const name = useSignal(''); + const result = useSignal('Please enter your name below 👇'); + const time = useSignal('Listening for Time event...'); + + const doGreet = () => { + let localName = name.value; + if (!localName) { + localName = 'anonymous'; + } + GreetService.Greet(localName).then((resultValue: string) => { + result.value = resultValue; + }).catch((err: any) => { + console.log(err); + }); + } + + useVisibleTask$(() => { + Events.On('time', (timeValue: any) => { + time.value = timeValue.data; + }); + // Reload WML so it picks up the wml tags + WML.Reload(); + }); + + return ( +
        + +

        Wails + Qwik

        +
        {result.value}
        +
        +
        + name.value = (e.target as HTMLInputElement).value} type="text" autocomplete="off"/> + +
        +
        + +
        + ) +}) diff --git a/v3/internal/templates/qwik-ts/frontend/src/main.tsx b/v3/internal/templates/qwik-ts/frontend/src/main.tsx new file mode 100644 index 000000000..2c779e49e --- /dev/null +++ b/v3/internal/templates/qwik-ts/frontend/src/main.tsx @@ -0,0 +1,6 @@ +import '@builder.io/qwik/qwikloader.js' + +import { render } from '@builder.io/qwik' +import { App } from './app.tsx' + +render(document.getElementById('app') as HTMLElement, ) diff --git a/v3/internal/templates/qwik-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/qwik-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/qwik-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/qwik-ts/frontend/tsconfig.json b/v3/internal/templates/qwik-ts/frontend/tsconfig.json new file mode 100644 index 000000000..2c7f9d75e --- /dev/null +++ b/v3/internal/templates/qwik-ts/frontend/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "jsxImportSource": "@builder.io/qwik", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "bindings"], +} diff --git a/v3/internal/templates/qwik-ts/frontend/vite.config.ts b/v3/internal/templates/qwik-ts/frontend/vite.config.ts new file mode 100644 index 000000000..3ea402c7b --- /dev/null +++ b/v3/internal/templates/qwik-ts/frontend/vite.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "vite"; +import { qwikVite } from "@builder.io/qwik/optimizer"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + qwikVite({ + csr: true, + }), + wails("./bindings"), + ], +}); diff --git a/v3/internal/templates/qwik-ts/template.json b/v3/internal/templates/qwik-ts/template.json new file mode 100644 index 000000000..fd27f0b0a --- /dev/null +++ b/v3/internal/templates/qwik-ts/template.json @@ -0,0 +1,9 @@ +{ + "name": "Qwik + TS + Vite", + "shortname": "qwik", + "author": "Lea Anthony", + "description": "Qwik + TS + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/qwik/frontend/.gitignore b/v3/internal/templates/qwik/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/qwik/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/qwik/frontend/index.html b/v3/internal/templates/qwik/frontend/index.html new file mode 100644 index 000000000..7c6c2a226 --- /dev/null +++ b/v3/internal/templates/qwik/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + Vite + Qwik + + +
        + + + diff --git a/v3/internal/templates/qwik/frontend/package.json b/v3/internal/templates/qwik/frontend/package.json new file mode 100644 index 000000000..3139e426b --- /dev/null +++ b/v3/internal/templates/qwik/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "qwik-latest", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@builder.io/qwik": "^1.3.0", + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "typescript": "^5.2.2", + "vite": "^5.0.8" + } +} diff --git a/v3/internal/templates/qwik/frontend/public/Inter-Medium.ttf b/v3/internal/templates/qwik/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/qwik/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/qwik/frontend/public/qwik.svg b/v3/internal/templates/qwik/frontend/public/qwik.svg new file mode 100644 index 000000000..08a46e2da --- /dev/null +++ b/v3/internal/templates/qwik/frontend/public/qwik.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/v3/internal/templates/qwik/frontend/public/style.css b/v3/internal/templates/qwik/frontend/public/style.css new file mode 100644 index 000000000..c1d8d1a2e --- /dev/null +++ b/v3/internal/templates/qwik/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.qwik:hover { + filter: drop-shadow(0 0 2em #673ab8aa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/qwik/frontend/public/wails.png b/v3/internal/templates/qwik/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/qwik/frontend/public/wails.png differ diff --git a/v3/internal/templates/qwik/frontend/src/app.jsx.tmpl b/v3/internal/templates/qwik/frontend/src/app.jsx.tmpl new file mode 100644 index 000000000..004a54ace --- /dev/null +++ b/v3/internal/templates/qwik/frontend/src/app.jsx.tmpl @@ -0,0 +1,54 @@ +import { component$, useSignal, useVisibleTask$ } from '@builder.io/qwik' +import {Events, WML} from "@wailsio/runtime"; +import {GreetService} from "../bindings/{{js .ModulePath}}"; + +export const App = component$(() => { + const name = useSignal(''); + const result = useSignal('Please enter your name below 👇'); + const time = useSignal('Listening for Time event...'); + + const doGreet = () => { + let localName = name.value; + if (!localName) { + localName = 'anonymous'; + } + GreetService.Greet(localName).then((resultValue) => { + result.value = resultValue; + }).catch((err) => { + console.log(err); + }); + } + + useVisibleTask$(() => { + Events.On('time', (timeValue) => { + time.value = timeValue.data; + }); + // Reload WML so it picks up the wml tags + WML.Reload(); + }); + + return ( +
        + +

        Wails + Qwik

        +
        {result.value}
        +
        +
        + name.value = e.target.value} type="text" autocomplete="off"/> + +
        +
        + +
        + ) +}) diff --git a/v3/internal/templates/qwik/frontend/src/main.jsx b/v3/internal/templates/qwik/frontend/src/main.jsx new file mode 100644 index 000000000..93884c8df --- /dev/null +++ b/v3/internal/templates/qwik/frontend/src/main.jsx @@ -0,0 +1,6 @@ +import '@builder.io/qwik/qwikloader.js' + +import { render } from '@builder.io/qwik' +import { App } from './app.jsx.tmpl' + +render(document.getElementById('app'), ) diff --git a/v3/internal/templates/qwik/frontend/src/vite-env.d.ts b/v3/internal/templates/qwik/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/qwik/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/qwik/frontend/tsconfig.json b/v3/internal/templates/qwik/frontend/tsconfig.json new file mode 100644 index 000000000..2be606f10 --- /dev/null +++ b/v3/internal/templates/qwik/frontend/tsconfig.json @@ -0,0 +1,25 @@ +/** + * This file tells your IDE where the root of your JavaScript project is, and sets some + * options that it can use to provide autocompletion and other features. + */ +{ + "compilerOptions": { + "allowJs": true, + "moduleResolution": "bundler", + /** + * The target and module can be set to ESNext to allow writing modern JavaScript, + * and Vite will compile down to the level of "build.target" specified in the vite config file. + * Builds will error if you use a feature that cannot be compiled down to the target level. + */ + "target": "ESNext", + "module": "ESNext", + "resolveJsonModule": true, + /** + * Enable checkJs if you'd like type checking in `.js(x)` files. + */ + "checkJs": false, + "strict": true, + "skipLibCheck": true, + }, + "include": ["src", "bindings"] +} diff --git a/v3/internal/templates/qwik/frontend/vite.config.js b/v3/internal/templates/qwik/frontend/vite.config.js new file mode 100644 index 000000000..3ea402c7b --- /dev/null +++ b/v3/internal/templates/qwik/frontend/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from "vite"; +import { qwikVite } from "@builder.io/qwik/optimizer"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + qwikVite({ + csr: true, + }), + wails("./bindings"), + ], +}); diff --git a/v3/internal/templates/qwik/template.json b/v3/internal/templates/qwik/template.json new file mode 100644 index 000000000..2677702bf --- /dev/null +++ b/v3/internal/templates/qwik/template.json @@ -0,0 +1,9 @@ +{ + "name": "Qwik + Vite", + "shortname": "qwik", + "author": "Lea Anthony", + "description": "Qwik + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/react-swc-ts/frontend/.gitignore b/v3/internal/templates/react-swc-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/react-swc-ts/frontend/index.html b/v3/internal/templates/react-swc-ts/frontend/index.html new file mode 100644 index 000000000..0dba04049 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + Wails + React + TS + + +
        + + + diff --git a/v3/internal/templates/react-swc-ts/frontend/package.json b/v3/internal/templates/react-swc-ts/frontend/package.json new file mode 100644 index 000000000..0dcc8cdcd --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/package.json @@ -0,0 +1,24 @@ +{ + "name": "react-ts-latest", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "tsc && vite build --minify false --mode development", + "build": "tsc && vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@vitejs/plugin-react-swc": "^3.5.0", + "typescript": "^5.2.2", + "vite": "^5.0.8" + } +} diff --git a/v3/internal/templates/react-swc-ts/frontend/public/Inter-Medium.ttf b/v3/internal/templates/react-swc-ts/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/react-swc-ts/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/react-swc-ts/frontend/public/react.svg b/v3/internal/templates/react-swc-ts/frontend/public/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/public/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/react-swc-ts/frontend/public/style.css b/v3/internal/templates/react-swc-ts/frontend/public/style.css new file mode 100644 index 000000000..0ba9cf5cc --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} diff --git a/v3/internal/templates/react-swc-ts/frontend/public/wails.png b/v3/internal/templates/react-swc-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/react-swc-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/react-swc-ts/frontend/src/App.tsx.tmpl b/v3/internal/templates/react-swc-ts/frontend/src/App.tsx.tmpl new file mode 100644 index 000000000..29c9f2440 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/src/App.tsx.tmpl @@ -0,0 +1,56 @@ +import { useState, useEffect } from 'react' +import {Events, WML} from "@wailsio/runtime"; +import {GreetService} from "../bindings/{{js .ModulePath}}"; + +function App() { + const [name, setName] = useState(''); + const [result, setResult] = useState('Please enter your name below 👇'); + const [time, setTime] = useState('Listening for Time event...'); + + const doGreet = () => { + let localName = name; + if (!localName) { + localName = 'anonymous'; + } + GreetService.Greet(localName).then((resultValue: string) => { + setResult(resultValue); + }).catch((err: any) => { + console.log(err); + }); + } + + useEffect(() => { + Events.On('time', (timeValue: any) => { + setTime(timeValue.data); + }); + // Reload WML so it picks up the wml tags + WML.Reload(); + }, []); + + return ( +
        + +

        Wails + React

        +
        {result}
        +
        +
        + setName(e.target.value)} type="text" autoComplete="off"/> + +
        +
        +
        +

        Click on the Wails logo to learn more

        +

        {time}

        +
        +
        + ) +} + +export default App diff --git a/v3/internal/templates/react-swc-ts/frontend/src/main.tsx b/v3/internal/templates/react-swc-ts/frontend/src/main.tsx new file mode 100644 index 000000000..3e1823139 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/src/main.tsx @@ -0,0 +1,9 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + , +) diff --git a/v3/internal/templates/react-swc-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/react-swc-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/react-swc-ts/frontend/tsconfig.json b/v3/internal/templates/react-swc-ts/frontend/tsconfig.json new file mode 100644 index 000000000..ae81ea6d5 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "bindings"], +} diff --git a/v3/internal/templates/react-swc-ts/frontend/vite.config.ts b/v3/internal/templates/react-swc-ts/frontend/vite.config.ts new file mode 100644 index 000000000..cd3b2c1c4 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react-swc"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), wails("./bindings")], +}); diff --git a/v3/internal/templates/react-swc-ts/template.json b/v3/internal/templates/react-swc-ts/template.json new file mode 100644 index 000000000..c40ff0772 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/template.json @@ -0,0 +1,9 @@ +{ + "name": "React + SWC + Vite (Typescript)", + "shortname": "react-swc-ts", + "author": "Lea Anthony", + "description": "React + TS + SWC + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/react-swc/frontend/.gitignore b/v3/internal/templates/react-swc/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/react-swc/frontend/index.html b/v3/internal/templates/react-swc/frontend/index.html new file mode 100644 index 000000000..468143c3d --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + Wails + React + + +
        + + + diff --git a/v3/internal/templates/react-swc/frontend/package.json b/v3/internal/templates/react-swc/frontend/package.json new file mode 100644 index 000000000..158ba6880 --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/package.json @@ -0,0 +1,23 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@vitejs/plugin-react-swc": "^3.5.0", + "vite": "^5.0.8" + } +} diff --git a/v3/internal/templates/react-swc/frontend/public/Inter-Medium.ttf b/v3/internal/templates/react-swc/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/react-swc/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/react-swc/frontend/public/react.svg b/v3/internal/templates/react-swc/frontend/public/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/public/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/react-swc/frontend/public/style.css b/v3/internal/templates/react-swc/frontend/public/style.css new file mode 100644 index 000000000..0ba9cf5cc --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} diff --git a/v3/internal/templates/react-swc/frontend/public/wails.png b/v3/internal/templates/react-swc/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/react-swc/frontend/public/wails.png differ diff --git a/v3/internal/templates/react-swc/frontend/src/App.jsx.tmpl b/v3/internal/templates/react-swc/frontend/src/App.jsx.tmpl new file mode 100644 index 000000000..87395733d --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/src/App.jsx.tmpl @@ -0,0 +1,56 @@ +import { useState, useEffect } from 'react' +import {Events, WML} from "@wailsio/runtime"; +import {GreetService} from "../bindings/{{js .ModulePath}}"; + +function App() { + const [name, setName] = useState(''); + const [result, setResult] = useState('Please enter your name below 👇'); + const [time, setTime] = useState('Listening for Time event...'); + + const doGreet = () => { + let localName = name; + if (!localName) { + localName = 'anonymous'; + } + GreetService.Greet(localName).then((resultValue) => { + setResult(resultValue); + }).catch((err) => { + console.log(err); + }); + } + + useEffect(() => { + Events.On('time', (timeValue) => { + setTime(timeValue.data); + }); + // Reload WML so it picks up the wml tags + WML.Reload(); + }, []); + + return ( +
        + +

        Wails + React

        +
        {result}
        +
        +
        + setName(e.target.value)} type="text" autoComplete="off"/> + +
        +
        +
        +

        Click on the Wails logo to learn more

        +

        {time}

        +
        +
        + ) +} + +export default App diff --git a/v3/internal/templates/react-swc/frontend/src/main.jsx b/v3/internal/templates/react-swc/frontend/src/main.jsx new file mode 100644 index 000000000..1943cc824 --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/src/main.jsx @@ -0,0 +1,9 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + , +) diff --git a/v3/internal/templates/react-swc/frontend/src/vite-env.d.ts b/v3/internal/templates/react-swc/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/react-swc/frontend/tsconfig.json b/v3/internal/templates/react-swc/frontend/tsconfig.json new file mode 100644 index 000000000..2be606f10 --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/tsconfig.json @@ -0,0 +1,25 @@ +/** + * This file tells your IDE where the root of your JavaScript project is, and sets some + * options that it can use to provide autocompletion and other features. + */ +{ + "compilerOptions": { + "allowJs": true, + "moduleResolution": "bundler", + /** + * The target and module can be set to ESNext to allow writing modern JavaScript, + * and Vite will compile down to the level of "build.target" specified in the vite config file. + * Builds will error if you use a feature that cannot be compiled down to the target level. + */ + "target": "ESNext", + "module": "ESNext", + "resolveJsonModule": true, + /** + * Enable checkJs if you'd like type checking in `.js(x)` files. + */ + "checkJs": false, + "strict": true, + "skipLibCheck": true, + }, + "include": ["src", "bindings"] +} diff --git a/v3/internal/templates/react-swc/frontend/vite.config.js b/v3/internal/templates/react-swc/frontend/vite.config.js new file mode 100644 index 000000000..cd3b2c1c4 --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/vite.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react-swc"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), wails("./bindings")], +}); diff --git a/v3/internal/templates/react-swc/template.json b/v3/internal/templates/react-swc/template.json new file mode 100644 index 000000000..de25db768 --- /dev/null +++ b/v3/internal/templates/react-swc/template.json @@ -0,0 +1,9 @@ +{ + "name": "React + SWC + Vite", + "shortname": "react-swc", + "author": "Lea Anthony", + "description": "React + SWC + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/react-ts/frontend/.gitignore b/v3/internal/templates/react-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/react-ts/frontend/index.html b/v3/internal/templates/react-ts/frontend/index.html new file mode 100644 index 000000000..0dba04049 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + Wails + React + TS + + +
        + + + diff --git a/v3/internal/templates/react-ts/frontend/package.json b/v3/internal/templates/react-ts/frontend/package.json new file mode 100644 index 000000000..f718c0073 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/package.json @@ -0,0 +1,24 @@ +{ + "name": "react-ts-latest", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "tsc && vite build --minify false --mode development", + "build": "tsc && vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@vitejs/plugin-react": "^4.2.1", + "typescript": "^5.2.2", + "vite": "^5.0.8" + } +} diff --git a/v3/internal/templates/react-ts/frontend/public/Inter-Medium.ttf b/v3/internal/templates/react-ts/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/react-ts/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/react-ts/frontend/public/react.svg b/v3/internal/templates/react-ts/frontend/public/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/public/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/react-ts/frontend/public/style.css b/v3/internal/templates/react-ts/frontend/public/style.css new file mode 100644 index 000000000..0ba9cf5cc --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} diff --git a/v3/internal/templates/react-ts/frontend/public/wails.png b/v3/internal/templates/react-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/react-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/react-ts/frontend/src/App.tsx.tmpl b/v3/internal/templates/react-ts/frontend/src/App.tsx.tmpl new file mode 100644 index 000000000..f6185f2a8 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/src/App.tsx.tmpl @@ -0,0 +1,56 @@ +import { useState, useEffect } from 'react' +import {Events, WML} from "@wailsio/runtime"; +import {GreetService} from "../bindings/{{js .ModulePath}}"; + +function App() { + const [name, setName] = useState(''); + const [result, setResult] = useState('Please enter your name below 👇'); + const [time, setTime] = useState('Listening for Time event...'); + + const doGreet = () => { + let localName = name; + if (!localName) { + localName = 'anonymous'; + } + GreetService.Greet(localName).then((resultValue: string) => { + setResult(resultValue); + }).catch((err: any) => { + console.log(err); + }); + } + + useEffect(() => { + Events.On('time', (timeValue: any) => { + setTime(timeValue.data); + }); + // Reload WML so it picks up the wml tags + WML.Reload(); + }, []); + + return ( +
        + +

        Wails + React

        +
        {result}
        +
        +
        + setName(e.target.value)} type="text" autoComplete="off"/> + +
        +
        +
        +

        Click on the Wails logo to learn more

        +

        {time}

        +
        +
        + ) +} + +export default App diff --git a/v3/internal/templates/react-ts/frontend/src/main.tsx b/v3/internal/templates/react-ts/frontend/src/main.tsx new file mode 100644 index 000000000..3e1823139 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/src/main.tsx @@ -0,0 +1,9 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + , +) diff --git a/v3/internal/templates/react-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/react-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/react-ts/frontend/tsconfig.json b/v3/internal/templates/react-ts/frontend/tsconfig.json new file mode 100644 index 000000000..ae81ea6d5 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "bindings"], +} diff --git a/v3/internal/templates/react-ts/frontend/vite.config.ts b/v3/internal/templates/react-ts/frontend/vite.config.ts new file mode 100644 index 000000000..a642bc024 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), wails("./bindings")], +}); diff --git a/v3/internal/templates/react-ts/template.json b/v3/internal/templates/react-ts/template.json new file mode 100644 index 000000000..33dd85583 --- /dev/null +++ b/v3/internal/templates/react-ts/template.json @@ -0,0 +1,9 @@ +{ + "name": "React + Vite (Typescript)", + "shortname": "react-ts", + "author": "Lea Anthony", + "description": "React + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/react/frontend/.gitignore b/v3/internal/templates/react/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/react/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/react/frontend/index.html b/v3/internal/templates/react/frontend/index.html new file mode 100644 index 000000000..468143c3d --- /dev/null +++ b/v3/internal/templates/react/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + Wails + React + + +
        + + + diff --git a/v3/internal/templates/react/frontend/package.json b/v3/internal/templates/react/frontend/package.json new file mode 100644 index 000000000..59d4d62b3 --- /dev/null +++ b/v3/internal/templates/react/frontend/package.json @@ -0,0 +1,23 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@vitejs/plugin-react": "^4.2.1", + "vite": "^5.0.8" + } +} diff --git a/v3/internal/templates/react/frontend/public/Inter-Medium.ttf b/v3/internal/templates/react/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/react/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/react/frontend/public/react.svg b/v3/internal/templates/react/frontend/public/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/v3/internal/templates/react/frontend/public/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/react/frontend/public/style.css b/v3/internal/templates/react/frontend/public/style.css new file mode 100644 index 000000000..0ba9cf5cc --- /dev/null +++ b/v3/internal/templates/react/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} diff --git a/v3/internal/templates/react/frontend/public/wails.png b/v3/internal/templates/react/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/react/frontend/public/wails.png differ diff --git a/v3/internal/templates/react/frontend/src/App.jsx.tmpl b/v3/internal/templates/react/frontend/src/App.jsx.tmpl new file mode 100644 index 000000000..87395733d --- /dev/null +++ b/v3/internal/templates/react/frontend/src/App.jsx.tmpl @@ -0,0 +1,56 @@ +import { useState, useEffect } from 'react' +import {Events, WML} from "@wailsio/runtime"; +import {GreetService} from "../bindings/{{js .ModulePath}}"; + +function App() { + const [name, setName] = useState(''); + const [result, setResult] = useState('Please enter your name below 👇'); + const [time, setTime] = useState('Listening for Time event...'); + + const doGreet = () => { + let localName = name; + if (!localName) { + localName = 'anonymous'; + } + GreetService.Greet(localName).then((resultValue) => { + setResult(resultValue); + }).catch((err) => { + console.log(err); + }); + } + + useEffect(() => { + Events.On('time', (timeValue) => { + setTime(timeValue.data); + }); + // Reload WML so it picks up the wml tags + WML.Reload(); + }, []); + + return ( +
        + +

        Wails + React

        +
        {result}
        +
        +
        + setName(e.target.value)} type="text" autoComplete="off"/> + +
        +
        +
        +

        Click on the Wails logo to learn more

        +

        {time}

        +
        +
        + ) +} + +export default App diff --git a/v3/internal/templates/react/frontend/src/main.jsx b/v3/internal/templates/react/frontend/src/main.jsx new file mode 100644 index 000000000..1943cc824 --- /dev/null +++ b/v3/internal/templates/react/frontend/src/main.jsx @@ -0,0 +1,9 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + , +) diff --git a/v3/internal/templates/react/frontend/src/vite-env.d.ts b/v3/internal/templates/react/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/react/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/react/frontend/tsconfig.json b/v3/internal/templates/react/frontend/tsconfig.json new file mode 100644 index 000000000..2be606f10 --- /dev/null +++ b/v3/internal/templates/react/frontend/tsconfig.json @@ -0,0 +1,25 @@ +/** + * This file tells your IDE where the root of your JavaScript project is, and sets some + * options that it can use to provide autocompletion and other features. + */ +{ + "compilerOptions": { + "allowJs": true, + "moduleResolution": "bundler", + /** + * The target and module can be set to ESNext to allow writing modern JavaScript, + * and Vite will compile down to the level of "build.target" specified in the vite config file. + * Builds will error if you use a feature that cannot be compiled down to the target level. + */ + "target": "ESNext", + "module": "ESNext", + "resolveJsonModule": true, + /** + * Enable checkJs if you'd like type checking in `.js(x)` files. + */ + "checkJs": false, + "strict": true, + "skipLibCheck": true, + }, + "include": ["src", "bindings"] +} diff --git a/v3/internal/templates/react/frontend/vite.config.js b/v3/internal/templates/react/frontend/vite.config.js new file mode 100644 index 000000000..a642bc024 --- /dev/null +++ b/v3/internal/templates/react/frontend/vite.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), wails("./bindings")], +}); diff --git a/v3/internal/templates/react/template.json b/v3/internal/templates/react/template.json new file mode 100644 index 000000000..c9fff4a20 --- /dev/null +++ b/v3/internal/templates/react/template.json @@ -0,0 +1,9 @@ +{ + "name": "React + Vite", + "shortname": "react", + "author": "Lea Anthony", + "description": "React + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/solid-ts/frontend/.gitignore b/v3/internal/templates/solid-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/solid-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/solid-ts/frontend/index.html b/v3/internal/templates/solid-ts/frontend/index.html new file mode 100644 index 000000000..86dfcc88e --- /dev/null +++ b/v3/internal/templates/solid-ts/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + Vite + Solid + TS + + +
        + + + diff --git a/v3/internal/templates/solid-ts/frontend/package.json b/v3/internal/templates/solid-ts/frontend/package.json new file mode 100644 index 000000000..741674ea7 --- /dev/null +++ b/v3/internal/templates/solid-ts/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "solid-ts", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "tsc && vite build --minify false --mode development", + "build": "tsc && vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "solid-js": "^1.8.7" + }, + "devDependencies": { + "typescript": "^5.2.2", + "vite": "^5.0.8", + "vite-plugin-solid": "^2.8.0" + } +} diff --git a/v3/internal/templates/solid-ts/frontend/public/Inter-Medium.ttf b/v3/internal/templates/solid-ts/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/solid-ts/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/solid-ts/frontend/public/solid.svg b/v3/internal/templates/solid-ts/frontend/public/solid.svg new file mode 100644 index 000000000..025aa303c --- /dev/null +++ b/v3/internal/templates/solid-ts/frontend/public/solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/solid-ts/frontend/public/style.css b/v3/internal/templates/solid-ts/frontend/public/style.css new file mode 100644 index 000000000..892241249 --- /dev/null +++ b/v3/internal/templates/solid-ts/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.solid:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/solid-ts/frontend/public/wails.png b/v3/internal/templates/solid-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/solid-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/solid-ts/frontend/src/App.tsx.tmpl b/v3/internal/templates/solid-ts/frontend/src/App.tsx.tmpl new file mode 100644 index 000000000..620959b51 --- /dev/null +++ b/v3/internal/templates/solid-ts/frontend/src/App.tsx.tmpl @@ -0,0 +1,54 @@ +import { createSignal, onMount } from 'solid-js' +import {Events} from "@wailsio/runtime"; +import {GreetService} from "../bindings/{{js .ModulePath}}"; + +function App() { + const [name, setName] = createSignal(''); + const [result, setResult] = createSignal('Please enter your name below 👇'); + const [time, setTime] = createSignal('Listening for Time event...'); + + const doGreet = () => { + let localName = name(); + if (!localName) { + localName = 'anonymous'; + } + GreetService.Greet(localName).then((resultValue: string) => { + setResult(resultValue); + }).catch((err: any) => { + console.log(err); + }); + } + + onMount(() => { + Events.On('time', (timeValue: any) => { + setTime(timeValue.data); + }); + }); + + return ( +
        + +

        Wails + Solid

        +
        {result()}
        +
        +
        + setName(e.currentTarget.value)} type="text" autocomplete="off"/> + +
        +
        + +
        + ) +} + +export default App diff --git a/v3/internal/templates/solid-ts/frontend/src/index.tsx b/v3/internal/templates/solid-ts/frontend/src/index.tsx new file mode 100644 index 000000000..ff5c09ee3 --- /dev/null +++ b/v3/internal/templates/solid-ts/frontend/src/index.tsx @@ -0,0 +1,7 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import App from './App' + +const root = document.getElementById('root') + +render(() => , root!) diff --git a/v3/internal/templates/solid-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/solid-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/solid-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/solid-ts/frontend/tsconfig.json b/v3/internal/templates/solid-ts/frontend/tsconfig.json new file mode 100644 index 000000000..89492db4a --- /dev/null +++ b/v3/internal/templates/solid-ts/frontend/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "bindings"], +} diff --git a/v3/internal/templates/solid-ts/frontend/vite.config.ts b/v3/internal/templates/solid-ts/frontend/vite.config.ts new file mode 100644 index 000000000..9b9614f01 --- /dev/null +++ b/v3/internal/templates/solid-ts/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import solid from "vite-plugin-solid"; +import wails from "@wailsio/runtime/plugins/vite"; + +export default defineConfig({ + plugins: [solid(), wails("./bindings")], +}); diff --git a/v3/internal/templates/solid-ts/template.json b/v3/internal/templates/solid-ts/template.json new file mode 100644 index 000000000..cc226b409 --- /dev/null +++ b/v3/internal/templates/solid-ts/template.json @@ -0,0 +1,9 @@ +{ + "name": "Solid + Vite (Typescript)", + "shortname": "solid-ts", + "author": "Lea Anthony", + "description": "Solid + TS + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/solid/frontend/.gitignore b/v3/internal/templates/solid/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/solid/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/solid/frontend/index.html b/v3/internal/templates/solid/frontend/index.html new file mode 100644 index 000000000..b074274aa --- /dev/null +++ b/v3/internal/templates/solid/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + Vite + Solid + + +
        + + + diff --git a/v3/internal/templates/solid/frontend/package.json b/v3/internal/templates/solid/frontend/package.json new file mode 100644 index 000000000..03ed9141f --- /dev/null +++ b/v3/internal/templates/solid/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "solid-latest", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "solid-js": "^1.8.7" + }, + "devDependencies": { + "vite": "^5.0.8", + "vite-plugin-solid": "^2.8.0" + } +} diff --git a/v3/internal/templates/solid/frontend/public/Inter-Medium.ttf b/v3/internal/templates/solid/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/solid/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/solid/frontend/public/solid.svg b/v3/internal/templates/solid/frontend/public/solid.svg new file mode 100644 index 000000000..025aa303c --- /dev/null +++ b/v3/internal/templates/solid/frontend/public/solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/solid/frontend/public/style.css b/v3/internal/templates/solid/frontend/public/style.css new file mode 100644 index 000000000..892241249 --- /dev/null +++ b/v3/internal/templates/solid/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.solid:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/solid/frontend/public/wails.png b/v3/internal/templates/solid/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/solid/frontend/public/wails.png differ diff --git a/v3/internal/templates/solid/frontend/src/App.jsx.tmpl b/v3/internal/templates/solid/frontend/src/App.jsx.tmpl new file mode 100644 index 000000000..d691909b3 --- /dev/null +++ b/v3/internal/templates/solid/frontend/src/App.jsx.tmpl @@ -0,0 +1,54 @@ +import { createSignal, onMount } from 'solid-js' +import {Events} from "@wailsio/runtime"; +import {GreetService} from "../bindings/{{js .ModulePath}}"; + +function App() { + const [name, setName] = createSignal(''); + const [result, setResult] = createSignal('Please enter your name below 👇'); + const [time, setTime] = createSignal('Listening for Time event...'); + + const doGreet = () => { + let localName = name(); + if (!localName) { + localName = 'anonymous'; + } + GreetService.Greet(localName).then((resultValue) => { + setResult(resultValue); + }).catch((err) => { + console.log(err); + }); + } + + onMount(() => { + Events.On('time', (timeValue) => { + setTime(timeValue.data); + }); + }); + + return ( +
        + +

        Wails + Solid

        +
        {result()}
        +
        +
        + setName(e.target.value)} type="text" autocomplete="off"/> + +
        +
        +
        +

        Click on the Wails logo to learn more

        +

        {time()}

        +
        +
        + ) +} + +export default App diff --git a/v3/internal/templates/solid/frontend/src/index.jsx b/v3/internal/templates/solid/frontend/src/index.jsx new file mode 100644 index 000000000..fd6808055 --- /dev/null +++ b/v3/internal/templates/solid/frontend/src/index.jsx @@ -0,0 +1,8 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' + +import App from './App' + +const root = document.getElementById('root') + +render(() => , root) diff --git a/v3/internal/templates/solid/frontend/src/vite-env.d.ts b/v3/internal/templates/solid/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/solid/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/solid/frontend/tsconfig.json b/v3/internal/templates/solid/frontend/tsconfig.json new file mode 100644 index 000000000..2be606f10 --- /dev/null +++ b/v3/internal/templates/solid/frontend/tsconfig.json @@ -0,0 +1,25 @@ +/** + * This file tells your IDE where the root of your JavaScript project is, and sets some + * options that it can use to provide autocompletion and other features. + */ +{ + "compilerOptions": { + "allowJs": true, + "moduleResolution": "bundler", + /** + * The target and module can be set to ESNext to allow writing modern JavaScript, + * and Vite will compile down to the level of "build.target" specified in the vite config file. + * Builds will error if you use a feature that cannot be compiled down to the target level. + */ + "target": "ESNext", + "module": "ESNext", + "resolveJsonModule": true, + /** + * Enable checkJs if you'd like type checking in `.js(x)` files. + */ + "checkJs": false, + "strict": true, + "skipLibCheck": true, + }, + "include": ["src", "bindings"] +} diff --git a/v3/internal/templates/solid/frontend/vite.config.js b/v3/internal/templates/solid/frontend/vite.config.js new file mode 100644 index 000000000..df7eac4c4 --- /dev/null +++ b/v3/internal/templates/solid/frontend/vite.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; +import solid from "vite-plugin-solid"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [solid(), wails("./bindings")], +}); diff --git a/v3/internal/templates/solid/template.json b/v3/internal/templates/solid/template.json new file mode 100644 index 000000000..87591ef3d --- /dev/null +++ b/v3/internal/templates/solid/template.json @@ -0,0 +1,9 @@ +{ + "name": "Solid + Vite", + "shortname": "solid", + "author": "Lea Anthony", + "description": "Solid + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/svelte-ts/frontend/.gitignore b/v3/internal/templates/svelte-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/svelte-ts/frontend/.vscode/extensions.json b/v3/internal/templates/svelte-ts/frontend/.vscode/extensions.json new file mode 100644 index 000000000..bdef82015 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode"] +} diff --git a/v3/internal/templates/svelte-ts/frontend/index.html b/v3/internal/templates/svelte-ts/frontend/index.html new file mode 100644 index 000000000..02e1fd7d1 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + Wails + Svelte + TS + + +
        + + + diff --git a/v3/internal/templates/svelte-ts/frontend/package.json b/v3/internal/templates/svelte-ts/frontend/package.json new file mode 100644 index 000000000..c14954e77 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/package.json @@ -0,0 +1,25 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.1", + "@tsconfig/svelte": "^5.0.2", + "svelte": "^4.2.8", + "svelte-check": "^3.6.2", + "tslib": "^2.6.2", + "typescript": "^5.2.2", + "vite": "^5.0.8" + } +} diff --git a/v3/internal/templates/svelte-ts/frontend/public/Inter-Medium.ttf b/v3/internal/templates/svelte-ts/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/svelte-ts/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/svelte-ts/frontend/public/style.css b/v3/internal/templates/svelte-ts/frontend/public/style.css new file mode 100644 index 000000000..0b9c58279 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/svelte-ts/frontend/public/svelte.svg b/v3/internal/templates/svelte-ts/frontend/public/svelte.svg new file mode 100644 index 000000000..c5e08481f --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/public/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/svelte-ts/frontend/public/wails.png b/v3/internal/templates/svelte-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/svelte-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/svelte-ts/frontend/src/App.svelte.tmpl b/v3/internal/templates/svelte-ts/frontend/src/App.svelte.tmpl new file mode 100644 index 000000000..b1a7a7ead --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/src/App.svelte.tmpl @@ -0,0 +1,51 @@ + + +
        +
        + + + + + + +
        +

        Wails + Svelte

        +
        {result}
        +
        +
        + + +
        +
        + +
        + + diff --git a/v3/internal/templates/svelte-ts/frontend/src/main.ts b/v3/internal/templates/svelte-ts/frontend/src/main.ts new file mode 100644 index 000000000..fb363569d --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/src/main.ts @@ -0,0 +1,7 @@ +import App from './App.svelte' + +const app = new App({ + target: document.getElementById('app'), +}) + +export default app diff --git a/v3/internal/templates/svelte-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/svelte-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..4078e7476 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/v3/internal/templates/svelte-ts/frontend/svelte.config.js b/v3/internal/templates/svelte-ts/frontend/svelte.config.js new file mode 100644 index 000000000..b0683fd24 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/svelte.config.js @@ -0,0 +1,7 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), +} diff --git a/v3/internal/templates/svelte-ts/frontend/tsconfig.json b/v3/internal/templates/svelte-ts/frontend/tsconfig.json new file mode 100644 index 000000000..6925f49d7 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable checkJs if you'd like to use dynamic types in JS. + * Note that setting allowJs false does not prevent the use + * of JS in `.svelte` files. + */ + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte", "bindings"], +} diff --git a/v3/internal/templates/svelte-ts/frontend/vite.config.ts b/v3/internal/templates/svelte-ts/frontend/vite.config.ts new file mode 100644 index 000000000..a9c90ba39 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [svelte(), wails("./bindings")], +}); diff --git a/v3/internal/templates/svelte-ts/template.json b/v3/internal/templates/svelte-ts/template.json new file mode 100644 index 000000000..597ae44db --- /dev/null +++ b/v3/internal/templates/svelte-ts/template.json @@ -0,0 +1,9 @@ +{ + "name": "Svelte + Vite (Typescript)", + "shortname": "svelte-ts", + "author": "Lea Anthony", + "description": "Svelte + TS + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/svelte/frontend/.gitignore b/v3/internal/templates/svelte/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/svelte/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/svelte/frontend/.vscode/extensions.json b/v3/internal/templates/svelte/frontend/.vscode/extensions.json new file mode 100644 index 000000000..bdef82015 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode"] +} diff --git a/v3/internal/templates/svelte/frontend/index.html b/v3/internal/templates/svelte/frontend/index.html new file mode 100644 index 000000000..20f11c0c0 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + Wails + Svelte + + +
        + + + diff --git a/v3/internal/templates/svelte/frontend/package.json b/v3/internal/templates/svelte/frontend/package.json new file mode 100644 index 000000000..4655c0e95 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.1", + "svelte": "^4.2.8", + "vite": "^5.0.8" + } +} diff --git a/v3/internal/templates/svelte/frontend/public/Inter-Medium.ttf b/v3/internal/templates/svelte/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/svelte/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/svelte/frontend/public/style.css b/v3/internal/templates/svelte/frontend/public/style.css new file mode 100644 index 000000000..0b9c58279 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/svelte/frontend/public/svelte.svg b/v3/internal/templates/svelte/frontend/public/svelte.svg new file mode 100644 index 000000000..c5e08481f --- /dev/null +++ b/v3/internal/templates/svelte/frontend/public/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/svelte/frontend/public/wails.png b/v3/internal/templates/svelte/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/svelte/frontend/public/wails.png differ diff --git a/v3/internal/templates/svelte/frontend/src/App.svelte.tmpl b/v3/internal/templates/svelte/frontend/src/App.svelte.tmpl new file mode 100644 index 000000000..f2e7aa1b1 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/src/App.svelte.tmpl @@ -0,0 +1,51 @@ + + +
        +
        + + + + + + +
        +

        Wails + Svelte

        +
        {result}
        +
        +
        + + +
        +
        + +
        + + diff --git a/v3/internal/templates/svelte/frontend/src/main.js b/v3/internal/templates/svelte/frontend/src/main.js new file mode 100644 index 000000000..fb363569d --- /dev/null +++ b/v3/internal/templates/svelte/frontend/src/main.js @@ -0,0 +1,7 @@ +import App from './App.svelte' + +const app = new App({ + target: document.getElementById('app'), +}) + +export default app diff --git a/v3/internal/templates/svelte/frontend/src/vite-env.d.ts b/v3/internal/templates/svelte/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..4078e7476 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/v3/internal/templates/svelte/frontend/tsconfig.json b/v3/internal/templates/svelte/frontend/tsconfig.json new file mode 100644 index 000000000..445179f36 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/tsconfig.json @@ -0,0 +1,25 @@ +/** + * This file tells your IDE where the root of your JavaScript project is, and sets some + * options that it can use to provide autocompletion and other features. + */ +{ + "compilerOptions": { + "allowJs": true, + "moduleResolution": "bundler", + /** + * The target and module can be set to ESNext to allow writing modern JavaScript, + * and Vite will compile down to the level of "build.target" specified in the vite config file. + * Builds will error if you use a feature that cannot be compiled down to the target level. + */ + "target": "ESNext", + "module": "ESNext", + "resolveJsonModule": true, + /** + * Enable checkJs if you'd like type checking in `.svelte` and `.js` files. + */ + "checkJs": false, + "strict": true, + "skipLibCheck": true, + }, + "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte", "bindings"] +} diff --git a/v3/internal/templates/svelte/frontend/vite.config.js b/v3/internal/templates/svelte/frontend/vite.config.js new file mode 100644 index 000000000..a9c90ba39 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/vite.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [svelte(), wails("./bindings")], +}); diff --git a/v3/internal/templates/svelte/template.json b/v3/internal/templates/svelte/template.json new file mode 100644 index 000000000..2a3da7371 --- /dev/null +++ b/v3/internal/templates/svelte/template.json @@ -0,0 +1,9 @@ +{ + "name": "Svelte + Vite", + "shortname": "svelte", + "author": "Lea Anthony", + "description": "Svelte + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/sveltekit-ts/frontend/.gitignore b/v3/internal/templates/sveltekit-ts/frontend/.gitignore new file mode 100644 index 000000000..79518f716 --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/frontend/.gitignore @@ -0,0 +1,21 @@ +node_modules + +# Output +.output +.vercel +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/v3/internal/templates/sveltekit-ts/frontend/.npmrc b/v3/internal/templates/sveltekit-ts/frontend/.npmrc new file mode 100644 index 000000000..b6f27f135 --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/frontend/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/v3/internal/templates/sveltekit-ts/frontend/README.md b/v3/internal/templates/sveltekit-ts/frontend/README.md new file mode 100644 index 000000000..5ce676612 --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/frontend/README.md @@ -0,0 +1,38 @@ +# create-svelte + +Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```bash +# create a new project in the current directory +npm create svelte@latest + +# create a new project in my-app +npm create svelte@latest my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```bash +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. diff --git a/v3/internal/templates/sveltekit-ts/frontend/package.json b/v3/internal/templates/sveltekit-ts/frontend/package.json new file mode 100644 index 000000000..a0418917b --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/frontend/package.json @@ -0,0 +1,26 @@ +{ + "name": "frontend", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "vite dev", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "@sveltejs/adapter-static": "^3.0.5", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "svelte": "^4.2.7", + "svelte-check": "^4.0.0", + "typescript": "^5.0.0", + "vite": "^5.0.3" + } +} diff --git a/v3/internal/templates/sveltekit-ts/frontend/src/app.d.ts b/v3/internal/templates/sveltekit-ts/frontend/src/app.d.ts new file mode 100644 index 000000000..743f07b2e --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/frontend/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://kit.svelte.dev/docs/types#app +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/v3/internal/templates/sveltekit-ts/frontend/src/app.html b/v3/internal/templates/sveltekit-ts/frontend/src/app.html new file mode 100644 index 000000000..2db551d71 --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/frontend/src/app.html @@ -0,0 +1,13 @@ + + + + + + + + %sveltekit.head% + + +
        %sveltekit.body%
        + + diff --git a/v3/internal/templates/sveltekit-ts/frontend/src/lib/index.ts b/v3/internal/templates/sveltekit-ts/frontend/src/lib/index.ts new file mode 100644 index 000000000..856f2b6c3 --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/frontend/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/v3/internal/templates/sveltekit-ts/frontend/src/routes/+layout.ts b/v3/internal/templates/sveltekit-ts/frontend/src/routes/+layout.ts new file mode 100644 index 000000000..ceccaaf67 --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/frontend/src/routes/+layout.ts @@ -0,0 +1,2 @@ +export const prerender = true; +export const ssr = false; diff --git a/v3/internal/templates/sveltekit-ts/frontend/src/routes/+page.svelte.tmpl b/v3/internal/templates/sveltekit-ts/frontend/src/routes/+page.svelte.tmpl new file mode 100644 index 000000000..4191d6c12 --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/frontend/src/routes/+page.svelte.tmpl @@ -0,0 +1,50 @@ + + +
        +
        + + + + + + +
        +

        Wails + Svelte

        +
        {result}
        +
        +
        + + +
        +
        + +
        + + diff --git a/v3/internal/templates/sveltekit-ts/frontend/static/Inter-Medium.ttf b/v3/internal/templates/sveltekit-ts/frontend/static/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/sveltekit-ts/frontend/static/Inter-Medium.ttf differ diff --git a/v3/internal/templates/sveltekit-ts/frontend/static/favicon.png b/v3/internal/templates/sveltekit-ts/frontend/static/favicon.png new file mode 100644 index 000000000..825b9e65a Binary files /dev/null and b/v3/internal/templates/sveltekit-ts/frontend/static/favicon.png differ diff --git a/v3/internal/templates/sveltekit-ts/frontend/static/style.css b/v3/internal/templates/sveltekit-ts/frontend/static/style.css new file mode 100644 index 000000000..0b9c58279 --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/frontend/static/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/sveltekit-ts/frontend/static/svelte.svg b/v3/internal/templates/sveltekit-ts/frontend/static/svelte.svg new file mode 100644 index 000000000..c5e08481f --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/frontend/static/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/sveltekit-ts/frontend/static/wails.png b/v3/internal/templates/sveltekit-ts/frontend/static/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/sveltekit-ts/frontend/static/wails.png differ diff --git a/v3/internal/templates/sveltekit-ts/frontend/svelte.config.js b/v3/internal/templates/sveltekit-ts/frontend/svelte.config.js new file mode 100644 index 000000000..7acd75386 --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/frontend/svelte.config.js @@ -0,0 +1,21 @@ +import adapter from '@sveltejs/adapter-static'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: vitePreprocess(), + kit: { + // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://kit.svelte.dev/docs/adapters for more information about adapters. + adapter: adapter({ + pages: 'dist', + assets: 'dist', + fallback: undefined, + precompress: false, + strict: true + }) + } +}; + +export default config; diff --git a/v3/internal/templates/sveltekit-ts/frontend/tsconfig.json b/v3/internal/templates/sveltekit-ts/frontend/tsconfig.json new file mode 100644 index 000000000..2de4494e1 --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/frontend/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "allowUnusedLabels": true, + "noUnusedLocals": false, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias + // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files + // + // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes + // from the referenced tsconfig.json - TypeScript does not merge them in +} diff --git a/v3/internal/templates/sveltekit-ts/frontend/vite.config.ts b/v3/internal/templates/sveltekit-ts/frontend/vite.config.ts new file mode 100644 index 000000000..5495c1564 --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/frontend/vite.config.ts @@ -0,0 +1,17 @@ +import { sveltekit } from "@sveltejs/kit/vite"; +import { defineConfig, searchForWorkspaceRoot } from "vite"; +import wails from "@wailsio/runtime/plugins/vite"; + +export default defineConfig({ + server: { + fs: { + allow: [ + // search up for workspace root + searchForWorkspaceRoot(process.cwd()), + // your custom rules + "./bindings/*", + ], + }, + }, + plugins: [sveltekit(), wails("./bindings")], +}); diff --git a/v3/internal/templates/sveltekit-ts/template.json b/v3/internal/templates/sveltekit-ts/template.json new file mode 100644 index 000000000..f4fab015d --- /dev/null +++ b/v3/internal/templates/sveltekit-ts/template.json @@ -0,0 +1,9 @@ +{ + "name": "SvelteKit Typescript + Vite", + "shortname": "sveltekit-ts", + "author": "Atterpac", + "description": "SvelteKit + TS + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} diff --git a/v3/internal/templates/sveltekit/frontend/.gitignore b/v3/internal/templates/sveltekit/frontend/.gitignore new file mode 100644 index 000000000..79518f716 --- /dev/null +++ b/v3/internal/templates/sveltekit/frontend/.gitignore @@ -0,0 +1,21 @@ +node_modules + +# Output +.output +.vercel +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/v3/internal/templates/sveltekit/frontend/.npmrc b/v3/internal/templates/sveltekit/frontend/.npmrc new file mode 100644 index 000000000..b6f27f135 --- /dev/null +++ b/v3/internal/templates/sveltekit/frontend/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/v3/internal/templates/sveltekit/frontend/README.md b/v3/internal/templates/sveltekit/frontend/README.md new file mode 100644 index 000000000..5ce676612 --- /dev/null +++ b/v3/internal/templates/sveltekit/frontend/README.md @@ -0,0 +1,38 @@ +# create-svelte + +Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```bash +# create a new project in the current directory +npm create svelte@latest + +# create a new project in my-app +npm create svelte@latest my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```bash +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. diff --git a/v3/internal/templates/sveltekit/frontend/frontend/style.css b/v3/internal/templates/sveltekit/frontend/frontend/style.css new file mode 100644 index 000000000..0b9c58279 --- /dev/null +++ b/v3/internal/templates/sveltekit/frontend/frontend/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/sveltekit/frontend/package.json b/v3/internal/templates/sveltekit/frontend/package.json new file mode 100644 index 000000000..fe7426489 --- /dev/null +++ b/v3/internal/templates/sveltekit/frontend/package.json @@ -0,0 +1,24 @@ +{ + "name": "frontend", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "vite dev", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "@sveltejs/adapter-static": "^3.0.5", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "svelte": "^4.2.7", + "vite": "^5.0.3" + } +} diff --git a/v3/internal/templates/sveltekit/frontend/src/app.html b/v3/internal/templates/sveltekit/frontend/src/app.html new file mode 100644 index 000000000..d72bd3485 --- /dev/null +++ b/v3/internal/templates/sveltekit/frontend/src/app.html @@ -0,0 +1,13 @@ + + + + + + + + %sveltekit.head% + + +
        %sveltekit.body%
        + + diff --git a/v3/internal/templates/sveltekit/frontend/src/lib/index.js b/v3/internal/templates/sveltekit/frontend/src/lib/index.js new file mode 100644 index 000000000..856f2b6c3 --- /dev/null +++ b/v3/internal/templates/sveltekit/frontend/src/lib/index.js @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/v3/internal/templates/sveltekit/frontend/src/routes/+layout.js b/v3/internal/templates/sveltekit/frontend/src/routes/+layout.js new file mode 100644 index 000000000..ceccaaf67 --- /dev/null +++ b/v3/internal/templates/sveltekit/frontend/src/routes/+layout.js @@ -0,0 +1,2 @@ +export const prerender = true; +export const ssr = false; diff --git a/v3/internal/templates/sveltekit/frontend/src/routes/+page.svelte.tmpl b/v3/internal/templates/sveltekit/frontend/src/routes/+page.svelte.tmpl new file mode 100644 index 000000000..4191d6c12 --- /dev/null +++ b/v3/internal/templates/sveltekit/frontend/src/routes/+page.svelte.tmpl @@ -0,0 +1,50 @@ + + +
        +
        + + + + + + +
        +

        Wails + Svelte

        +
        {result}
        +
        +
        + + +
        +
        + +
        + + diff --git a/v3/internal/templates/sveltekit/frontend/static/Inter-Medium.ttf b/v3/internal/templates/sveltekit/frontend/static/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/sveltekit/frontend/static/Inter-Medium.ttf differ diff --git a/v3/internal/templates/sveltekit/frontend/static/style.css b/v3/internal/templates/sveltekit/frontend/static/style.css new file mode 100644 index 000000000..0b9c58279 --- /dev/null +++ b/v3/internal/templates/sveltekit/frontend/static/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/sveltekit/frontend/static/svelte.svg b/v3/internal/templates/sveltekit/frontend/static/svelte.svg new file mode 100644 index 000000000..c5e08481f --- /dev/null +++ b/v3/internal/templates/sveltekit/frontend/static/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/sveltekit/frontend/static/wails.png b/v3/internal/templates/sveltekit/frontend/static/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/sveltekit/frontend/static/wails.png differ diff --git a/v3/internal/templates/sveltekit/frontend/svelte.config.js b/v3/internal/templates/sveltekit/frontend/svelte.config.js new file mode 100644 index 000000000..7f2998d89 --- /dev/null +++ b/v3/internal/templates/sveltekit/frontend/svelte.config.js @@ -0,0 +1,19 @@ +import adapter from '@sveltejs/adapter-static'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://kit.svelte.dev/docs/adapters for more information about adapters. + adapter: adapter({ + pages: 'dist', + assets: 'dist', + fallback: undefined, + precompress: false, + strict: true + }) + } +}; + +export default config; diff --git a/v3/internal/templates/sveltekit/frontend/tsconfig.json b/v3/internal/templates/sveltekit/frontend/tsconfig.json new file mode 100644 index 000000000..3a60c268c --- /dev/null +++ b/v3/internal/templates/sveltekit/frontend/tsconfig.json @@ -0,0 +1,25 @@ +/** + * This file tells your IDE where the root of your JavaScript project is, and sets some + * options that it can use to provide autocompletion and other features. + */ +{ + "compilerOptions": { + "allowJs": true, + "moduleResolution": "bundler", + /** + * The target and module can be set to ESNext to allow writing modern JavaScript, + * and Vite will compile down to the level of "build.target" specified in the vite config file. + * Builds will error if you use a feature that cannot be compiled down to the target level. + */ + "target": "ESNext", + "module": "ESNext", + "resolveJsonModule": true, + /** + * Enable checkJs if you'd like type checking in `.svelte` and `.js` files. + */ + "checkJs": false, + "strict": true, + "skipLibCheck": true, + }, + "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte", "bindings/**/*.d.ts"] +} diff --git a/v3/internal/templates/sveltekit/frontend/vite.config.js b/v3/internal/templates/sveltekit/frontend/vite.config.js new file mode 100644 index 000000000..d9aa89049 --- /dev/null +++ b/v3/internal/templates/sveltekit/frontend/vite.config.js @@ -0,0 +1,18 @@ +import { sveltekit } from "@sveltejs/kit/vite"; +import { defineConfig, searchForWorkspaceRoot } from "vite"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [sveltekit(), wails("./bindings")], + server: { + fs: { + allow: [ + // search up for workspace root + searchForWorkspaceRoot(process.cwd()), + // your custom rules + "./bindings/*", + ], + }, + }, +}); diff --git a/v3/internal/templates/sveltekit/template.json b/v3/internal/templates/sveltekit/template.json new file mode 100644 index 000000000..f81f2813b --- /dev/null +++ b/v3/internal/templates/sveltekit/template.json @@ -0,0 +1,9 @@ +{ + "name": "SvelteKit Typescript + Vite", + "shortname": "svelte-ts", + "author": "Atterpac", + "description": "SvelteKit + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} diff --git a/v3/internal/templates/templates.go b/v3/internal/templates/templates.go new file mode 100644 index 000000000..f10efe081 --- /dev/null +++ b/v3/internal/templates/templates.go @@ -0,0 +1,483 @@ +package templates + +import ( + "embed" + "encoding/json" + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + + "github.com/wailsapp/wails/v3/internal/buildinfo" + "github.com/wailsapp/wails/v3/internal/s" + "github.com/wailsapp/wails/v3/internal/version" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/pkg/errors" + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v3/internal/debug" + + "github.com/wailsapp/wails/v3/internal/flags" + + "github.com/leaanthony/gosod" + + "github.com/samber/lo" +) + +//go:embed * +var templates embed.FS + +type TemplateData struct { + Name string + Description string + FS fs.FS +} + +var defaultTemplates = []TemplateData{} + +func init() { + dirs, err := templates.ReadDir(".") + if err != nil { + return + } + for _, dir := range dirs { + if strings.HasPrefix(dir.Name(), "_") { + continue + } + if dir.IsDir() { + template, err := parseTemplate(templates, dir.Name()) + if err != nil { + continue + } + defaultTemplates = append(defaultTemplates, + TemplateData{ + Name: dir.Name(), + Description: template.Description, + FS: templates, + }) + } + } +} + +func ValidTemplateName(name string) bool { + return lo.ContainsBy(defaultTemplates, func(template TemplateData) bool { + return template.Name == name + }) +} + +func GetDefaultTemplates() []TemplateData { + return defaultTemplates +} + +type TemplateOptions struct { + *flags.Init + LocalModulePath string + UseTypescript bool + WailsVersion string +} + +func getInternalTemplate(templateName string) (*Template, error) { + templateData, found := lo.Find(defaultTemplates, func(template TemplateData) bool { + return template.Name == templateName + }) + + if !found { + return nil, nil + } + + template, err := parseTemplate(templateData.FS, templateData.Name) + if err != nil { + return nil, err + } + template.source = sourceInternal + return &template, nil +} + +func getLocalTemplate(templateName string) (*Template, error) { + var template Template + var err error + _, err = os.Stat(templateName) + if err != nil { + return nil, nil + } + + template, err = parseTemplate(os.DirFS(templateName), "") + if err != nil { + println("err2 = ", err.Error()) + return nil, err + } + template.source = sourceLocal + + return &template, nil +} + +type BaseTemplate struct { + Name string `json:"name" description:"The name of the template"` + ShortName string `json:"shortname" description:"The short name of the template"` + Author string `json:"author" description:"The author of the template"` + Description string `json:"description" description:"The template description"` + HelpURL string `json:"helpurl" description:"The help url for the template"` + Version string `json:"version" description:"The version of the template" default:"v0.0.1"` + Dir string `json:"-" description:"The directory to generate the template" default:"."` + Frontend string `json:"-" description:"The frontend directory to migrate"` +} + +type source int + +const ( + sourceInternal source = 1 + sourceLocal source = 2 + sourceRemote source = 3 +) + +// Template holds data relating to a template including the metadata stored in template.yaml +type Template struct { + BaseTemplate + Schema uint8 `json:"schema"` + + // Other data + FS fs.FS `json:"-"` + source source + tempDir string +} + +func parseTemplate(template fs.FS, templateName string) (Template, error) { + var result Template + jsonFile := "template.json" + if templateName != "" { + jsonFile = templateName + "/template.json" + } + data, err := fs.ReadFile(template, jsonFile) + if err != nil { + return result, errors.Wrap(err, "Error parsing template") + } + err = json.Unmarshal(data, &result) + if err != nil { + return result, err + } + result.FS = template + + // We need to do a version check here + if result.Schema == 0 { + return result, fmt.Errorf("template not supported by wails 3. This template is probably for wails 2") + } + if result.Schema != 3 { + return result, fmt.Errorf("template version %s is not supported by wails 3. Ensure 'version' is set to 3 in the `template.json` file", result.Version) + } + + return result, nil +} + +// Clones the given uri and returns the temporary cloned directory +func gitclone(uri string) (string, error) { + // Create temporary directory + dirname, err := os.MkdirTemp("", "wails-template-*") + if err != nil { + return "", err + } + + // Parse remote template url and version number + templateInfo := strings.Split(uri, "@") + cloneOption := &git.CloneOptions{ + URL: templateInfo[0], + } + if len(templateInfo) > 1 { + cloneOption.ReferenceName = plumbing.NewTagReferenceName(templateInfo[1]) + } + + _, err = git.PlainClone(dirname, false, cloneOption) + + return dirname, err + +} + +func getRemoteTemplate(uri string) (*Template, error) { + // git clone to temporary dir + var tempDir string + tempDir, err := gitclone(uri) + + if err != nil { + return nil, err + } + // Remove the .git directory + err = os.RemoveAll(filepath.Join(tempDir, ".git")) + if err != nil { + return nil, err + } + + templateFS := os.DirFS(tempDir) + var parsedTemplate Template + parsedTemplate, err = parseTemplate(templateFS, "") + if err != nil { + return nil, err + } + parsedTemplate.tempDir = tempDir + parsedTemplate.source = sourceRemote + return &parsedTemplate, nil +} + +func Install(options *flags.Init) error { + var wd = lo.Must(os.Getwd()) + var projectDir string + if options.ProjectDir == "." || options.ProjectDir == "" { + projectDir = wd + } else { + projectDir = options.ProjectDir + } + var err error + projectDir, err = filepath.Abs(filepath.Join(projectDir, options.ProjectName)) + if err != nil { + return err + } + + buildInfo, err := buildinfo.Get() + if err != nil { + return err + } + + // Calculate relative path from project directory to LocalModulePath + var localModulePath string + + // Use module path if it is set + if buildInfo.Development { + var relativePath string + // Check if the project directory and LocalModulePath are in the same drive + if filepath.VolumeName(wd) != filepath.VolumeName(debug.LocalModulePath) { + relativePath = debug.LocalModulePath + } else { + relativePath, err = filepath.Rel(projectDir, debug.LocalModulePath) + } + if err != nil { + return err + } + localModulePath = filepath.ToSlash(relativePath + "/") + } + UseTypescript := strings.HasSuffix(options.TemplateName, "-ts") + + templateData := TemplateOptions{ + Init: options, + LocalModulePath: localModulePath, + UseTypescript: UseTypescript, + WailsVersion: version.String(), + } + + defer func() { + // if `template.json` exists, remove it + _ = os.Remove(filepath.Join(templateData.ProjectDir, "template.json")) + }() + + var template *Template + + if ValidTemplateName(options.TemplateName) { + template, err = getInternalTemplate(options.TemplateName) + if err != nil { + return err + } + } else { + template, err = getLocalTemplate(options.TemplateName) + if err != nil { + return err + } + if template == nil { + template, err = getRemoteTemplate(options.TemplateName) + } + } + + if template == nil { + return fmt.Errorf("invalid template name: %s. Use -l flag to view available templates or use a valid filepath / url to a template", options.TemplateName) + } + + templateData.ProjectDir = projectDir + + // If project directory already exists and is not empty, error (unless Force is set) + if _, err := os.Stat(templateData.ProjectDir); !os.IsNotExist(err) { + // Check if the directory is empty + files := lo.Must(os.ReadDir(templateData.ProjectDir)) + if len(files) > 0 && !options.Force { + return fmt.Errorf("project directory '%s' already exists and is not empty. Use -f to force init in a non-empty directory", templateData.ProjectDir) + } + } + + if template.source == sourceRemote && !options.SkipWarning { + var confirmed = confirmRemote(template) + if !confirmed { + return nil + } + } + + pterm.Printf("Creating project\n") + pterm.Printf("----------------\n\n") + table := pterm.TableData{ + {"Project Name", options.ProjectName}, + {"Project Directory", filepath.FromSlash(options.ProjectDir)}, + {"Template", template.Name}, + {"Template Source", template.HelpURL}, + {"Template Version", template.Version}, + } + err = pterm.DefaultTable.WithData(table).Render() + if err != nil { + return err + } + + switch template.source { + case sourceInternal: + tfs, err := fs.Sub(template.FS, options.TemplateName) + if err != nil { + return err + } + common, err := fs.Sub(templates, "_common") + if err != nil { + return err + } + err = gosod.New(common).Extract(options.ProjectDir, templateData) + if err != nil { + return err + } + err = gosod.New(tfs).Extract(options.ProjectDir, templateData) + if err != nil { + return err + } + case sourceLocal, sourceRemote: + data := struct { + TemplateOptions + Dir string + Name string + BinaryName string + ProductName string + ProductDescription string + ProductVersion string + ProductCompany string + ProductCopyright string + ProductComments string + ProductIdentifier string + Silent bool + Typescript bool + }{ + Name: options.ProjectName, + Silent: true, + ProductCompany: options.ProductCompany, + ProductName: options.ProductName, + ProductDescription: options.ProductDescription, + ProductVersion: options.ProductVersion, + ProductIdentifier: options.ProductIdentifier, + ProductCopyright: options.ProductCopyright, + ProductComments: options.ProductComments, + Typescript: templateData.UseTypescript, + TemplateOptions: templateData, + } + // If options.ProjectDir does not exist, create it + if _, err := os.Stat(options.ProjectDir); os.IsNotExist(err) { + err = os.Mkdir(options.ProjectDir, 0755) + if err != nil { + return err + } + } + err = gosod.New(template.FS).Extract(options.ProjectDir, data) + if err != nil { + return err + } + + if template.tempDir != "" { + s.RMDIR(template.tempDir) + } + } + if !options.SkipGoModTidy { + err = goModTidy(templateData.ProjectDir) + if err != nil { + return err + } + } + + // Change to project directory + err = os.Chdir(templateData.ProjectDir) + if err != nil { + return err + } + + pterm.Printf("\nProject '%s' created successfully.\n", options.ProjectName) + + return nil + +} + +func GenerateTemplate(options *BaseTemplate) error { + if options.Name == "" { + return fmt.Errorf("please provide a template name using the -name flag") + } + + // Get current directory + baseOutputDir, err := filepath.Abs(options.Dir) + if err != nil { + return err + } + outDir := filepath.Join(baseOutputDir, options.Name) + + // Extract base files + _, filename, _, _ := runtime.Caller(0) + basePath := filepath.Join(filepath.Dir(filename), "_common") + s.COPYDIR2(basePath, outDir) + s.RMDIR(filepath.Join(outDir, "build")) + + // Copy frontend + targetFrontendPath := filepath.Join(outDir, "frontend") + sourceFrontendPath := options.Frontend + if sourceFrontendPath == "" { + sourceFrontendPath = filepath.Join(filepath.Dir(filename), "base", "frontend") + } + s.COPYDIR2(sourceFrontendPath, targetFrontendPath) + + // Copy files from relative directory ../commands/build_assets + // Get the path to THIS file + assetPath := filepath.Join(filepath.Dir(filename), "..", "commands", "build_assets") + assetdir := filepath.Join(outDir, "build") + + s.COPYDIR2(assetPath, assetdir) + + // Copy the template NEXTSTEPS.md + s.COPY(filepath.Join(filepath.Dir(filename), "base", "NEXTSTEPS.md"), filepath.Join(outDir, "NEXTSTEPS.md")) + + // Write the template.json file + templateJSON := filepath.Join(outDir, "template.json") + // Marshall + optionsJSON, err := json.MarshalIndent(&Template{ + BaseTemplate: *options, + Schema: 3, + }, "", " ") + if err != nil { + return err + } + err = os.WriteFile(templateJSON, optionsJSON, 0o755) + if err != nil { + return err + } + + fmt.Printf("Successfully generated template in %s\n", outDir) + return nil +} + +func confirmRemote(template *Template) bool { + pterm.Println(pterm.LightRed("\n--- REMOTE TEMPLATES ---")) + + // Create boxes with the title positioned differently and containing different content + pterm.Println(pterm.LightYellow("You are creating a project using a remote template.\nThe Wails project takes no responsibility for 3rd party templates.\nOnly use remote templates that you trust.")) + + result, _ := pterm.DefaultInteractiveConfirm.WithConfirmText("Are you sure you want to continue?").WithConfirmText("y").WithRejectText("n").Show() + + return result +} + +// goModTidy runs go mod tidy in the given project directory +// It returns an error if the command fails +func goModTidy(projectDir string) error { + cmd := exec.Command("go", "mod", "tidy") + cmd.Dir = projectDir + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to run go mod tidy: %w\n%s", err, string(output)) + } + return nil +} diff --git a/v3/internal/templates/vanilla-ts/frontend/.gitignore b/v3/internal/templates/vanilla-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/vanilla-ts/frontend/index.html b/v3/internal/templates/vanilla-ts/frontend/index.html new file mode 100644 index 000000000..3eb06610a --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/index.html @@ -0,0 +1,35 @@ + + + + + + + + Wails App + + +
        + +

        Wails + Typescript

        +
        Please enter your name below 👇
        +
        +
        + + +
        +
        + +
        + + + diff --git a/v3/internal/templates/vanilla-ts/frontend/package.json b/v3/internal/templates/vanilla-ts/frontend/package.json new file mode 100644 index 000000000..2bf167fe2 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/package.json @@ -0,0 +1,19 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "typescript": "^4.9.3", + "vite": "^5.0.0" + } +} diff --git a/v3/internal/templates/vanilla-ts/frontend/public/Inter-Medium.ttf b/v3/internal/templates/vanilla-ts/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/vanilla-ts/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/vanilla-ts/frontend/public/style.css b/v3/internal/templates/vanilla-ts/frontend/public/style.css new file mode 100644 index 000000000..0b9c58279 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/vanilla-ts/frontend/public/typescript.svg b/v3/internal/templates/vanilla-ts/frontend/public/typescript.svg new file mode 100644 index 000000000..d91c910cc --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/public/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/vanilla-ts/frontend/public/wails.png b/v3/internal/templates/vanilla-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/vanilla-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/vanilla-ts/frontend/src/main.ts.tmpl b/v3/internal/templates/vanilla-ts/frontend/src/main.ts.tmpl new file mode 100644 index 000000000..87dae063f --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/src/main.ts.tmpl @@ -0,0 +1,23 @@ +import {Events} from "@wailsio/runtime"; +import {GreetService} from "../bindings/{{js .ModulePath}}"; + +const greetButton = document.getElementById('greet')! as HTMLButtonElement; +const resultElement = document.getElementById('result')! as HTMLDivElement; +const nameElement : HTMLInputElement = document.getElementById('name')! as HTMLInputElement; +const timeElement = document.getElementById('time')! as HTMLDivElement; + +greetButton.addEventListener('click', async () => { + let name = (nameElement as HTMLInputElement).value + if (!name) { + name = 'anonymous'; + } + try { + resultElement.innerText = await GreetService.Greet(name); + } catch (err) { + console.error(err); + } +}); + +Events.On('time', (time) => { + timeElement.innerText = time.data; +}); diff --git a/v3/internal/templates/vanilla-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/vanilla-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/vanilla-ts/frontend/tsconfig.json b/v3/internal/templates/vanilla-ts/frontend/tsconfig.json new file mode 100644 index 000000000..53c9f903e --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "allowJs": true, + + "noEmit": true, + "skipLibCheck": true, + + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext", + ], + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + }, + "include": ["src", "bindings"] +} diff --git a/v3/internal/templates/vanilla-ts/frontend/vite.config.ts b/v3/internal/templates/vanilla-ts/frontend/vite.config.ts new file mode 100644 index 000000000..56f2d6a8a --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [wails("./bindings")], +}); diff --git a/v3/internal/templates/vanilla-ts/template.json b/v3/internal/templates/vanilla-ts/template.json new file mode 100644 index 000000000..6d9e55107 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/template.json @@ -0,0 +1,9 @@ +{ + "name": "Vanilla + Vite (Typescript)", + "shortname": "vanilla-ts", + "author": "Lea Anthony", + "description": "Vanilla + TS + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/vanilla/frontend/.gitignore b/v3/internal/templates/vanilla/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/vanilla/frontend/index.html b/v3/internal/templates/vanilla/frontend/index.html new file mode 100644 index 000000000..798204fa9 --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/index.html @@ -0,0 +1,35 @@ + + + + + + + + Wails App + + +
        + +

        Wails + Javascript

        +
        +
        Please enter your name below 👇
        +
        + + +
        +
        + +
        + + + diff --git a/v3/internal/templates/vanilla/frontend/package.json b/v3/internal/templates/vanilla/frontend/package.json new file mode 100644 index 000000000..0a118e984 --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "vite": "^5.0.0" + } +} diff --git a/v3/internal/templates/vanilla/frontend/public/Inter-Medium.ttf b/v3/internal/templates/vanilla/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/vanilla/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/vanilla/frontend/public/javascript.svg b/v3/internal/templates/vanilla/frontend/public/javascript.svg new file mode 100644 index 000000000..f9abb2b72 --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/public/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/vanilla/frontend/public/style.css b/v3/internal/templates/vanilla/frontend/public/style.css new file mode 100644 index 000000000..0b9c58279 --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/vanilla/frontend/public/wails.png b/v3/internal/templates/vanilla/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/vanilla/frontend/public/wails.png differ diff --git a/v3/internal/templates/vanilla/frontend/src/main.js.tmpl b/v3/internal/templates/vanilla/frontend/src/main.js.tmpl new file mode 100644 index 000000000..2b256789d --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/src/main.js.tmpl @@ -0,0 +1,21 @@ +import {Events} from "@wailsio/runtime"; +import {GreetService} from "../bindings/{{js .ModulePath}}"; + +const resultElement = document.getElementById('result'); +const timeElement = document.getElementById('time'); + +window.doGreet = async () => { + let name = document.getElementById('name').value; + if (!name) { + name = 'anonymous'; + } + try { + resultElement.innerText = await GreetService.Greet(name); + } catch (err) { + console.error(err); + } +} + +Events.On('time', (time) => { + timeElement.innerText = time.data; +}); diff --git a/v3/internal/templates/vanilla/frontend/tsconfig.json b/v3/internal/templates/vanilla/frontend/tsconfig.json new file mode 100644 index 000000000..f103fe3a9 --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/tsconfig.json @@ -0,0 +1,25 @@ +/** + * This file tells your IDE where the root of your JavaScript project is, and sets some + * options that it can use to provide autocompletion and other features. + */ +{ + "compilerOptions": { + "allowJs": true, + "moduleResolution": "bundler", + /** + * The target and module can be set to ESNext to allow writing modern JavaScript, + * and Vite will compile down to the level of "build.target" specified in the vite config file. + * Builds will error if you use a feature that cannot be compiled down to the target level. + */ + "target": "ESNext", + "module": "ESNext", + "resolveJsonModule": true, + /** + * Enable checkJs if you'd like type checking in `.js` files. + */ + "checkJs": false, + "strict": true, + "skipLibCheck": true, + }, + "include": ["src", "bindings"] +} diff --git a/v3/internal/templates/vanilla/frontend/vite.config.js b/v3/internal/templates/vanilla/frontend/vite.config.js new file mode 100644 index 000000000..56f2d6a8a --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [wails("./bindings")], +}); diff --git a/v3/internal/templates/vanilla/template.json b/v3/internal/templates/vanilla/template.json new file mode 100644 index 000000000..5f8a281a5 --- /dev/null +++ b/v3/internal/templates/vanilla/template.json @@ -0,0 +1,9 @@ +{ + "name": "Vanilla + Vite", + "shortname": "vanilla", + "author": "Lea Anthony", + "description": "Vanilla + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/vue-ts/.vscode/extensions.json b/v3/internal/templates/vue-ts/.vscode/extensions.json new file mode 100644 index 000000000..86c616f02 --- /dev/null +++ b/v3/internal/templates/vue-ts/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["octref.vetur"] +} diff --git a/v3/internal/templates/vue-ts/frontend/.gitignore b/v3/internal/templates/vue-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/vue-ts/frontend/.vscode/extensions.json b/v3/internal/templates/vue-ts/frontend/.vscode/extensions.json new file mode 100644 index 000000000..c0a6e5a48 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] +} diff --git a/v3/internal/templates/vue-ts/frontend/README.md b/v3/internal/templates/vue-ts/frontend/README.md new file mode 100644 index 000000000..ef72fd524 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/README.md @@ -0,0 +1,18 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/v3/internal/templates/vue-ts/frontend/package.json b/v3/internal/templates/vue-ts/frontend/package.json new file mode 100644 index 000000000..dc08ffa11 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/package.json @@ -0,0 +1,22 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vue-tsc && vite build --minify false --mode development", + "build": "vue-tsc && vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "vue": "^3.2.45" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.0.0", + "typescript": "^4.9.3", + "vite": "^5.0.0", + "vue-tsc": "^1.0.11" + } +} diff --git a/v3/internal/templates/vue-ts/frontend/public/Inter-Medium.ttf b/v3/internal/templates/vue-ts/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/vue-ts/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/vue-ts/frontend/public/style.css b/v3/internal/templates/vue-ts/frontend/public/style.css new file mode 100644 index 000000000..187e7e4b9 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/public/style.css @@ -0,0 +1,145 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +* { + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + text-align: center; +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/vue-ts/frontend/public/vue.svg b/v3/internal/templates/vue-ts/frontend/public/vue.svg new file mode 100644 index 000000000..770e9d333 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/public/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/vue-ts/frontend/public/wails.png b/v3/internal/templates/vue-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/vue-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/vue-ts/frontend/src/App.vue b/v3/internal/templates/vue-ts/frontend/src/App.vue new file mode 100644 index 000000000..8f62a7e44 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/App.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/v3/internal/templates/vue-ts/frontend/src/components/HelloWorld.vue.tmpl b/v3/internal/templates/vue-ts/frontend/src/components/HelloWorld.vue.tmpl new file mode 100644 index 000000000..9e599ab80 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/components/HelloWorld.vue.tmpl @@ -0,0 +1,47 @@ + + + diff --git a/v3/internal/templates/vue-ts/frontend/src/main.ts b/v3/internal/templates/vue-ts/frontend/src/main.ts new file mode 100644 index 000000000..01433bca2 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/main.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/v3/internal/templates/vue-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/vue-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/vue-ts/frontend/tsconfig.json b/v3/internal/templates/vue-ts/frontend/tsconfig.json new file mode 100644 index 000000000..5e8658ed7 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true, + "noEmit": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "bindings"], +} diff --git a/v3/internal/templates/vue-ts/frontend/vite.config.ts b/v3/internal/templates/vue-ts/frontend/vite.config.ts new file mode 100644 index 000000000..16127a822 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue(), wails("./bindings")], +}); diff --git a/v3/internal/templates/vue-ts/template.json b/v3/internal/templates/vue-ts/template.json new file mode 100644 index 000000000..d87ef872e --- /dev/null +++ b/v3/internal/templates/vue-ts/template.json @@ -0,0 +1,9 @@ +{ + "name": "Vue + Vite (Typescript)", + "shortname": "vue-ts", + "author": "Lea Anthony", + "description": "Vue + TS + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/vue/.vscode/extensions.json b/v3/internal/templates/vue/.vscode/extensions.json new file mode 100644 index 000000000..86c616f02 --- /dev/null +++ b/v3/internal/templates/vue/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["octref.vetur"] +} diff --git a/v3/internal/templates/vue/frontend/.gitignore b/v3/internal/templates/vue/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/vue/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/vue/frontend/.vscode/extensions.json b/v3/internal/templates/vue/frontend/.vscode/extensions.json new file mode 100644 index 000000000..c0a6e5a48 --- /dev/null +++ b/v3/internal/templates/vue/frontend/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] +} diff --git a/v3/internal/templates/vue/frontend/index.html b/v3/internal/templates/vue/frontend/index.html new file mode 100644 index 000000000..2700f93b9 --- /dev/null +++ b/v3/internal/templates/vue/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + Wails + Vue + + +
        + + + diff --git a/v3/internal/templates/vue/frontend/package.json b/v3/internal/templates/vue/frontend/package.json new file mode 100644 index 000000000..6958956b3 --- /dev/null +++ b/v3/internal/templates/vue/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "vue": "^3.2.45" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.0.0", + "vite": "^5.0.0" + } +} diff --git a/v3/internal/templates/vue/frontend/public/Inter-Medium.ttf b/v3/internal/templates/vue/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/vue/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/vue/frontend/public/style.css b/v3/internal/templates/vue/frontend/public/style.css new file mode 100644 index 000000000..187e7e4b9 --- /dev/null +++ b/v3/internal/templates/vue/frontend/public/style.css @@ -0,0 +1,145 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +* { + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + text-align: center; +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/vue/frontend/public/vue.svg b/v3/internal/templates/vue/frontend/public/vue.svg new file mode 100644 index 000000000..770e9d333 --- /dev/null +++ b/v3/internal/templates/vue/frontend/public/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/vue/frontend/public/wails.png b/v3/internal/templates/vue/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/vue/frontend/public/wails.png differ diff --git a/v3/internal/templates/vue/frontend/src/App.vue b/v3/internal/templates/vue/frontend/src/App.vue new file mode 100644 index 000000000..8af6ab35d --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/App.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/v3/internal/templates/vue/frontend/src/components/HelloWorld.vue.tmpl b/v3/internal/templates/vue/frontend/src/components/HelloWorld.vue.tmpl new file mode 100644 index 000000000..99b5f88ce --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/components/HelloWorld.vue.tmpl @@ -0,0 +1,49 @@ + + + diff --git a/v3/internal/templates/vue/frontend/src/main.js b/v3/internal/templates/vue/frontend/src/main.js new file mode 100644 index 000000000..01433bca2 --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/main.js @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/v3/internal/templates/vue/frontend/src/vite-env.d.ts b/v3/internal/templates/vue/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/vue/frontend/tsconfig.json b/v3/internal/templates/vue/frontend/tsconfig.json new file mode 100644 index 000000000..bd83d39fe --- /dev/null +++ b/v3/internal/templates/vue/frontend/tsconfig.json @@ -0,0 +1,25 @@ +/** + * This file tells your IDE where the root of your JavaScript project is, and sets some + * options that it can use to provide autocompletion and other features. + */ +{ + "compilerOptions": { + "allowJs": true, + "moduleResolution": "bundler", + /** + * The target and module can be set to ESNext to allow writing modern JavaScript, + * and Vite will compile down to the level of "build.target" specified in the vite config file. + * Builds will error if you use a feature that cannot be compiled down to the target level. + */ + "target": "ESNext", + "module": "ESNext", + "resolveJsonModule": true, + /** + * Enable checkJs if you'd like type checking in `.vue` and `.js` files. + */ + "checkJs": false, + "strict": true, + "skipLibCheck": true, + }, + "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.jsx", "src/**/*.vue", "bindings"] +} diff --git a/v3/internal/templates/vue/frontend/vite.config.js b/v3/internal/templates/vue/frontend/vite.config.js new file mode 100644 index 000000000..16127a822 --- /dev/null +++ b/v3/internal/templates/vue/frontend/vite.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue(), wails("./bindings")], +}); diff --git a/v3/internal/templates/vue/template.json b/v3/internal/templates/vue/template.json new file mode 100644 index 000000000..b6e8faf93 --- /dev/null +++ b/v3/internal/templates/vue/template.json @@ -0,0 +1,9 @@ +{ + "name": "Vue + Vite", + "shortname": "vue", + "author": "Lea Anthony", + "description": "Vue + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/term/term.go b/v3/internal/term/term.go new file mode 100644 index 000000000..9127ff08a --- /dev/null +++ b/v3/internal/term/term.go @@ -0,0 +1,127 @@ +package term + +import ( + "fmt" + "os" + + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v3/internal/generator/config" + "github.com/wailsapp/wails/v3/internal/version" + "golang.org/x/term" +) + +func Header(header string) { + // Print Wails with the current version in white on red background with the header in white with a green background + pterm.BgLightRed.Print(pterm.LightWhite(" Wails (" + version.String() + ") ")) + pterm.BgLightGreen.Println(pterm.LightWhite(" " + header + " ")) +} + +func IsTerminal() bool { + return term.IsTerminal(int(os.Stdout.Fd())) && (os.Getenv("CI") != "true") +} + +type Spinner struct { + spinner *pterm.SpinnerPrinter +} + +func (s Spinner) Logger() config.Logger { + return config.DefaultPtermLogger(s.spinner) +} + +func StartSpinner(text string) Spinner { + if !IsTerminal() { + return Spinner{} + } + spinner, err := pterm.DefaultSpinner.Start(text) + if err != nil { + return Spinner{} + } + spinner.RemoveWhenDone = true + return Spinner{spinner} +} + +func StopSpinner(s Spinner) { + if s.spinner != nil { + _ = s.spinner.Stop() + } +} + +func output(input any, printer pterm.PrefixPrinter, args ...any) { + switch v := input.(type) { + case string: + printer.Printfln(input.(string), args...) + case error: + printer.Println(v.Error()) + default: + printer.Printfln("%v", v) + } +} + +func Info(input any) { + output(input, pterm.Info) +} + +func Infof(input any, args ...interface{}) { + output(input, pterm.Info, args...) +} + +func Warning(input any) { + output(input, pterm.Warning) +} + +func Warningf(input any, args ...any) { + output(input, pterm.Warning, args...) +} + +func Error(input any) { + output(input, pterm.Error) +} + +func Success(input any) { + output(input, pterm.Success) +} + +func Section(s string) { + style := pterm.NewStyle(pterm.BgDefault, pterm.FgLightBlue, pterm.Bold) + style.Println("\n# " + s + " \n") +} + +func DisableColor() { + pterm.DisableColor() +} + +func EnableOutput() { + pterm.EnableOutput() +} + +func DisableOutput() { + pterm.DisableOutput() +} + +func EnableDebug() { + pterm.EnableDebugMessages() +} + +func DisableDebug() { + pterm.DisableDebugMessages() +} + +func Println(s string) { + pterm.Println(s) +} + +func Hyperlink(url, text string) string { + // OSC 8 sequence to start a clickable link + linkStart := "\x1b]8;;" + + // OSC 8 sequence to end a clickable link + linkEnd := "\x1b]8;;\x1b\\" + + // ANSI escape code for underline + underlineStart := "\x1b[4m" + + // ANSI escape code to reset text formatting + resetFormat := "\x1b[0m" + + return fmt.Sprintf("%s%s%s%s%s%s%s", linkStart, url, "\x1b\\", underlineStart, text, resetFormat, linkEnd) +} diff --git a/v3/internal/version/version.go b/v3/internal/version/version.go new file mode 100644 index 000000000..fedd279af --- /dev/null +++ b/v3/internal/version/version.go @@ -0,0 +1,26 @@ +package version + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/internal/debug" +) + +//go:embed version.txt +var versionString string + +const DevVersion = "v3.0.0-dev" + +func String() string { + if !IsDev() { + return versionString + } + return DevVersion +} + +func LatestStable() string { + return versionString +} + +func IsDev() bool { + return debug.LocalModulePath != "" +} diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt new file mode 100644 index 000000000..8e81eb3ef --- /dev/null +++ b/v3/internal/version/version.txt @@ -0,0 +1 @@ +v3.0.0-alpha.65 \ No newline at end of file diff --git a/v3/old b/v3/old new file mode 120000 index 000000000..4532eb733 --- /dev/null +++ b/v3/old @@ -0,0 +1 @@ +../../../GolandProjects/ios \ No newline at end of file diff --git a/v3/pkg/application/TODO.md b/v3/pkg/application/TODO.md new file mode 100644 index 000000000..ae4700425 --- /dev/null +++ b/v3/pkg/application/TODO.md @@ -0,0 +1,10 @@ + +Features +- [ ] AssetServer +- [ ] Offline page if navigating to external URL +- [x] Application menu + +Bugs +- [ ] Resize Window +- [ ] Fullscreen/Maximise/Minimise/Restore + diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go new file mode 100644 index 000000000..e31245abf --- /dev/null +++ b/v3/pkg/application/application.go @@ -0,0 +1,900 @@ +package application + +import ( + "context" + "embed" + "errors" + "fmt" + "io" + "log/slog" + "net/http" + "os" + "runtime" + "slices" + "strconv" + "strings" + "sync" + + "github.com/wailsapp/wails/v3/internal/assetserver" + "github.com/wailsapp/wails/v3/internal/assetserver/bundledassets" + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + "github.com/wailsapp/wails/v3/internal/capabilities" +) + +//go:embed assets/* +var alphaAssets embed.FS + +var globalApplication *App + +// AlphaAssets is the default assets for the alpha application +var AlphaAssets = AssetOptions{ + Handler: BundledAssetFileServer(alphaAssets), +} + +type EventListener struct { + callback func(app *ApplicationEvent) +} + +func Get() *App { + return globalApplication +} + +func New(appOptions Options) *App { + if globalApplication != nil { + return globalApplication + } + + mergeApplicationDefaults(&appOptions) + + result := newApplication(appOptions) + globalApplication = result + fatalHandler(result.handleFatalError) + + if result.Logger == nil { + if result.isDebugMode { + result.Logger = DefaultLogger(result.options.LogLevel) + } else { + result.Logger = slog.New(slog.NewTextHandler(io.Discard, nil)) + } + } + + // Set up signal handling (platform-specific) + result.setupSignalHandler(appOptions) + + result.logStartup() + result.logPlatformInfo() + + result.customEventProcessor = NewWailsEventProcessor(result.Event.dispatch) + + messageProc := NewMessageProcessor(result.Logger) + + // Initialize transport (default to HTTP if not specified) + transport := appOptions.Transport + if transport == nil { + transport = NewHTTPTransport(HTTPTransportWithLogger(result.Logger)) + } + + err := transport.Start(result.ctx, messageProc) + if err != nil { + result.fatal("failed to start custom transport: %w", err) + } + // Register shutdown task to stop transport + result.OnShutdown(func() { + if err := transport.Stop(); err != nil { + result.error("failed to stop custom transport: %w", err) + } + }) + + // Auto-wire events if transport supports event delivery + if eventTransport, ok := transport.(WailsEventListener); ok { + result.wailsEventListeners = append(result.wailsEventListeners, eventTransport) + } else { + // otherwise fallback to IPC + result.wailsEventListeners = append(result.wailsEventListeners, &EventIPCTransport{ + app: result, + }) + } + + middlewares := []assetserver.Middleware{ + func(next http.Handler) http.Handler { + if m := appOptions.Assets.Middleware; m != nil { + return m(next) + } + return next + }, + func(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + path := req.URL.Path + switch path { + case "/wails/runtime.js": + err := assetserver.ServeFile(rw, path, bundledassets.RuntimeJS) + if err != nil { + result.fatal("unable to serve runtime.js: %w", err) + } + case "/wails/transport.js": + err := assetserver.ServeFile(rw, path, transport.JSClient()) + if err != nil { + result.fatal("unable to serve transport.js: %w", err) + } + default: + next.ServeHTTP(rw, req) + } + }) + }, + } + + if handler, ok := transport.(TransportHTTPHandler); ok { + middlewares = append(middlewares, handler.Handler()) + } + + opts := &assetserver.Options{ + Handler: appOptions.Assets.Handler, + Middleware: assetserver.ChainMiddleware(middlewares...), + Logger: result.Logger, + } + + if appOptions.Assets.DisableLogging { + opts.Logger = slog.New(slog.NewTextHandler(io.Discard, nil)) + } + + srv, err := assetserver.NewAssetServer(opts) + if err != nil { + result.fatal("application initialisation failed: %w", err) + } + + result.assets = srv + result.assets.LogDetails() + + // If transport implements AssetServerTransport, configure it to serve assets + if assetTransport, ok := transport.(AssetServerTransport); ok { + err := assetTransport.ServeAssets(srv) + if err != nil { + result.fatal("failed to configure transport for serving assets: %w", err) + } + result.debug("Transport configured to serve assets") + } + + result.bindings = NewBindings(appOptions.MarshalError, appOptions.BindAliases) + result.options.Services = slices.Clone(appOptions.Services) + + // Process keybindings + if result.options.KeyBindings != nil { + result.keyBindings = processKeyBindingOptions(result.options.KeyBindings) + } + + if appOptions.OnShutdown != nil { + result.OnShutdown(appOptions.OnShutdown) + } + + // Initialize single instance manager if enabled + if appOptions.SingleInstance != nil { + manager, err := newSingleInstanceManager(result, appOptions.SingleInstance) + if err != nil { + if errors.Is(err, alreadyRunningError) && manager != nil { + err = manager.notifyFirstInstance() + if err != nil { + globalApplication.error("failed to notify first instance: %w", err) + } + os.Exit(appOptions.SingleInstance.ExitCode) + } + result.fatal("failed to initialize single instance manager: %w", err) + } else { + result.singleInstanceManager = manager + } + } + + return result +} + +func mergeApplicationDefaults(o *Options) { + if o.Name == "" { + o.Name = "My Wails Application" + } + if o.Description == "" { + o.Description = "An application written using Wails" + } + if o.Windows.WndClass == "" { + o.Windows.WndClass = "WailsWebviewWindow" + } +} + +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) + hide() + show() + getPrimaryScreen() (*Screen, error) + getScreens() ([]*Screen, error) + GetFlags(options Options) map[string]any + isOnMainThread() bool + isDarkMode() bool + getAccentColor() string + } + + runnable interface { + Run() + } +) + +// Messages sent from javascript get routed here +type windowMessage struct { + windowId uint + message string + originInfo *OriginInfo +} + +type OriginInfo struct { + Origin string + TopOrigin string + IsMainFrame bool +} + +var windowMessageBuffer = make(chan *windowMessage, 5) + +// DropTargetDetails contains information about the HTML element +// where files were dropped (the element with data-file-drop-target attribute). +type DropTargetDetails struct { + X int `json:"x"` + Y int `json:"y"` + ElementID string `json:"id"` + ClassList []string `json:"classList"` + Attributes map[string]string `json:"attributes,omitempty"` +} + +type dragAndDropMessage struct { + windowId uint + filenames []string + X int + Y int + DropTarget *DropTargetDetails +} + +var windowDragAndDropBuffer = make(chan *dragAndDropMessage, 5) + +func addDragAndDropMessage(windowId uint, filenames []string, dropTarget *DropTargetDetails) { + windowDragAndDropBuffer <- &dragAndDropMessage{ + windowId: windowId, + filenames: filenames, + DropTarget: dropTarget, + } +} + +var _ webview.Request = &webViewAssetRequest{} + +const webViewRequestHeaderWindowId = "x-wails-window-id" +const webViewRequestHeaderWindowName = "x-wails-window-name" + +type webViewAssetRequest struct { + Request webview.Request + windowId uint + windowName string +} + +var windowKeyEvents = make(chan *windowKeyEvent, 5) + +type windowKeyEvent struct { + windowId uint + acceleratorString string +} + +func (r *webViewAssetRequest) URL() (string, error) { + return r.Request.URL() +} + +func (r *webViewAssetRequest) Method() (string, error) { + return r.Request.Method() +} + +func (r *webViewAssetRequest) Header() (http.Header, error) { + h, err := r.Request.Header() + if err != nil { + return nil, err + } + + hh := h.Clone() + hh.Set(webViewRequestHeaderWindowId, strconv.FormatUint(uint64(r.windowId), 10)) + if r.windowName != "" { + hh.Set(webViewRequestHeaderWindowName, r.windowName) + } + return hh, nil +} + +func (r *webViewAssetRequest) Body() (io.ReadCloser, error) { + return r.Request.Body() +} + +func (r *webViewAssetRequest) Response() webview.ResponseWriter { + return r.Request.Response() +} + +func (r *webViewAssetRequest) Close() error { + return r.Request.Close() +} + +var webviewRequests = make(chan *webViewAssetRequest, 5) + +type eventHook struct { + callback func(event *ApplicationEvent) +} + +type App struct { + ctx context.Context + cancel context.CancelFunc + options Options + applicationEventListeners map[uint][]*EventListener + applicationEventListenersLock sync.RWMutex + applicationEventHooks map[uint][]*eventHook + applicationEventHooksLock sync.RWMutex + + // Manager pattern for organized API + Window *WindowManager + ContextMenu *ContextMenuManager + KeyBinding *KeyBindingManager + Browser *BrowserManager + Env *EnvironmentManager + Dialog *DialogManager + Event *EventManager + Menu *MenuManager + Screen *ScreenManager + Clipboard *ClipboardManager + SystemTray *SystemTrayManager + + // Windows + windows map[uint]Window + windowsLock sync.RWMutex + + // System Trays + systemTrays map[uint]*SystemTray + systemTraysLock sync.Mutex + systemTrayID uint + systemTrayIDLock sync.RWMutex + + // MenuItems + menuItems map[uint]*MenuItem + menuItemsLock sync.Mutex + + // Starting and running + starting bool + running bool + runLock sync.Mutex + pendingRun []runnable + + bindings *Bindings + + // platform app + impl platformApp + + // The main application menu (private - use app.Menu.GetApplicationMenu/SetApplicationMenu) + applicationMenu *Menu + + clipboard *Clipboard + customEventProcessor *EventProcessor + Logger *slog.Logger + + contextMenus map[string]*ContextMenu + contextMenusLock sync.RWMutex + + assets *assetserver.AssetServer + startURL string + + // Hooks + windowCreatedCallbacks []func(window Window) + pid int + + // Capabilities + capabilities capabilities.Capabilities + isDebugMode bool + + // Keybindings + keyBindings map[string]func(window Window) + keyBindingsLock sync.RWMutex + + // Shutdown + performingShutdown bool + shutdownLock sync.Mutex + serviceShutdownLock sync.Mutex + + // Shutdown tasks are run when the application is shutting down. + // They are run in the order they are added and run on the main thread. + // The application option `OnShutdown` is run first. + shutdownTasks []func() + + // Platform-specific fields (includes signal handler on desktop) + platformSignalHandler + + // Wails ApplicationEvent Listener related + wailsEventListenerLock sync.Mutex + wailsEventListeners []WailsEventListener + + // singleInstanceManager handles single instance functionality + singleInstanceManager *singleInstanceManager +} + +func (a *App) Config() Options { + return a.options +} + +// Context returns the application context that is canceled when the application shuts down. +// This context should be used for graceful shutdown of goroutines and long-running operations. +func (a *App) Context() context.Context { + return a.ctx +} + +func (a *App) handleWarning(msg string) { + if a.options.WarningHandler != nil { + a.options.WarningHandler(msg) + } else { + a.Logger.Warn(msg) + } +} + +func (a *App) handleError(err error) { + if a.options.ErrorHandler != nil { + a.options.ErrorHandler(err) + } else { + a.Logger.Error(err.Error()) + } +} + +// RegisterService appends the given service to the list of bound services. +// Registered services will be bound and initialised +// in registration order upon calling [App.Run]. +// +// RegisterService will log an error message +// and discard the given service +// if called after [App.Run]. +func (a *App) RegisterService(service Service) { + a.runLock.Lock() + defer a.runLock.Unlock() + + if a.starting || a.running { + a.error( + "services must be registered before running the application. Service '%s' will not be registered.", + getServiceName(service), + ) + return + } + + a.options.Services = append(a.options.Services, service) +} + +func (a *App) handleFatalError(err error) { + a.handleError(&FatalError{err: err}) + os.Exit(1) +} + +func (a *App) init() { + a.ctx, a.cancel = context.WithCancel(context.Background()) + a.applicationEventHooks = make(map[uint][]*eventHook) + a.applicationEventListeners = make(map[uint][]*EventListener) + a.windows = make(map[uint]Window) + a.systemTrays = make(map[uint]*SystemTray) + a.contextMenus = make(map[string]*ContextMenu) + a.keyBindings = make(map[string]func(window Window)) + a.Logger = a.options.Logger + a.pid = os.Getpid() + a.wailsEventListeners = make([]WailsEventListener, 0) + + // Initialize managers + a.Window = newWindowManager(a) + a.ContextMenu = newContextMenuManager(a) + a.KeyBinding = newKeyBindingManager(a) + a.Browser = newBrowserManager(a) + a.Env = newEnvironmentManager(a) + a.Dialog = newDialogManager(a) + a.Event = newEventManager(a) + a.Menu = newMenuManager(a) + a.Screen = newScreenManager(a) + a.Clipboard = newClipboardManager(a) + a.SystemTray = newSystemTrayManager(a) +} + +func (a *App) Capabilities() capabilities.Capabilities { + return a.capabilities +} + +func (a *App) GetPID() int { + return a.pid +} + +func (a *App) info(message string, args ...any) { + if a.Logger != nil { + go func() { + defer handlePanic() + a.Logger.Info(message, args...) + }() + } +} + +func (a *App) debug(message string, args ...any) { + if a.Logger != nil { + go func() { + defer handlePanic() + a.Logger.Debug(message, args...) + }() + } +} + +func (a *App) fatal(message string, args ...any) { + err := fmt.Errorf(message, args...) + a.handleFatalError(err) +} +func (a *App) warning(message string, args ...any) { + msg := fmt.Sprintf(message, args...) + a.handleWarning(msg) +} + +func (a *App) error(message string, args ...any) { + a.handleError(fmt.Errorf(message, args...)) +} + +func (a *App) Run() error { + a.runLock.Lock() + // Prevent double invocations. + if a.starting || a.running { + a.runLock.Unlock() + return errors.New("application is running or a previous run has failed") + } + // Block further service registrations. + a.starting = true + a.runLock.Unlock() + + // Ensure application context is cancelled in case of failures. + defer a.cancel() + + // Call post-create hooks + err := a.preRun() + if err != nil { + return err + } + + a.impl = newPlatformApp(a) + + // Ensure services are shut down in case of failures. + defer a.shutdownServices() + + // Ensure application context is canceled before service shutdown (duplicate calls don't hurt). + defer a.cancel() + + // Startup services before dispatching any events. + // No need to hold the lock here because a.options.Services may only change when a.running is false. + services := a.options.Services + a.options.Services = nil + for i, service := range services { + if err := a.startupService(service); err != nil { + return fmt.Errorf("error starting service '%s': %w", getServiceName(service), err) + } + // Schedule started services for shutdown. + a.options.Services = services[:i+1] + } + + go func() { + for { + event := <-applicationEvents + go a.Event.handleApplicationEvent(event) + } + }() + go func() { + for { + event := <-windowEvents + go a.handleWindowEvent(event) + } + }() + go func() { + for { + request := <-webviewRequests + go a.handleWebViewRequest(request) + } + }() + go func() { + for { + event := <-windowMessageBuffer + go a.handleWindowMessage(event) + } + }() + go func() { + for { + event := <-windowKeyEvents + go a.handleWindowKeyEvent(event) + } + }() + go func() { + for { + dragAndDropMessage := <-windowDragAndDropBuffer + go a.handleDragAndDropMessage(dragAndDropMessage) + } + }() + + go func() { + for { + menuItemID := <-menuItemClicked + go a.Menu.handleMenuItemClicked(menuItemID) + } + }() + + a.runLock.Lock() + a.running = true + a.runLock.Unlock() + + // No need to hold the lock here because + // - a.pendingRun may only change while a.running is false. + // - runnables are scheduled asynchronously anyway. + for _, pending := range a.pendingRun { + go func() { + defer handlePanic() + pending.Run() + }() + } + a.pendingRun = nil + + // set the application menu + if runtime.GOOS == "darwin" { + a.impl.setApplicationMenu(a.applicationMenu) + } + if a.options.Icon != nil { + a.impl.setIcon(a.options.Icon) + } + + return a.impl.run() +} + +func (a *App) startupService(service Service) error { + err := a.bindings.Add(service) + if err != nil { + return fmt.Errorf("cannot bind service methods: %w", err) + } + + if service.options.Route != "" { + handler, ok := service.Instance().(http.Handler) + if !ok { + handler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + http.Error( + rw, + fmt.Sprintf( + "Service '%s' does not handle HTTP requests", + getServiceName(service), + ), + http.StatusServiceUnavailable, + ) + }) + } + a.assets.AttachServiceHandler(service.options.Route, handler) + } + + if s, ok := service.instance.(ServiceStartup); ok { + a.debug("Starting up service:", "name", getServiceName(service)) + return s.ServiceStartup(a.ctx, service.options) + } + + return nil +} + +func (a *App) shutdownServices() { + // Acquire lock to prevent double calls (defer in Run() + OnShutdown) + a.serviceShutdownLock.Lock() + defer a.serviceShutdownLock.Unlock() + + // Ensure app context is cancelled first (duplicate calls don't hurt). + a.cancel() + + for len(a.options.Services) > 0 { + last := len(a.options.Services) - 1 + service := a.options.Services[last] + a.options.Services = a.options.Services[:last] // Prevent double shutdowns + + if s, ok := service.instance.(ServiceShutdown); ok { + a.debug("Shutting down service:", "name", getServiceName(service)) + if err := s.ServiceShutdown(); err != nil { + a.error("error shutting down service '%s': %w", getServiceName(service), err) + } + } + } +} + +func (a *App) handleDragAndDropMessage(event *dragAndDropMessage) { + defer handlePanic() + a.windowsLock.Lock() + window, ok := a.windows[event.windowId] + a.windowsLock.Unlock() + if !ok { + a.warning("WebviewWindow #%d not found", event.windowId) + return + } + window.handleDragAndDropMessage(event.filenames, event.DropTarget) +} + +func (a *App) handleWindowMessage(event *windowMessage) { + defer handlePanic() + // Get window from window map + a.windowsLock.RLock() + window, ok := a.windows[event.windowId] + // Debug: log all window IDs + var ids []uint + for id := range a.windows { + ids = append(ids, id) + } + a.windowsLock.RUnlock() + + a.debug("handleWindowMessage: Looking for window", "windowId", event.windowId, "availableIDs", ids) + + if !ok { + a.warning("WebviewWindow #%d not found", event.windowId) + return + } + // Check if the message starts with "wails:" + if strings.HasPrefix(event.message, "wails:") { + a.debug("handleWindowMessage: Processing wails message", "message", event.message) + window.HandleMessage(event.message) + } else { + if a.options.RawMessageHandler != nil { + a.options.RawMessageHandler(window, event.message, event.originInfo) + } + } +} + +func (a *App) handleWebViewRequest(request *webViewAssetRequest) { + defer handlePanic() + // Log that we're processing the request + url, _ := request.Request.URL() + a.debug("handleWebViewRequest: Processing request", "url", url) + // IMPORTANT: pass the wrapper request so our injected headers (x-wails-window-id/name) are used + a.assets.ServeWebViewRequest(request) + a.debug("handleWebViewRequest: Request processing complete", "url", url) +} + +func (a *App) handleWindowEvent(event *windowEvent) { + defer handlePanic() + // Get window from window map + a.windowsLock.RLock() + window, ok := a.windows[event.WindowID] + a.windowsLock.RUnlock() + if !ok { + a.warning("Window #%d not found", event.WindowID) + return + } + window.HandleWindowEvent(event.EventID) +} + +// OnShutdown adds a function to be run when the application is shutting down. +func (a *App) OnShutdown(f func()) { + if f == nil { + return + } + + a.shutdownLock.Lock() + + if !a.performingShutdown { + defer a.shutdownLock.Unlock() + a.shutdownTasks = append(a.shutdownTasks, f) + return + } + + a.shutdownLock.Unlock() + InvokeAsync(f) +} + +func (a *App) cleanup() { + a.shutdownLock.Lock() + if a.performingShutdown { + a.shutdownLock.Unlock() + return + } + a.cancel() // Cancel app context before running shutdown hooks. + a.performingShutdown = true + a.shutdownLock.Unlock() + + // No need to hold the lock here because a.shutdownTasks + // may only change while a.performingShutdown is false. + for _, shutdownTask := range a.shutdownTasks { + InvokeSync(shutdownTask) + } + InvokeSync(func() { + a.shutdownServices() + a.windowsLock.RLock() + for _, window := range a.windows { + window.Close() + } + a.windows = nil + a.windowsLock.RUnlock() + a.systemTraysLock.Lock() + for _, systray := range a.systemTrays { + systray.destroy() + } + a.systemTrays = nil + a.systemTraysLock.Unlock() + + // Cleanup single instance manager + if a.singleInstanceManager != nil { + a.singleInstanceManager.cleanup() + } + + a.postQuit() + + if a.options.PostShutdown != nil { + a.options.PostShutdown() + } + }) +} + +func (a *App) Quit() { + if a.impl != nil { + InvokeSync(a.impl.destroy) + } +} + +func (a *App) SetIcon(icon []byte) { + if a.impl != nil { + a.impl.setIcon(icon) + } +} + +func (a *App) dispatchOnMainThread(fn func()) { + // If we are on the main thread, just call the function + if a.impl.isOnMainThread() { + fn() + return + } + + mainThreadFunctionStoreLock.Lock() + id := generateFunctionStoreID() + mainThreadFunctionStore[id] = fn + mainThreadFunctionStoreLock.Unlock() + // Call platform specific dispatch function + a.impl.dispatchOnMainThread(id) +} + +func (a *App) Hide() { + if a.impl != nil { + a.impl.hide() + } +} + +func (a *App) Show() { + if a.impl != nil { + a.impl.show() + } +} + +func (a *App) runOrDeferToAppRun(r runnable) { + a.runLock.Lock() + + if !a.running { + defer a.runLock.Unlock() // Defer unlocking for panic tolerance. + a.pendingRun = append(a.pendingRun, r) + return + } + + // Unlock immediately to prevent deadlocks. + // No TOC/TOU risk here because a.running can never switch back to false. + a.runLock.Unlock() + r.Run() +} + +func (a *App) handleWindowKeyEvent(event *windowKeyEvent) { + defer handlePanic() + // Get window from window map + a.windowsLock.RLock() + window, ok := a.windows[event.windowId] + a.windowsLock.RUnlock() + if !ok { + a.warning("WebviewWindow #%d not found", event.windowId) + return + } + // Get callback from window + window.HandleKeyEvent(event.acceleratorString) +} + +func (a *App) shouldQuit() bool { + if a.options.ShouldQuit != nil { + return a.options.ShouldQuit() + } + return true +} diff --git a/v3/pkg/application/application_android.go b/v3/pkg/application/application_android.go new file mode 100644 index 000000000..ce620a603 --- /dev/null +++ b/v3/pkg/application/application_android.go @@ -0,0 +1,687 @@ +//go:build android && cgo && !server + +package application + +/* +#include +#include +#include + +// Global JavaVM reference for thread attachment +static JavaVM* g_jvm = NULL; + +// Global reference to bridge object (must be a global ref, not local) +static jobject g_bridge = NULL; + +// Cached method ID for executeJavaScript +static jmethodID g_executeJsMethod = NULL; + +// Helper function to convert Java String to C string +static const char* jstringToC(JNIEnv *env, jstring jstr) { + if (jstr == NULL) return NULL; + return (*env)->GetStringUTFChars(env, jstr, NULL); +} + +// Helper function to release Java String +static void releaseJString(JNIEnv *env, jstring jstr, const char* cstr) { + if (jstr != NULL && cstr != NULL) { + (*env)->ReleaseStringUTFChars(env, jstr, cstr); + } +} + +// Helper function to create Java byte array from C data +static jbyteArray createByteArray(JNIEnv *env, const void* data, int len) { + if (data == NULL || len <= 0) return NULL; + jbyteArray arr = (*env)->NewByteArray(env, len); + if (arr != NULL) { + (*env)->SetByteArrayRegion(env, arr, 0, len, (const jbyte*)data); + } + return arr; +} + +// Helper function to create Java String from C string +static jstring createJString(JNIEnv *env, const char* str) { + if (str == NULL) return NULL; + return (*env)->NewStringUTF(env, str); +} + +// Store JavaVM and create global reference to bridge +static void storeBridgeRef(JNIEnv *env, jobject bridge) { + // Get JavaVM + if ((*env)->GetJavaVM(env, &g_jvm) != 0) { + return; + } + + // Create global reference to bridge object + g_bridge = (*env)->NewGlobalRef(env, bridge); + if (g_bridge == NULL) { + return; + } + + // Cache the executeJavaScript method ID + jclass bridgeClass = (*env)->GetObjectClass(env, g_bridge); + if (bridgeClass != NULL) { + g_executeJsMethod = (*env)->GetMethodID(env, bridgeClass, "executeJavaScript", "(Ljava/lang/String;)V"); + (*env)->DeleteLocalRef(env, bridgeClass); + } +} + +// Android logging via __android_log_print +#include +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "WailsNative", __VA_ARGS__) + +// Execute JavaScript via the bridge - can be called from any thread +static void executeJavaScriptOnBridge(const char* js) { + LOGD("executeJavaScriptOnBridge called, js length: %d", js ? (int)strlen(js) : -1); + + if (g_jvm == NULL) { + LOGD("executeJavaScriptOnBridge: g_jvm is NULL"); + return; + } + if (g_bridge == NULL) { + LOGD("executeJavaScriptOnBridge: g_bridge is NULL"); + return; + } + if (g_executeJsMethod == NULL) { + LOGD("executeJavaScriptOnBridge: g_executeJsMethod is NULL"); + return; + } + if (js == NULL) { + LOGD("executeJavaScriptOnBridge: js is NULL"); + return; + } + + JNIEnv *env = NULL; + int needsDetach = 0; + + // Get JNIEnv for current thread + jint result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6); + LOGD("executeJavaScriptOnBridge: GetEnv result = %d", result); + if (result == JNI_EDETACHED) { + // Attach current thread to JVM + LOGD("executeJavaScriptOnBridge: Attaching thread"); + if ((*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL) != 0) { + LOGD("executeJavaScriptOnBridge: AttachCurrentThread failed"); + return; + } + needsDetach = 1; + } else if (result != JNI_OK) { + LOGD("executeJavaScriptOnBridge: GetEnv failed with %d", result); + return; + } + + // Create Java string and call method + jstring jJs = (*env)->NewStringUTF(env, js); + LOGD("executeJavaScriptOnBridge: jJs created: %p", jJs); + if (jJs != NULL) { + LOGD("executeJavaScriptOnBridge: Calling Java method"); + (*env)->CallVoidMethod(env, g_bridge, g_executeJsMethod, jJs); + LOGD("executeJavaScriptOnBridge: Java method called"); + (*env)->DeleteLocalRef(env, jJs); + } + + // Check for exceptions + if ((*env)->ExceptionCheck(env)) { + LOGD("executeJavaScriptOnBridge: Exception occurred!"); + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + + // Detach if we attached + if (needsDetach) { + LOGD("executeJavaScriptOnBridge: Detaching thread"); + (*g_jvm)->DetachCurrentThread(g_jvm); + } + + LOGD("executeJavaScriptOnBridge: Done"); +} +*/ +import "C" + +import ( + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + "sync" + "time" + "unsafe" + + "encoding/json" + + "github.com/wailsapp/wails/v3/internal/runtime" +) + +var ( + // Global reference to the app for JNI callbacks + globalApp *App + globalAppLock sync.RWMutex + + // JNI environment and class references + javaVM unsafe.Pointer + bridgeObject unsafe.Pointer + + // Android main function registration + androidMainFunc func() + androidMainLock sync.Mutex + + // App ready signal + appReady = make(chan struct{}) + appReadyOnce sync.Once +) + +func init() { + androidLogf("info", "🤖 [application_android.go] init() called") +} + +// RegisterAndroidMain registers the main function to be called when the Android app starts. +// This should be called from init() in your main.go file for Android builds. +// Example: +// +// func init() { +// application.RegisterAndroidMain(main) +// } +func RegisterAndroidMain(mainFunc func()) { + androidMainLock.Lock() + defer androidMainLock.Unlock() + androidMainFunc = mainFunc + androidLogf("info", "🤖 [application_android.go] Android main function registered") +} + +// signalAppReady signals that the app is ready to serve requests +func signalAppReady() { + appReadyOnce.Do(func() { + close(appReady) + androidLogf("info", "🤖 [application_android.go] App ready signal sent") + }) +} + +// waitForAppReady waits for the app to be ready, with a timeout +func waitForAppReady(timeout time.Duration) bool { + select { + case <-appReady: + return true + case <-time.After(timeout): + return false + } +} + +func androidLogf(level string, format string, a ...interface{}) { + msg := fmt.Sprintf(format, a...) + // For now, just use println - we'll connect to Android's Log.* later + println(fmt.Sprintf("[Android/%s] %s", level, msg)) +} + +func (a *App) platformRun() { + androidLogf("info", "🤖 [application_android.go] platformRun() called") + + // Store global reference for JNI callbacks + globalAppLock.Lock() + globalApp = a + globalAppLock.Unlock() + + // Signal that the app is ready to serve requests + signalAppReady() + + androidLogf("info", "🤖 [application_android.go] App ready, waiting for Android lifecycle...") + + // Block forever - Android manages the app lifecycle via JNI callbacks + select {} +} + +func (a *App) platformQuit() { + androidLogf("info", "🤖 [application_android.go] platformQuit() called") + // Android will handle app termination +} + +func (a *App) isDarkMode() bool { + // TODO: Query Android for dark mode status + return false +} + +func (a *App) isWindows() bool { + return false +} + +// Platform-specific app implementation for Android +type androidApp struct { + parent *App +} + +func newPlatformApp(app *App) *androidApp { + androidLogf("info", "🤖 [application_android.go] newPlatformApp() called") + return &androidApp{ + parent: app, + } +} + +func (a *androidApp) run() error { + androidLogf("info", "🤖 [application_android.go] androidApp.run() called") + + // Emit application started event + a.parent.Event.Emit("ApplicationStarted") + + a.parent.platformRun() + return nil +} + +func (a *androidApp) destroy() { + androidLogf("info", "🤖 [application_android.go] androidApp.destroy() called") +} + +func (a *androidApp) setIcon(_ []byte) { + // Android app icon is set through AndroidManifest.xml +} + +func (a *androidApp) name() string { + return a.parent.options.Name +} + +func (a *androidApp) GetFlags(options Options) map[string]any { + return nil +} + +func (a *androidApp) getAccentColor() string { + return "" +} + +func (a *androidApp) getCurrentWindowID() uint { + return 0 +} + +func (a *androidApp) hide() { + // Android manages app visibility +} + +func (a *androidApp) isDarkMode() bool { + return a.parent.isDarkMode() +} + +func (a *androidApp) on(_ uint) { + // Android event handling +} + +func (a *androidApp) setApplicationMenu(_ *Menu) { + // Android doesn't have application menus in the same way +} + +func (a *androidApp) show() { + // Android manages app visibility +} + +func (a *androidApp) showAboutDialog(_ string, _ string, _ []byte) { + // TODO: Implement Android about dialog +} + +func (a *androidApp) getPrimaryScreen() (*Screen, error) { + screens, err := getScreens() + if err != nil || len(screens) == 0 { + return nil, err + } + return screens[0], nil +} + +func (a *androidApp) getScreens() ([]*Screen, error) { + return getScreens() +} + +func (a *App) logPlatformInfo() { + // Log Android platform info + androidLogf("info", "Platform: Android") +} + +func (a *App) platformEnvironment() map[string]any { + return map[string]any{ + "platform": "android", + } +} + +func fatalHandler(errFunc func(error)) { + // Android fatal handler +} + +// JNI Export Functions - Called from Java + +//export Java_com_wails_app_WailsBridge_nativeInit +func Java_com_wails_app_WailsBridge_nativeInit(env *C.JNIEnv, obj C.jobject, bridge C.jobject) { + androidLogf("info", "🤖 [JNI] nativeInit called") + + // Store references for later use (legacy - keeping for compatibility) + javaVM = unsafe.Pointer(env) + bridgeObject = unsafe.Pointer(bridge) + + // Store JavaVM and bridge global reference for JNI callbacks + C.storeBridgeRef(env, bridge) + androidLogf("info", "🤖 [JNI] Bridge reference stored for JNI callbacks") + + // Start the registered main function in a goroutine + androidMainLock.Lock() + mainFunc := androidMainFunc + androidMainLock.Unlock() + + if mainFunc != nil { + androidLogf("info", "🤖 [JNI] Starting registered main function in goroutine") + go mainFunc() + } else { + androidLogf("warn", "🤖 [JNI] No main function registered! Call application.RegisterAndroidMain(main) in init()") + } + + androidLogf("info", "🤖 [JNI] nativeInit complete") +} + +//export Java_com_wails_app_WailsBridge_nativeShutdown +func Java_com_wails_app_WailsBridge_nativeShutdown(env *C.JNIEnv, obj C.jobject) { + androidLogf("info", "🤖 [JNI] nativeShutdown called") + + globalAppLock.Lock() + if globalApp != nil { + globalApp.Quit() + } + globalAppLock.Unlock() +} + +//export Java_com_wails_app_WailsBridge_nativeOnResume +func Java_com_wails_app_WailsBridge_nativeOnResume(env *C.JNIEnv, obj C.jobject) { + androidLogf("info", "🤖 [JNI] nativeOnResume called") + + globalAppLock.RLock() + app := globalApp + globalAppLock.RUnlock() + + if app != nil { + app.Event.Emit("ApplicationResumed") + } +} + +//export Java_com_wails_app_WailsBridge_nativeOnPause +func Java_com_wails_app_WailsBridge_nativeOnPause(env *C.JNIEnv, obj C.jobject) { + androidLogf("info", "🤖 [JNI] nativeOnPause called") + + globalAppLock.RLock() + app := globalApp + globalAppLock.RUnlock() + + if app != nil { + app.Event.Emit("ApplicationPaused") + } +} + +//export Java_com_wails_app_WailsBridge_nativeOnPageFinished +func Java_com_wails_app_WailsBridge_nativeOnPageFinished(env *C.JNIEnv, obj C.jobject, jurl C.jstring) { + cUrl := C.jstringToC(env, jurl) + defer C.releaseJString(env, jurl, cUrl) + url := C.GoString(cUrl) + + androidLogf("info", "🤖 [JNI] nativeOnPageFinished called: %s", url) + + globalAppLock.RLock() + app := globalApp + globalAppLock.RUnlock() + + if app == nil { + androidLogf("error", "🤖 [JNI] nativeOnPageFinished: app is nil") + return + } + + // Inject the runtime into the first window (with proper locking) + app.windowsLock.RLock() + windowCount := len(app.windows) + androidLogf("info", "🤖 [JNI] nativeOnPageFinished: window count = %d", windowCount) + for id, win := range app.windows { + androidLogf("info", "🤖 [JNI] Found window ID: %d", id) + if win != nil { + androidLogf("info", "🤖 [JNI] Injecting runtime.Core() into window %d", id) + // Get the runtime core JavaScript + runtimeJS := runtime.Core() + androidLogf("info", "🤖 [JNI] Runtime JS length: %d bytes", len(runtimeJS)) + app.windowsLock.RUnlock() + // IMPORTANT: We must bypass win.ExecJS because it queues if runtimeLoaded is false. + // On Android, we need to inject the runtime directly since the runtime hasn't been loaded yet. + // This is the bootstrap injection that enables the runtime to load. + androidLogf("info", "🤖 [JNI] Calling executeJavaScript directly (bypassing queue)") + executeJavaScript(runtimeJS) + // Emit event + app.Event.Emit("PageFinished", url) + return + } + } + app.windowsLock.RUnlock() + + androidLogf("warn", "🤖 [JNI] nativeOnPageFinished: no windows found to inject runtime") + // Emit event even if no windows + app.Event.Emit("PageFinished", url) +} + +//export Java_com_wails_app_WailsBridge_nativeServeAsset +func Java_com_wails_app_WailsBridge_nativeServeAsset(env *C.JNIEnv, obj C.jobject, jpath C.jstring, jmethod C.jstring, jheaders C.jstring) C.jbyteArray { + // Convert Java strings to Go strings + cPath := C.jstringToC(env, jpath) + cMethod := C.jstringToC(env, jmethod) + defer C.releaseJString(env, jpath, cPath) + defer C.releaseJString(env, jmethod, cMethod) + + goPath := C.GoString(cPath) + goMethod := C.GoString(cMethod) + + androidLogf("debug", "🤖 [JNI] nativeServeAsset: %s %s", goMethod, goPath) + + // Wait for the app to be ready (timeout after 10 seconds) + if !waitForAppReady(10 * time.Second) { + androidLogf("error", "🤖 [JNI] Timeout waiting for app to be ready") + return C.createByteArray(env, nil, 0) + } + + globalAppLock.RLock() + app := globalApp + globalAppLock.RUnlock() + + if app == nil || app.assets == nil { + androidLogf("error", "🤖 [JNI] App or assets not initialized after ready signal") + return C.createByteArray(env, nil, 0) + } + + // Serve the asset through the asset server + data, err := serveAssetForAndroid(app, goPath) + if err != nil { + androidLogf("error", "🤖 [JNI] Error serving asset %s: %v", goPath, err) + return C.createByteArray(env, nil, 0) + } + + androidLogf("debug", "🤖 [JNI] Serving asset %s (%d bytes)", goPath, len(data)) + + // Create Java byte array from the data + // Handle empty data case to avoid index out of range panic + if len(data) == 0 { + return C.createByteArray(env, nil, 0) + } + return C.createByteArray(env, unsafe.Pointer(&data[0]), C.int(len(data))) +} + +//export Java_com_wails_app_WailsBridge_nativeHandleMessage +func Java_com_wails_app_WailsBridge_nativeHandleMessage(env *C.JNIEnv, obj C.jobject, jmessage C.jstring) C.jstring { + // Convert Java string to Go string + cMessage := C.jstringToC(env, jmessage) + defer C.releaseJString(env, jmessage, cMessage) + + goMessage := C.GoString(cMessage) + + androidLogf("debug", "🤖 [JNI] nativeHandleMessage: %s", goMessage) + + globalAppLock.RLock() + app := globalApp + globalAppLock.RUnlock() + + if app == nil { + errorResponse := `{"error":"App not initialized"}` + return C.createJString(env, C.CString(errorResponse)) + } + + // Parse and handle the message + response := handleMessageForAndroid(app, goMessage) + return C.createJString(env, C.CString(response)) +} + +//export Java_com_wails_app_WailsBridge_nativeGetAssetMimeType +func Java_com_wails_app_WailsBridge_nativeGetAssetMimeType(env *C.JNIEnv, obj C.jobject, jpath C.jstring) C.jstring { + // Convert Java string to Go string + cPath := C.jstringToC(env, jpath) + defer C.releaseJString(env, jpath, cPath) + + goPath := C.GoString(cPath) + mimeType := getMimeTypeForPath(goPath) + return C.createJString(env, C.CString(mimeType)) +} + +// Helper functions + +func serveAssetForAndroid(app *App, path string) ([]byte, error) { + // Check if this is a runtime call (includes query string) + isRuntimeCall := strings.HasPrefix(path, "/wails/runtime") + + // Normalize path for regular assets (not runtime calls) + if !isRuntimeCall { + if path == "" || path == "/" { + path = "/index.html" + } + } + + // Ensure path starts with / + if len(path) > 0 && path[0] != '/' { + path = "/" + path + } + + // Check if asset server is available + if app.assets == nil { + return nil, fmt.Errorf("asset server not initialized") + } + + // Create a fake HTTP request + fullURL := "https://wails.localhost" + path + androidLogf("debug", "🤖 [serveAssetForAndroid] Creating request for: %s", fullURL) + + req, err := http.NewRequest("GET", fullURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + // For runtime calls (/wails/runtime), we need to add the window ID header + // This is required by the MessageProcessor to route the call correctly + if isRuntimeCall { + // Get the first window (on Android, there's typically only one) + windows := app.Window.GetAll() + androidLogf("debug", "🤖 [serveAssetForAndroid] Runtime call, found %d windows", len(windows)) + if len(windows) > 0 { + // Use the first window's ID + windowID := windows[0].ID() + req.Header.Set("x-wails-window-id", fmt.Sprintf("%d", windowID)) + androidLogf("debug", "🤖 [serveAssetForAndroid] Added window ID header: %d", windowID) + } else { + androidLogf("warn", "🤖 [serveAssetForAndroid] No windows available for runtime call") + } + } + + // Use httptest.ResponseRecorder to capture the response + recorder := httptest.NewRecorder() + + // Serve the request through the asset server + app.assets.ServeHTTP(recorder, req) + + // Check response status + result := recorder.Result() + defer result.Body.Close() + + // Read the response body + body, err := io.ReadAll(result.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + androidLogf("debug", "🤖 [serveAssetForAndroid] Response status: %d, body length: %d", result.StatusCode, len(body)) + + // For runtime calls, we need to return the body even for error responses + // so the JavaScript can see the error message + if isRuntimeCall { + if result.StatusCode != http.StatusOK { + androidLogf("warn", "🤖 [serveAssetForAndroid] Runtime call returned status %d: %s", result.StatusCode, string(body)) + } + // Return the body regardless of status - the JS will handle errors + return body, nil + } + + // For regular assets, check status codes + if result.StatusCode == http.StatusNotFound { + return nil, fmt.Errorf("asset not found: %s", path) + } + + if result.StatusCode != http.StatusOK { + return nil, fmt.Errorf("asset server error: status %d for %s", result.StatusCode, path) + } + + return body, nil +} + +func handleMessageForAndroid(app *App, message string) string { + // Parse the message + var msg map[string]interface{} + if err := json.Unmarshal([]byte(message), &msg); err != nil { + return fmt.Sprintf(`{"error":"%s"}`, err.Error()) + } + + // TODO: Route to appropriate handler based on message type + // For now, return success + return `{"success":true}` +} + +func getMimeTypeForPath(path string) string { + // Simple MIME type detection based on extension + switch { + case endsWith(path, ".html"), endsWith(path, ".htm"): + return "text/html" + case endsWith(path, ".js"), endsWith(path, ".mjs"): + return "application/javascript" + case endsWith(path, ".css"): + return "text/css" + case endsWith(path, ".json"): + return "application/json" + case endsWith(path, ".png"): + return "image/png" + case endsWith(path, ".jpg"), endsWith(path, ".jpeg"): + return "image/jpeg" + case endsWith(path, ".gif"): + return "image/gif" + case endsWith(path, ".svg"): + return "image/svg+xml" + case endsWith(path, ".ico"): + return "image/x-icon" + case endsWith(path, ".woff"): + return "font/woff" + case endsWith(path, ".woff2"): + return "font/woff2" + case endsWith(path, ".ttf"): + return "font/ttf" + default: + return "application/octet-stream" + } +} + +func endsWith(s, suffix string) bool { + return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix +} + +// executeJavaScript executes JavaScript code in the Android WebView via JNI callback +func executeJavaScript(js string) { + androidLogf("info", "🤖 executeJavaScript called, length: %d", len(js)) + if js == "" { + androidLogf("warn", "🤖 executeJavaScript: empty JS string") + return + } + + // Convert Go string to C string and call the JNI bridge + androidLogf("info", "🤖 executeJavaScript: calling C.executeJavaScriptOnBridge") + cJs := C.CString(js) + defer C.free(unsafe.Pointer(cJs)) + + C.executeJavaScriptOnBridge(cJs) + androidLogf("info", "🤖 executeJavaScript: done") +} diff --git a/v3/pkg/application/application_android_nocgo.go b/v3/pkg/application/application_android_nocgo.go new file mode 100644 index 000000000..045c5ae3f --- /dev/null +++ b/v3/pkg/application/application_android_nocgo.go @@ -0,0 +1,225 @@ +//go:build android && !cgo && !server + +package application + +import ( + "fmt" + "sync" + "unsafe" + + "encoding/json" +) + +var ( + // Global reference to the app for JNI callbacks + globalApp *App + globalAppLock sync.RWMutex + + // JNI environment and class references + javaVM unsafe.Pointer + bridgeObject unsafe.Pointer +) + +func init() { + androidLogf("info", "🤖 [application_android.go] init() called") +} + +func androidLogf(level string, format string, a ...interface{}) { + msg := fmt.Sprintf(format, a...) + // For now, just use println - we'll connect to Android's Log.* later + println(fmt.Sprintf("[Android/%s] %s", level, msg)) +} + +func (a *App) platformRun() { + androidLogf("info", "🤖 [application_android.go] platformRun() called") + + // Store global reference for JNI callbacks + globalAppLock.Lock() + globalApp = a + globalAppLock.Unlock() + + androidLogf("info", "🤖 [application_android.go] Waiting for Android lifecycle...") + + // Block forever - Android manages the app lifecycle via JNI callbacks + select {} +} + +func (a *App) platformQuit() { + androidLogf("info", "🤖 [application_android.go] platformQuit() called") + // Android will handle app termination +} + +func (a *App) isDarkMode() bool { + // TODO: Query Android for dark mode status + return false +} + +func (a *App) isWindows() bool { + return false +} + +// Platform-specific app implementation for Android +type androidApp struct { + parent *App +} + +func newPlatformApp(app *App) *androidApp { + androidLogf("info", "🤖 [application_android.go] newPlatformApp() called") + return &androidApp{ + parent: app, + } +} + +func (a *androidApp) run() error { + androidLogf("info", "🤖 [application_android.go] androidApp.run() called") + + // Wire common events + a.setupCommonEvents() + + // Emit application started event + a.parent.Event.Emit("ApplicationStarted") + + a.parent.platformRun() + return nil +} + +func (a *androidApp) destroy() { + androidLogf("info", "🤖 [application_android.go] androidApp.destroy() called") +} + +func (a *androidApp) setIcon(_ []byte) { + // Android app icon is set through AndroidManifest.xml +} + +func (a *androidApp) name() string { + return a.parent.options.Name +} + +func (a *androidApp) GetFlags(options Options) map[string]any { + return nil +} + +func (a *androidApp) getAccentColor() string { + return "" +} + +func (a *androidApp) getCurrentWindowID() uint { + return 0 +} + +func (a *androidApp) hide() { + // Android manages app visibility +} + +func (a *androidApp) isDarkMode() bool { + return a.parent.isDarkMode() +} + +func (a *androidApp) on(_ uint) { + // Android event handling +} + +func (a *androidApp) setApplicationMenu(_ *Menu) { + // Android doesn't have application menus in the same way +} + +func (a *androidApp) show() { + // Android manages app visibility +} + +func (a *androidApp) showAboutDialog(_ string, _ string, _ []byte) { + // TODO: Implement Android about dialog +} + +func (a *androidApp) getPrimaryScreen() (*Screen, error) { + screens, err := getScreens() + if err != nil || len(screens) == 0 { + return nil, err + } + return screens[0], nil +} + +func (a *androidApp) getScreens() ([]*Screen, error) { + return getScreens() +} + +func (a *App) logPlatformInfo() { + // Log Android platform info + androidLogf("info", "Platform: Android") +} + +func (a *App) platformEnvironment() map[string]any { + return map[string]any{ + "platform": "android", + } +} + +func fatalHandler(errFunc func(error)) { + // Android fatal handler +} + +// Helper functions + +func serveAssetForAndroid(app *App, path string) ([]byte, error) { + // Normalize path + if path == "" || path == "/" { + path = "/index.html" + } + + // TODO: Use the actual asset server to serve the file + // For now, return a placeholder + return nil, fmt.Errorf("asset serving not yet implemented: %s", path) +} + +func handleMessageForAndroid(app *App, message string) string { + // Parse the message + var msg map[string]interface{} + if err := json.Unmarshal([]byte(message), &msg); err != nil { + return fmt.Sprintf(`{"error":"%s"}`, err.Error()) + } + + // TODO: Route to appropriate handler based on message type + // For now, return success + return `{"success":true}` +} + +func getMimeTypeForPath(path string) string { + // Simple MIME type detection based on extension + switch { + case endsWith(path, ".html"), endsWith(path, ".htm"): + return "text/html" + case endsWith(path, ".js"), endsWith(path, ".mjs"): + return "application/javascript" + case endsWith(path, ".css"): + return "text/css" + case endsWith(path, ".json"): + return "application/json" + case endsWith(path, ".png"): + return "image/png" + case endsWith(path, ".jpg"), endsWith(path, ".jpeg"): + return "image/jpeg" + case endsWith(path, ".gif"): + return "image/gif" + case endsWith(path, ".svg"): + return "image/svg+xml" + case endsWith(path, ".ico"): + return "image/x-icon" + case endsWith(path, ".woff"): + return "font/woff" + case endsWith(path, ".woff2"): + return "font/woff2" + case endsWith(path, ".ttf"): + return "font/ttf" + default: + return "application/octet-stream" + } +} + +func endsWith(s, suffix string) bool { + return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix +} + +// executeJavaScript is a stub for non-cgo builds +func executeJavaScript(js string) { + androidLogf("warn", "executeJavaScript called but cgo is not enabled") +} diff --git a/v3/pkg/application/application_darwin.go b/v3/pkg/application/application_darwin.go new file mode 100644 index 000000000..1de18f632 --- /dev/null +++ b/v3/pkg/application/application_darwin.go @@ -0,0 +1,726 @@ +//go:build darwin && !ios && !server + +package application + +/* + +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13 + +#include "application_darwin.h" +#include "application_darwin_delegate.h" +#include "webview_window_darwin.h" +#include + +extern void registerListener(unsigned int event); + +#import +#import + +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; + }]; + + NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter]; + [center addObserver:appDelegate selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object:nil]; + + // Register the custom URL scheme handler + StartCustomProtocolHandler(); +} + +static bool isDarkMode(void) { + NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; + if (userDefaults == nil) { + return false; + } + + NSString *interfaceStyle = [userDefaults stringForKey:@"AppleInterfaceStyle"]; + if (interfaceStyle == nil) { + return false; + } + + return [interfaceStyle isEqualToString:@"Dark"]; +} + +static char* getAccentColor(void) { + @autoreleasepool { + NSColor *accentColor; + if (@available(macOS 10.14, *)) { + accentColor = [NSColor controlAccentColor]; + } else { + // Fallback to system blue for older macOS versions + accentColor = [NSColor systemBlueColor]; + } + // Convert to RGB color space + NSColor *rgbColor = [accentColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]]; + if (rgbColor == nil) { + rgbColor = accentColor; + } + // Get RGB components + CGFloat red, green, blue, alpha; + [rgbColor getRed:&red green:&green blue:&blue alpha:&alpha]; + // Convert to 0-255 range and format as rgb() string + int r = (int)(red * 255); + int g = (int)(green * 255); + int b = (int)(blue * 255); + NSString *colorString = [NSString stringWithFormat:@"rgb(%d,%d,%d)", r, g, b]; + return strdup([colorString UTF8String]); + } +} + +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]; + [NSApp abortModal]; + } +} + +// destroyApp destroys the 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]; + }); +} + +// Hide the application +static void hide(void) { + [NSApp hide:nil]; +} + +// Show the application +static void show(void) { + [NSApp unhide:nil]; +} + +static const char* serializationNSDictionary(void *dict) { + @autoreleasepool { + NSDictionary *nsDict = (__bridge NSDictionary *)dict; + + if ([NSJSONSerialization isValidJSONObject:nsDict]) { + NSError *error; + NSData *data = [NSJSONSerialization dataWithJSONObject:nsDict options:kNilOptions error:&error]; + NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; + + return strdup([result UTF8String]); + } + } + + return nil; +} + +static void startSingleInstanceListener(const char *uniqueID) { + // Convert to NSString + NSString *uid = [NSString stringWithUTF8String:uniqueID]; + [[NSDistributedNotificationCenter defaultCenter] addObserver:appDelegate + selector:@selector(handleSecondInstanceNotification:) name:uid object:nil]; +} +*/ +import "C" +import ( + "sync" + "time" + "unsafe" + + "encoding/json" + + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + "github.com/wailsapp/wails/v3/internal/operatingsystem" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type macosApp struct { + applicationMenu unsafe.Pointer + parent *App +} + +func (m *macosApp) isDarkMode() bool { + return bool(C.isDarkMode()) +} + +func (m *macosApp) getAccentColor() string { + accentColorC := C.getAccentColor() + defer C.free(unsafe.Pointer(accentColorC)) + return C.GoString(accentColorC) +} + +func getNativeApplication() *macosApp { + return globalApplication.impl.(*macosApp) +} + +func (m *macosApp) hide() { + C.hide() +} + +func (m *macosApp) show() { + C.show() +} + +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 { + if m.parent.options.SingleInstance != nil { + cUniqueID := C.CString(m.parent.options.SingleInstance.UniqueID) + defer C.free(unsafe.Pointer(cUniqueID)) + C.startSingleInstanceListener(cUniqueID) + } + // Add a hook to the ApplicationDidFinishLaunching event + m.parent.Event.OnApplicationEvent( + events.Mac.ApplicationDidFinishLaunching, + func(*ApplicationEvent) { + C.setApplicationShouldTerminateAfterLastWindowClosed( + C.bool(m.parent.options.Mac.ApplicationShouldTerminateAfterLastWindowClosed), + ) + C.setActivationPolicy(C.int(m.parent.options.Mac.ActivationPolicy)) + C.activateIgnoringOtherApps() + }, + ) + m.setupCommonEvents() + // setup event listeners + for eventID := range m.parent.applicationEventListeners { + m.on(eventID) + } + C.run() + return nil +} + +func (m *macosApp) destroy() { + C.destroyApp() +} + +func (m *macosApp) GetFlags(options Options) map[string]any { + if options.Flags == nil { + options.Flags = make(map[string]any) + } + return options.Flags +} + +func newPlatformApp(app *App) *macosApp { + C.init() + return &macosApp{ + parent: app, + } +} + +//export processApplicationEvent +func processApplicationEvent(eventID C.uint, data unsafe.Pointer) { + event := newApplicationEvent(events.ApplicationEventType(eventID)) + + if data != nil { + dataCStrJSON := C.serializationNSDictionary(data) + if dataCStrJSON != nil { + defer C.free(unsafe.Pointer(dataCStrJSON)) + + dataJSON := C.GoString(dataCStrJSON) + var result map[string]any + err := json.Unmarshal([]byte(dataJSON), &result) + + if err != nil { + panic(err) + } + + event.Context().setData(result) + } + } + + switch event.Id { + case uint(events.Mac.ApplicationDidChangeTheme): + isDark := globalApplication.Env.IsDarkMode() + event.Context().setIsDarkMode(isDark) + } + applicationEvents <- event +} + +//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, origin *C.char, isMainFrame bool) { + o := "" + if origin != nil { + o = C.GoString(origin) + } + windowMessageBuffer <- &windowMessage{ + windowId: uint(windowID), + message: C.GoString(message), + originInfo: &OriginInfo{ + Origin: o, + IsMainFrame: isMainFrame, + }, + } +} + +//export processURLRequest +func processURLRequest(windowID C.uint, wkUrlSchemeTask unsafe.Pointer) { + window, ok := globalApplication.Window.GetByID(uint(windowID)) + if !ok || window == nil { + globalApplication.debug("could not find window with id", "windowID", windowID) + return + } + + webviewRequests <- &webViewAssetRequest{ + Request: webview.NewRequest(wkUrlSchemeTask), + windowId: uint(windowID), + windowName: window.Name(), + } +} + +//export processWindowKeyDownEvent +func processWindowKeyDownEvent(windowID C.uint, acceleratorString *C.char) { + windowKeyEvents <- &windowKeyEvent{ + windowId: uint(windowID), + acceleratorString: C.GoString(acceleratorString), + } +} + +//export processDragItems +func processDragItems(windowID C.uint, arr **C.char, length C.int, x C.int, y C.int) { + var filenames []string + // Convert the C array to a Go slice + goSlice := (*[1 << 30]*C.char)(unsafe.Pointer(arr))[:length:length] + for _, str := range goSlice { + filenames = append(filenames, C.GoString(str)) + } + + globalApplication.debug( + "[DragDropDebug] processDragItems called", + "windowID", + windowID, + "fileCount", + len(filenames), + "x", + x, + "y", + y, + ) + targetWindow, ok := globalApplication.Window.GetByID(uint(windowID)) + if !ok || targetWindow == nil { + println("Error: processDragItems could not find window with ID:", uint(windowID)) + return + } + + globalApplication.debug( + "[DragDropDebug] processDragItems: Calling targetWindow.InitiateFrontendDropProcessing", + ) + targetWindow.InitiateFrontendDropProcessing(filenames, int(x), int(y)) +} + +//export macosOnDragEnter +func macosOnDragEnter(windowID C.uint) { + window, ok := globalApplication.Window.GetByID(uint(windowID)) + if !ok || window == nil { + return + } + + // Call JavaScript to show drag entered state + window.ExecJS("window._wails.handleDragEnter();") +} + +//export macosOnDragExit +func macosOnDragExit(windowID C.uint) { + window, ok := globalApplication.Window.GetByID(uint(windowID)) + if !ok || window == nil { + return + } + + // Call JavaScript to clean up drag state + window.ExecJS("window._wails.handleDragLeave();") +} + +var ( + // Pre-allocated buffer for drag JS calls to avoid allocations + dragOverJSBuffer = make([]byte, 128) // Increased for safety + dragOverJSMutex sync.Mutex // Protects dragOverJSBuffer + dragOverJSPrefix = []byte("window._wails.handleDragOver(") + + // Cache window references to avoid repeated lookups + windowImplCache sync.Map // windowID -> *macosWebviewWindow + + // Per-window drag throttle state + dragThrottle sync.Map // windowID -> *dragThrottleState +) + +type dragThrottleState struct { + mu sync.Mutex // Protects all fields below + lastX, lastY int + timer *time.Timer + pendingX int + pendingY int + hasPending bool +} + +// clearWindowDragCache removes cached references for a window +func clearWindowDragCache(windowID uint) { + windowImplCache.Delete(windowID) + + // Cancel any pending timer + if throttleVal, ok := dragThrottle.Load(windowID); ok { + if throttle, ok := throttleVal.(*dragThrottleState); ok { + throttle.mu.Lock() + if throttle.timer != nil { + throttle.timer.Stop() + } + throttle.mu.Unlock() + } + } + dragThrottle.Delete(windowID) +} + +// writeInt writes an integer to a byte slice and returns the number of bytes written +func writeInt(buf []byte, n int) int { + if n < 0 { + if len(buf) == 0 { + return 0 + } + buf[0] = '-' + return 1 + writeInt(buf[1:], -n) + } + if n == 0 { + if len(buf) == 0 { + return 0 + } + buf[0] = '0' + return 1 + } + + // Count digits + tmp := n + digits := 0 + for tmp > 0 { + digits++ + tmp /= 10 + } + + // Bounds check + if digits > len(buf) { + return 0 + } + + // Write digits in reverse + for i := digits - 1; i >= 0; i-- { + buf[i] = byte('0' + n%10) + n /= 10 + } + return digits +} + +//export macosOnDragOver +func macosOnDragOver(windowID C.uint, x C.int, y C.int) { + winID := uint(windowID) + intX, intY := int(x), int(y) + + // Get or create throttle state + throttleKey := winID + throttleVal, _ := dragThrottle.LoadOrStore(throttleKey, &dragThrottleState{ + lastX: intX, + lastY: intY, + }) + throttle := throttleVal.(*dragThrottleState) + + throttle.mu.Lock() + + // Update pending position + throttle.pendingX = intX + throttle.pendingY = intY + throttle.hasPending = true + + // If timer is already running, just update the pending position + if throttle.timer != nil { + throttle.mu.Unlock() + return + } + + // Apply 5-pixel threshold for immediate update + dx := intX - throttle.lastX + dy := intY - throttle.lastY + if dx < 0 { + dx = -dx + } + if dy < 0 { + dy = -dy + } + + // Check if we should send an immediate update + shouldSendNow := dx >= 5 || dy >= 5 + + if shouldSendNow { + // Update last position + throttle.lastX = intX + throttle.lastY = intY + throttle.hasPending = false + + // Send this update immediately (unlock before JS call to avoid deadlock) + throttle.mu.Unlock() + sendDragUpdate(winID, intX, intY) + throttle.mu.Lock() + } + + // Start 50ms timer for next update (whether we sent now or not) + throttle.timer = time.AfterFunc(50*time.Millisecond, func() { + // Execute on main thread to ensure UI updates + InvokeSync(func() { + throttle.mu.Lock() + // Clear timer reference + throttle.timer = nil + + // Send pending update if any + if throttle.hasPending { + pendingX, pendingY := throttle.pendingX, throttle.pendingY + throttle.lastX = pendingX + throttle.lastY = pendingY + throttle.hasPending = false + throttle.mu.Unlock() + sendDragUpdate(winID, pendingX, pendingY) + } else { + throttle.mu.Unlock() + } + }) + }) + throttle.mu.Unlock() +} + +// sendDragUpdate sends the actual drag update to JavaScript +func sendDragUpdate(winID uint, x, y int) { + // Try cached implementation first + var darwinImpl *macosWebviewWindow + var needsExecJS bool + + if cached, found := windowImplCache.Load(winID); found { + darwinImpl = cached.(*macosWebviewWindow) + if darwinImpl != nil && darwinImpl.nsWindow != nil { + needsExecJS = true + } else { + // Invalid cache entry, remove it + windowImplCache.Delete(winID) + } + } + + if !needsExecJS { + // Fallback to full lookup + window, ok := globalApplication.Window.GetByID(winID) + if !ok || window == nil { + return + } + + // Type assert to WebviewWindow + webviewWindow, ok := window.(*WebviewWindow) + if !ok || webviewWindow == nil { + return + } + + // Get implementation + darwinImpl, ok = webviewWindow.impl.(*macosWebviewWindow) + if !ok { + return + } + + // Cache for next time + windowImplCache.Store(winID, darwinImpl) + needsExecJS = true + } + + if !needsExecJS || darwinImpl == nil { + return + } + + // Protect shared buffer access + dragOverJSMutex.Lock() + + // Build JS string with zero allocations + // Format: "window._wails.handleDragOver(X,Y)" + // Max length with int32 coords: 30 + 11 + 1 + 11 + 1 + 1 = 55 bytes + n := copy(dragOverJSBuffer[:], dragOverJSPrefix) + n += writeInt(dragOverJSBuffer[n:], x) + if n < len(dragOverJSBuffer) { + dragOverJSBuffer[n] = ',' + n++ + } + n += writeInt(dragOverJSBuffer[n:], y) + if n < len(dragOverJSBuffer) { + dragOverJSBuffer[n] = ')' + n++ + } + if n < len(dragOverJSBuffer) { + dragOverJSBuffer[n] = 0 // null terminate for C + } else { + // Buffer overflow - this should not happen with 128 byte buffer + dragOverJSMutex.Unlock() + return + } + + // Call JavaScript with zero allocations + darwinImpl.execJSDragOver(dragOverJSBuffer[:n+1]) // Include null terminator + dragOverJSMutex.Unlock() +} + +//export processMenuItemClick +func processMenuItemClick(menuID C.uint) { + menuItemClicked <- uint(menuID) +} + +//export shouldQuitApplication +func shouldQuitApplication() C.bool { + // TODO: This should be configurable + return C.bool(globalApplication.shouldQuit()) +} + +//export cleanup +func cleanup() { + globalApplication.cleanup() +} + +func (a *App) logPlatformInfo() { + info, err := operatingsystem.Info() + if err != nil { + a.error("error getting OS info: %w", err) + return + } + + a.info("Platform Info:", info.AsLogSlice()...) + +} + +func (a *App) platformEnvironment() map[string]any { + return map[string]any{} +} + +func fatalHandler(errFunc func(error)) { + return +} + +//export HandleOpenFile +func HandleOpenFile(filePath *C.char) { + goFilepath := C.GoString(filePath) + // Create new application event context + eventContext := newApplicationEventContext() + eventContext.setOpenedWithFile(goFilepath) + // EmitEvent application started event + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Common.ApplicationOpenedWithFile), + ctx: eventContext, + } +} + +//export HandleOpenURL +func HandleOpenURL(urlCString *C.char) { + urlString := C.GoString(urlCString) + eventContext := newApplicationEventContext() + eventContext.setURL(urlString) + + // Emit the standard event with the URL string as data + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Common.ApplicationLaunchedWithUrl), + ctx: eventContext, + } +} diff --git a/v3/pkg/application/application_darwin.h b/v3/pkg/application/application_darwin.h new file mode 100644 index 000000000..d4f7bc475 --- /dev/null +++ b/v3/pkg/application/application_darwin.h @@ -0,0 +1,11 @@ +//go:build darwin && !ios + +#ifndef application_h +#define application_h + +static void init(void); +static void run(void); +static void setActivationPolicy(int policy); +static char *getAppName(void); + +#endif \ No newline at end of file diff --git a/v3/pkg/application/application_darwin_delegate.h b/v3/pkg/application/application_darwin_delegate.h new file mode 100644 index 000000000..4e7722e4a --- /dev/null +++ b/v3/pkg/application/application_darwin_delegate.h @@ -0,0 +1,25 @@ +//go:build darwin && !ios + +#ifndef appdelegate_h +#define appdelegate_h + +#import + +@interface AppDelegate : NSResponder +@property bool shouldTerminateWhenLastWindowClosed; +@property bool shuttingDown; +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app; +@end + +extern void HandleOpenFile(char *); + +// Declarations for Apple Event based custom URL handling and universal link +extern void HandleOpenURL(char*); + +@interface CustomProtocolSchemeHandler : NSObject ++ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent; +@end + +void StartCustomProtocolHandler(void); + +#endif /* appdelegate_h */ diff --git a/v3/pkg/application/application_darwin_delegate.m b/v3/pkg/application/application_darwin_delegate.m new file mode 100644 index 000000000..6b33c1421 --- /dev/null +++ b/v3/pkg/application/application_darwin_delegate.m @@ -0,0 +1,207 @@ +//go:build darwin && !ios +#import "application_darwin_delegate.h" +#import "../events/events_darwin.h" +#import // For Apple Event constants +extern bool hasListeners(unsigned int); +extern bool shouldQuitApplication(); +extern void cleanup(); +extern void handleSecondInstanceData(char * message); +@implementation AppDelegate +- (void)dealloc +{ + [super dealloc]; +} +-(BOOL)application:(NSApplication *)sender openFile:(NSString *)filename + { + const char* utf8FileName = filename.UTF8String; + HandleOpenFile((char*)utf8FileName); + return YES; + } +- (BOOL)application:(NSApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> * _Nullable))restorationHandler { + if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { + NSURL *url = userActivity.webpageURL; + if (url) { + HandleOpenURL((char*)[[url absoluteString] UTF8String]); + return YES; + } + } + return NO; +} +// Create the applicationShouldTerminateAfterLastWindowClosed: method +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication +{ + return self.shouldTerminateWhenLastWindowClosed; +} +- (void)themeChanged:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeTheme) ) { + processApplicationEvent(EventApplicationDidChangeTheme, NULL); + } +} +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { + if( ! shouldQuitApplication() ) { + return NSTerminateCancel; + } + if( !self.shuttingDown ) { + self.shuttingDown = true; + cleanup(); + } + return NSTerminateNow; +} +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app +{ + return YES; +} +- (BOOL)applicationShouldHandleReopen:(NSNotification *)notification + hasVisibleWindows:(BOOL)flag { // Changed from NSApplication to NSNotification + if( hasListeners(EventApplicationShouldHandleReopen) ) { + processApplicationEvent(EventApplicationShouldHandleReopen, @{@"hasVisibleWindows": @(flag)}); + } + return TRUE; +} +- (void)handleSecondInstanceNotification:(NSNotification *)note; +{ + if (note.userInfo[@"message"] != nil) { + NSString *message = note.userInfo[@"message"]; + const char* utf8Message = message.UTF8String; + handleSecondInstanceData((char*)utf8Message); + } +} +// GENERATED EVENTS START +- (void)applicationDidBecomeActive:(NSNotification *)notification { + if( hasListeners(EventApplicationDidBecomeActive) ) { + processApplicationEvent(EventApplicationDidBecomeActive, NULL); + } +} + +- (void)applicationDidChangeBackingProperties:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeBackingProperties) ) { + processApplicationEvent(EventApplicationDidChangeBackingProperties, NULL); + } +} + +- (void)applicationDidChangeEffectiveAppearance:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeEffectiveAppearance) ) { + processApplicationEvent(EventApplicationDidChangeEffectiveAppearance, NULL); + } +} + +- (void)applicationDidChangeIcon:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeIcon) ) { + processApplicationEvent(EventApplicationDidChangeIcon, NULL); + } +} + +- (void)applicationDidChangeOcclusionState:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeOcclusionState) ) { + processApplicationEvent(EventApplicationDidChangeOcclusionState, NULL); + } +} + +- (void)applicationDidChangeScreenParameters:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeScreenParameters) ) { + processApplicationEvent(EventApplicationDidChangeScreenParameters, NULL); + } +} + +- (void)applicationDidChangeStatusBarFrame:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeStatusBarFrame) ) { + processApplicationEvent(EventApplicationDidChangeStatusBarFrame, NULL); + } +} + +- (void)applicationDidChangeStatusBarOrientation:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeStatusBarOrientation) ) { + processApplicationEvent(EventApplicationDidChangeStatusBarOrientation, NULL); + } +} + +- (void)applicationDidFinishLaunching:(NSNotification *)notification { + if( hasListeners(EventApplicationDidFinishLaunching) ) { + processApplicationEvent(EventApplicationDidFinishLaunching, NULL); + } +} + +- (void)applicationDidHide:(NSNotification *)notification { + if( hasListeners(EventApplicationDidHide) ) { + processApplicationEvent(EventApplicationDidHide, NULL); + } +} + +- (void)applicationDidResignActive:(NSNotification *)notification { + if( hasListeners(EventApplicationDidResignActive) ) { + processApplicationEvent(EventApplicationDidResignActive, NULL); + } +} + +- (void)applicationDidUnhide:(NSNotification *)notification { + if( hasListeners(EventApplicationDidUnhide) ) { + processApplicationEvent(EventApplicationDidUnhide, NULL); + } +} + +- (void)applicationDidUpdate:(NSNotification *)notification { + if( hasListeners(EventApplicationDidUpdate) ) { + processApplicationEvent(EventApplicationDidUpdate, NULL); + } +} + +- (void)applicationWillBecomeActive:(NSNotification *)notification { + if( hasListeners(EventApplicationWillBecomeActive) ) { + processApplicationEvent(EventApplicationWillBecomeActive, NULL); + } +} + +- (void)applicationWillFinishLaunching:(NSNotification *)notification { + if( hasListeners(EventApplicationWillFinishLaunching) ) { + processApplicationEvent(EventApplicationWillFinishLaunching, NULL); + } +} + +- (void)applicationWillHide:(NSNotification *)notification { + if( hasListeners(EventApplicationWillHide) ) { + processApplicationEvent(EventApplicationWillHide, NULL); + } +} + +- (void)applicationWillResignActive:(NSNotification *)notification { + if( hasListeners(EventApplicationWillResignActive) ) { + processApplicationEvent(EventApplicationWillResignActive, NULL); + } +} + +- (void)applicationWillTerminate:(NSNotification *)notification { + if( hasListeners(EventApplicationWillTerminate) ) { + processApplicationEvent(EventApplicationWillTerminate, NULL); + } +} + +- (void)applicationWillUnhide:(NSNotification *)notification { + if( hasListeners(EventApplicationWillUnhide) ) { + processApplicationEvent(EventApplicationWillUnhide, NULL); + } +} + +- (void)applicationWillUpdate:(NSNotification *)notification { + if( hasListeners(EventApplicationWillUpdate) ) { + processApplicationEvent(EventApplicationWillUpdate, NULL); + } +} + +// GENERATED EVENTS END +@end +// Implementation for Apple Event based custom URL handling +@implementation CustomProtocolSchemeHandler ++ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { + NSString *urlStr = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; + if (urlStr) { + HandleOpenURL((char*)[urlStr UTF8String]); + } +} +@end +void StartCustomProtocolHandler(void) { + NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager]; + [appleEventManager setEventHandler:[CustomProtocolSchemeHandler class] + andSelector:@selector(handleGetURLEvent:withReplyEvent:) + forEventClass:kInternetEventClass + andEventID: kAEGetURL]; +} diff --git a/v3/pkg/application/application_debug.go b/v3/pkg/application/application_debug.go new file mode 100644 index 000000000..bc850c3e8 --- /dev/null +++ b/v3/pkg/application/application_debug.go @@ -0,0 +1,71 @@ +//go:build !production + +package application + +import ( + "github.com/go-git/go-git/v5" + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/internal/version" + "path/filepath" + "runtime/debug" +) + +// BuildSettings contains the build settings for the application +var BuildSettings map[string]string + +// BuildInfo contains the build info for the application +var BuildInfo *debug.BuildInfo + +func init() { + var ok bool + BuildInfo, ok = debug.ReadBuildInfo() + if !ok { + return + } + BuildSettings = lo.Associate(BuildInfo.Settings, func(setting debug.BuildSetting) (string, string) { + return setting.Key, setting.Value + }) +} + +// We use this to patch the application to production mode. +func newApplication(options Options) *App { + result := &App{ + isDebugMode: true, + options: options, + } + result.init() + return result +} + +func (a *App) logStartup() { + var args []any + + // BuildInfo is nil when build with garble + if BuildInfo == nil { + return + } + + wailsPackage, _ := lo.Find(BuildInfo.Deps, func(dep *debug.Module) bool { + return dep.Path == "github.com/wailsapp/wails/v3" + }) + + wailsVersion := version.String() + if wailsPackage != nil && wailsPackage.Replace != nil { + wailsVersion = "(local) => " + filepath.ToSlash(wailsPackage.Replace.Path) + // Get the latest commit hash + repo, err := git.PlainOpen(filepath.Join(wailsPackage.Replace.Path, "..")) + if err == nil { + head, err := repo.Head() + if err == nil { + wailsVersion += " (" + head.Hash().String()[:8] + ")" + } + } + } + args = append(args, "Wails", wailsVersion) + args = append(args, "Compiler", BuildInfo.GoVersion) + for key, value := range BuildSettings { + args = append(args, key, value) + } + + a.info("Build Info:", args...) +} diff --git a/v3/pkg/application/application_dev.go b/v3/pkg/application/application_dev.go new file mode 100644 index 000000000..e12033e33 --- /dev/null +++ b/v3/pkg/application/application_dev.go @@ -0,0 +1,51 @@ +//go:build !production + +package application + +import ( + "net/http" + "time" + + "github.com/wailsapp/wails/v3/internal/assetserver" +) + +var devMode = false + +func (a *App) preRun() error { + // Check for frontend server url + frontendURL := assetserver.GetDevServerURL() + if frontendURL != "" { + devMode = true + // We want to check if the frontend server is running by trying to http get the url + // and if it is not, we wait 500ms and try again for a maximum of 10 times. If it is + // still not available, we return an error. + // This is to allow the frontend server to start up before the backend server. + client := http.Client{} + a.Logger.Info("Waiting for frontend dev server to start...", "url", frontendURL) + for i := 0; i < 10; i++ { + _, err := client.Get(frontendURL) + if err == nil { + a.Logger.Info("Connected to frontend dev server!") + return nil + } + // Wait 500ms + time.Sleep(500 * time.Millisecond) + if i%2 == 0 { + a.Logger.Info("Retrying...") + } + } + a.fatal("unable to connect to frontend server. Please check it is running - FRONTEND_DEVSERVER_URL='%s'", frontendURL) + } + return nil +} + +func (a *App) postQuit() { + if devMode { + a.Logger.Info("The application has terminated, but the watcher is still running.") + a.Logger.Info("To terminate the watcher, press CTRL+C") + } +} + +func (a *App) enableDevTools() { + +} diff --git a/v3/pkg/application/application_ios.go b/v3/pkg/application/application_ios.go new file mode 100644 index 000000000..6af32bf5e --- /dev/null +++ b/v3/pkg/application/application_ios.go @@ -0,0 +1,462 @@ +//go:build ios && !server + +package application + +/* +#cgo CFLAGS: -x objective-c -fobjc-arc +#cgo LDFLAGS: -framework Foundation -framework UIKit -framework WebKit + +#include +#include +#include "application_ios.h" +#include "webview_window_ios.h" + +*/ +import "C" + +import ( + "fmt" + "strings" + "time" + "unsafe" + + "encoding/json" + + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func iosConsoleLogf(level string, format string, a ...interface{}) { + msg := fmt.Sprintf(format, a...) + clevel := C.CString(level) + cmsg := C.CString(msg) + defer C.free(unsafe.Pointer(clevel)) + defer C.free(unsafe.Pointer(cmsg)) + C.ios_console_log(clevel, cmsg) +} + +func init() { + iosConsoleLogf("info", "🔵 [application_ios.go] START init()") + // For iOS, we need to handle signals differently + // Disable signal handling to avoid conflicts with iOS + // DO NOT call runtime.LockOSThread() - it causes signal handling issues on iOS! + iosConsoleLogf("info", "🔵 [application_ios.go] Skipping runtime.LockOSThread() on iOS") + + // Disable all signal handling on iOS + // iOS apps run in a sandboxed environment where signal handling is restricted + iosConsoleLogf("info", "🔵 [application_ios.go] END init()") +} + +//export init_go +func init_go() { + iosConsoleLogf("info", "🔵 [application_ios.go] init_go() called from iOS") + // This is called from the iOS main function + // to initialize the Go runtime +} + +func (a *App) platformRun() { + iosConsoleLogf("info", "🔵 [application_ios.go] START platformRun()") + + iosConsoleLogf("info", "🔵 [application_ios.go] platformRun called, initializing...") + + // Initialize what we need for the Go side + iosConsoleLogf("info", "🔵 [application_ios.go] About to call C.ios_app_init()") + C.ios_app_init() + iosConsoleLogf("info", "🔵 [application_ios.go] C.ios_app_init() returned") + + // Wait a bit for the UI to be ready (UIApplicationMain is running in main thread) + // The app delegate's didFinishLaunchingWithOptions will be called + iosConsoleLogf("info", "🔵 [application_ios.go] Waiting for UI to be ready...") + time.Sleep(2 * time.Second) // Give the app delegate time to initialize + + // The WebView will be created when the window runs (via app.Window.NewWithOptions in main.go) + iosConsoleLogf("info", "🔵 [application_ios.go] WebView creation will be handled by window manager") + + // UIApplicationMain is running in the main thread (called from main.m) + // We just need to keep the Go runtime alive + iosConsoleLogf("info", "🔵 [application_ios.go] Blocking to keep Go runtime alive...") + select {} // Block forever +} + +func (a *App) platformQuit() { + C.ios_app_quit() +} + +func (a *App) isDarkMode() bool { + return bool(C.ios_is_dark_mode()) +} + +func (a *App) isWindows() bool { + return false +} + +//export LogInfo +func LogInfo(source *C.char, message *C.char) { + goSource := C.GoString(source) + goMessage := C.GoString(message) + + // Add iOS marker for HTML logger + iosConsoleLogf("info", "[iOS-%s] %s", goSource, goMessage) + + if globalApplication != nil && globalApplication.Logger != nil { + globalApplication.info("iOS log", "source", goSource, "message", goMessage) + } +} + +// Platform-specific app implementation for iOS +type iosApp struct { + parent *App +} + +// newPlatformApp creates an iosApp for the provided App and applies iOS-specific +// configuration derived from app.options. It sets input accessory visibility, +// scrolling/bounce/indicator behavior, navigation gestures, link preview, +// media playback, inspector, user agent strings, app background color, and +// native tabs (marshaling items to JSON when enabled). The function invokes +// platform bindings to apply these settings and returns the configured *iosApp. +func newPlatformApp(app *App) *iosApp { + iosConsoleLogf("info", "🔵 [application_ios.go] START newPlatformApp()") + // iOS initialization + result := &iosApp{ + parent: app, + } + // Configure input accessory visibility according to options + // Default: false (show accessory) when not explicitly set to true + disable := false + if app != nil { + disable = app.options.IOS.DisableInputAccessoryView + } + C.ios_set_disable_input_accessory(C.bool(disable)) + iosConsoleLogf("info", "🔵 [application_ios.go] Input accessory view %s", map[bool]string{true: "DISABLED", false: "ENABLED"}[disable]) + + // Scrolling / Bounce / Indicators (defaults enabled; using Disable* flags) + C.ios_set_disable_scroll(C.bool(app.options.IOS.DisableScroll)) + C.ios_set_disable_bounce(C.bool(app.options.IOS.DisableBounce)) + C.ios_set_disable_scroll_indicators(C.bool(app.options.IOS.DisableScrollIndicators)) + + // Navigation gestures (Enable*) + C.ios_set_enable_back_forward_gestures(C.bool(app.options.IOS.EnableBackForwardNavigationGestures)) + + // Link preview (Disable*) + C.ios_set_disable_link_preview(C.bool(app.options.IOS.DisableLinkPreview)) + + // Media playback + C.ios_set_enable_inline_media_playback(C.bool(app.options.IOS.EnableInlineMediaPlayback)) + C.ios_set_enable_autoplay_without_user_action(C.bool(app.options.IOS.EnableAutoplayWithoutUserAction)) + + // Inspector (Disable*) + C.ios_set_disable_inspectable(C.bool(app.options.IOS.DisableInspectable)) + + // User agent strings + if ua := strings.TrimSpace(app.options.IOS.UserAgent); ua != "" { + cua := C.CString(ua) + C.ios_set_user_agent(cua) + C.free(unsafe.Pointer(cua)) + } + if appName := strings.TrimSpace(app.options.IOS.ApplicationNameForUserAgent); appName != "" { + cname := C.CString(appName) + C.ios_set_app_name_for_user_agent(cname) + C.free(unsafe.Pointer(cname)) + } + // App-wide background colour for iOS window (pre-WebView) + if app.options.IOS.AppBackgroundColourSet { + rgba := app.options.IOS.BackgroundColour + C.ios_set_app_background_color( + C.uchar(rgba.Red), C.uchar(rgba.Green), C.uchar(rgba.Blue), C.uchar(rgba.Alpha), C.bool(true), + ) + } else { + // Ensure it's marked as not set to allow delegate to fallback to white + C.ios_set_app_background_color(255, 255, 255, 255, C.bool(false)) + } + // Native tabs option: only enable when explicitly requested + if app.options.IOS.EnableNativeTabs { + if len(app.options.IOS.NativeTabsItems) > 0 { + if data, err := json.Marshal(app.options.IOS.NativeTabsItems); err == nil { + cjson := C.CString(string(data)) + C.ios_native_tabs_set_items_json(cjson) + C.free(unsafe.Pointer(cjson)) + } else if globalApplication != nil { + globalApplication.error("Failed to marshal IOS.NativeTabsItems: %v", err) + } + } + C.ios_native_tabs_set_enabled(C.bool(true)) + } + + iosConsoleLogf("info", "🔵 [application_ios.go] END newPlatformApp() - iosApp created") + return result +} + +func (a *iosApp) run() error { + iosConsoleLogf("info", "🔵 [application_ios.go] START iosApp.run()") + + // Initialize and create the WebView + // UIApplicationMain is already running in the main thread (from main.m) + // Wire common events (e.g. map ApplicationDidFinishLaunching → Common.ApplicationStarted) + a.setupCommonEvents() + iosConsoleLogf("info", "🔵 [application_ios.go] About to call parent.platformRun()") + a.parent.platformRun() + + // platformRun blocks forever with select{} + // If we get here, something went wrong + iosConsoleLogf("error", "🔵 [application_ios.go] ERROR: platformRun() returned unexpectedly") + return nil +} + +func (a *iosApp) destroy() { + iosConsoleLogf("info", "🔵 [application_ios.go] iosApp.destroy() called") + // Cleanup iOS resources +} + +func (a *iosApp) setIcon(_ []byte) { + // iOS app icon is set through Info.plist +} + +func (a *iosApp) name() string { + return a.parent.options.Name +} + +func (a *iosApp) GetFlags(options Options) map[string]any { + return nil +} + +// dispatchOnMainThread is implemented in mainthread_ios.go + +func (a *iosApp) getAccentColor() string { + // iOS accent color + return "" +} + +func (a *iosApp) getCurrentWindowID() uint { + // iOS current window ID + return 0 +} + +func (a *iosApp) hide() { + // iOS hide application - minimize to background +} + +func (a *iosApp) isDarkMode() bool { + return a.parent.isDarkMode() +} + +// isOnMainThread is implemented in mainthread_ios.go + +func (a *iosApp) on(_ uint) { + // iOS event handling +} + +func (a *iosApp) setApplicationMenu(_ *Menu) { + // iOS doesn't have application menus +} + +func (a *iosApp) show() { + // iOS show application +} + +func (a *iosApp) showAboutDialog(_ string, _ string, _ []byte) { + // iOS about dialog +} + +func (a *iosApp) getPrimaryScreen() (*Screen, error) { + screens, err := getScreens() + if err != nil || len(screens) == 0 { + return nil, err + } + return screens[0], nil +} + +func (a *iosApp) getScreens() ([]*Screen, error) { + return getScreens() +} + +func (a *App) logPlatformInfo() { + // Log iOS platform info +} + +func (a *App) platformEnvironment() map[string]any { + return map[string]any{ + "platform": "ios", + } +} + +func fatalHandler(errFunc func(error)) { + // iOS fatal handler +} + +// ExecuteJavaScript runs JavaScript code in the WebView +func (a *App) ExecuteJavaScript(windowID uint, js string) { + cjs := C.CString(js) + defer C.free(unsafe.Pointer(cjs)) + C.ios_execute_javascript(C.uint(windowID), cjs) +} + +// ServeAssetRequest handles requests from the WebView +// +//export ServeAssetRequest +func ServeAssetRequest(windowID C.uint, urlSchemeTask unsafe.Pointer) { + iosConsoleLogf("info", "[iOS-ServeAssetRequest] 🔵 Called with windowID=%d", windowID) + + // Route the request through the webviewRequests channel to use the asset server + go func() { + iosConsoleLogf("info", "[iOS-ServeAssetRequest] 🔵 Inside goroutine") + + // Use the webview package's NewRequest to wrap the task pointer + req := webview.NewRequest(urlSchemeTask) + url, _ := req.URL() + + // Log every single request with clear markers + iosConsoleLogf("info", "===============================================") + iosConsoleLogf("info", "[iOS-REQUEST] 🌐 RECEIVED REQUEST FOR: %s", url) + iosConsoleLogf("info", "===============================================") + + // Special CSS logging with big markers + if strings.Contains(url, ".css") || strings.Contains(url, "style") { + iosConsoleLogf("warn", "🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨") + iosConsoleLogf("warn", "[iOS-CSS] CSS FILE REQUESTED: %s", url) + iosConsoleLogf("warn", "🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨") + } + + // Log images separately + if strings.Contains(url, ".png") || strings.Contains(url, ".jpg") || strings.Contains(url, ".svg") { + iosConsoleLogf("info", "[iOS-IMAGE] 🇼 %s", url) + } + + // Log JS files + if strings.Contains(url, ".js") { + iosConsoleLogf("info", "[iOS-JS] ⚙️ %s", url) + } + + // Try to resolve the window name from the window ID so the AssetServer + // receives both x-wails-window-id and x-wails-window-name headers. + winName := "" + if globalApplication != nil { + if window, ok := globalApplication.Window.GetByID(uint(windowID)); ok && window != nil { + winName = window.Name() + } else { + iosConsoleLogf("warn", "[iOS-ServeAssetRequest] 🟠 Could not resolve window name for id=%d", windowID) + } + } + if winName != "" { + iosConsoleLogf("info", "[iOS-ServeAssetRequest] ✅ Resolved window name: %s (id=%d)", winName, windowID) + } + + request := &webViewAssetRequest{ + Request: req, + windowId: uint(windowID), + windowName: winName, + } + + // Send through the channel to be handled by the asset server + iosConsoleLogf("info", "[iOS-ServeAssetRequest] 🔵 Sending to webviewRequests channel") + webviewRequests <- request + iosConsoleLogf("info", "[iOS-ServeAssetRequest] 🔵 Request sent to channel successfully") + }() +} + +// HandleJSMessage handles messages from JavaScript +// +//export HandleJSMessage +func HandleJSMessage(windowID C.uint, message *C.char) { + msg := C.GoString(message) + + // Try to parse as JSON first + var msgData map[string]interface{} + if err := json.Unmarshal([]byte(msg), &msgData); err == nil && msgData != nil { + if name, ok := msgData["name"].(string); ok && name != "" { + // Special handling for asset debug messages + if name == "asset-debug" { + if data, ok := msgData["data"].(map[string]interface{}); ok { + iosConsoleLogf("info", "🔍 CLIENT ASSET DEBUG: %s %s - %s (status: %v)", + data["type"], data["name"], data["src"], data["status"]) + if contentType, ok := data["contentType"].(map[string]interface{}); ok { + iosConsoleLogf("info", "🔍 CLIENT CONTENT-TYPE: %s = %v", data["name"], contentType) + } + if code, ok := data["code"].(map[string]interface{}); ok { + iosConsoleLogf("info", "🔍 CLIENT HTTP CODE: %s = %v", data["name"], code) + } + if errorMsg, ok := data["error"].(map[string]interface{}); ok { + iosConsoleLogf("error", "🔍 CLIENT ERROR: %s = %v", data["name"], errorMsg) + } + } + return // Don't send asset-debug messages to the main event system + } + + if globalApplication != nil { + globalApplication.info("HandleJSMessage received from client", "name", name) + } + windowMessageBuffer <- &windowMessage{ + windowId: uint(windowID), + message: name, + } + return + } + // Fallback for structured payloads without a "name" field + if name, ok := msgData["message"].(string); ok && name != "" { + if globalApplication != nil { + globalApplication.info("HandleJSMessage received raw message field from client", "name", name) + } + windowMessageBuffer <- &windowMessage{ + windowId: uint(windowID), + message: name, + } + return + } + } else { + if globalApplication != nil { + globalApplication.error("[HandleJSMessage] Failed to parse JSON: %v", err) + } + iosConsoleLogf("warn", "🔍 RAW JS MESSAGE (unparsed JSON): %s", msg) + } + + // If not JSON or JSON without name/message, treat the entire payload as a string event + if msg != "" { + if globalApplication != nil { + globalApplication.info("HandleJSMessage received raw message from client", "message", msg) + } + windowMessageBuffer <- &windowMessage{ + windowId: uint(windowID), + message: msg, + } + return + } + + iosConsoleLogf("warn", "[HandleJSMessage] Ignored empty JS message") +} + +// Note: applicationEvents and windowEvents are already defined in events.go +// We'll use those existing channels + +type iosWindowEvent struct { + WindowID uint + EventID uint +} + +//export processApplicationEvent +func processApplicationEvent(eventID C.uint, data unsafe.Pointer) { + iosConsoleLogf("info", "🔵 [application_ios.go] processApplicationEvent called with eventID: %d", eventID) + + // Create and send the application event + event := newApplicationEvent(events.ApplicationEventType(eventID)) + + // Send to the applicationEvents channel for processing + applicationEvents <- event + + iosConsoleLogf("info", "🔵 [application_ios.go] Application event sent to channel: %d", eventID) +} + +//export processWindowEvent +func processWindowEvent(windowID C.uint, eventID C.uint) { + // For now, just log the event + iosConsoleLogf("info", "iOS: Window event received - Window: %d, Event: %d", windowID, eventID) + windowEvents <- &windowEvent{ + WindowID: uint(windowID), + EventID: uint(eventID), + } +} + +//export hasListeners +func hasListeners(eventID C.uint) C.bool { + // For now, return true to enable all events + // TODO: Check actual listener registration + return C.bool(true) +} \ No newline at end of file diff --git a/v3/pkg/application/application_ios.h b/v3/pkg/application/application_ios.h new file mode 100644 index 000000000..081298ca1 --- /dev/null +++ b/v3/pkg/application/application_ios.h @@ -0,0 +1,123 @@ +//go:build ios + +#ifndef APPLICATION_IOS_H +#define APPLICATION_IOS_H + +#include +#import + +// Forward declarations +@class WailsViewController; +@class WailsAppDelegate; + +// Global references +extern WailsAppDelegate *appDelegate; +extern unsigned int nextWindowID; + +// Initialize the iOS application +void ios_app_init(void); + +// Run the iOS application main loop +void ios_app_run(void); + +// Quit the iOS application +void ios_app_quit(void); + +// Check if dark mode is enabled +bool ios_is_dark_mode(void); + +// Configure/show state for iOS WKWebView input accessory view (keyboard toolbar) +// If disabled, the accessory view will be hidden. +void ios_set_disable_input_accessory(bool disabled); +bool ios_is_input_accessory_disabled(void); + +// Scrolling & bounce & indicators +void ios_set_disable_scroll(bool disabled); +bool ios_is_scroll_disabled(void); +void ios_set_disable_bounce(bool disabled); +bool ios_is_bounce_disabled(void); +void ios_set_disable_scroll_indicators(bool disabled); +bool ios_is_scroll_indicators_disabled(void); + +// Navigation gestures +void ios_set_enable_back_forward_gestures(bool enabled); +bool ios_is_back_forward_gestures_enabled(void); + +// Link previews +void ios_set_disable_link_preview(bool disabled); +bool ios_is_link_preview_disabled(void); + +// Media playback +void ios_set_enable_inline_media_playback(bool enabled); +bool ios_is_inline_media_playback_enabled(void); +void ios_set_enable_autoplay_without_user_action(bool enabled); +bool ios_is_autoplay_without_user_action_enabled(void); + +// Inspector +void ios_set_disable_inspectable(bool disabled); +bool ios_is_inspectable_disabled(void); + +// User agent customization +void ios_set_user_agent(const char* ua); +const char* ios_get_user_agent(void); +void ios_set_app_name_for_user_agent(const char* name); +const char* ios_get_app_name_for_user_agent(void); + +// Live runtime mutations (apply to existing WKWebView instances) +// These functions iterate current view controllers and update the active webviews on the main thread. +void ios_runtime_set_scroll_enabled(bool enabled); +void ios_runtime_set_bounce_enabled(bool enabled); +void ios_runtime_set_scroll_indicators_enabled(bool enabled); +void ios_runtime_set_back_forward_gestures_enabled(bool enabled); +void ios_runtime_set_link_preview_enabled(bool enabled); +void ios_runtime_set_inspectable_enabled(bool enabled); +void ios_runtime_set_custom_user_agent(const char* ua); + +// Native bottom tab bar (UITabBar) controls +void ios_native_tabs_set_enabled(bool enabled); +bool ios_native_tabs_is_enabled(void); +void ios_native_tabs_select_index(int index); +// Configure native tabs items as a JSON array: [{"Title":"...","SystemImage":"..."}] +void ios_native_tabs_set_items_json(const char* json); +const char* ios_native_tabs_get_items_json(void); + +// App-wide background colour control +// Setter accepts RGBA (0-255) and a flag indicating whether the colour is intentionally set by the app. +void ios_set_app_background_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a, bool isSet); +// Getter returns true if a colour was set; outputs RGBA components via pointers when non-null. +bool ios_get_app_background_color(unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a); + +// Create a WebView window and return its ID +unsigned int ios_create_webview(void); + +// Create a WebView window with specified Wails ID and return its native handle +void* ios_create_webview_with_id(unsigned int wailsID); + +// Execute JavaScript in a WebView by ID (legacy) +void ios_execute_javascript(unsigned int windowID, const char* js); + +// Direct JavaScript execution on a specific window handle +void ios_window_exec_js(void* viewController, const char* js); + +// Loaders +void ios_window_load_url(void* viewController, const char* url); +void ios_window_set_html(void* viewController, const char* html); + +// Get the window ID from a native handle +unsigned int ios_window_get_id(void* viewController); + +// Release a native handle when window is destroyed +void ios_window_release_handle(void* viewController); + +// Go callbacks +extern void ServeAssetRequest(unsigned int windowID, void* urlSchemeTask); +extern void HandleJSMessage(unsigned int windowID, char* message); +extern bool hasListeners(unsigned int eventId); + +// iOS Runtime bridges +// Trigger haptic impact with a style: "light"|"medium"|"heavy"|"soft"|"rigid" +void ios_haptics_impact(const char* style); +// Returns a JSON string with basic device info. Caller is responsible for freeing with free(). +const char* ios_device_info_json(void); + +#endif // APPLICATION_IOS_H \ No newline at end of file diff --git a/v3/pkg/application/application_ios.m b/v3/pkg/application/application_ios.m new file mode 100644 index 000000000..4ea237e87 --- /dev/null +++ b/v3/pkg/application/application_ios.m @@ -0,0 +1,324 @@ +//go:build ios + +#import +#import +#import "application_ios.h" +#import "application_ios_delegate.h" +#import "webview_window_ios.h" +#import +#import +#import + +// Forward declarations for Go callbacks +void ServeAssetRequest(unsigned int windowID, void* urlSchemeTask); +void HandleJSMessage(unsigned int windowID, char* message); + + +// Global references - declare after interface +WailsAppDelegate *appDelegate = nil; +unsigned int nextWindowID = 1; +static bool g_disableInputAccessory = false; // default: enabled (shown) +// New global flags with sensible iOS defaults +static bool g_disableScroll = false; // default: scrolling enabled +static bool g_disableBounce = false; // default: bounce enabled +static bool g_disableScrollIndicators = false; // default: indicators shown +static bool g_enableBackForwardGestures = false; // default: gestures disabled +static bool g_disableLinkPreview = false; // default: link preview enabled +static bool g_enableInlineMediaPlayback = false; // default: inline playback disabled +static bool g_enableAutoplayNoUserAction = false; // default: autoplay requires user action +static bool g_disableInspectable = false; // default: inspector enabled +static NSString* g_userAgent = nil; +static NSString* g_appNameForUA = nil; // default applied in code when nil +static bool g_enableNativeTabs = false; // default: off +static NSString* g_nativeTabsItemsJSON = nil; // JSON array of items + +// App-wide background colour storage (RGBA 0-255) +static bool g_appBGSet = false; +static unsigned char g_appBG_R = 255; +static unsigned char g_appBG_G = 255; +static unsigned char g_appBG_B = 255; +static unsigned char g_appBG_A = 255; + +// Note: The WailsAppDelegate implementation resides in application_ios_delegate.m + +// C interface implementation +void ios_app_init(void) { + // This will be called from Go's init + // Explicitly reference WailsAppDelegate to ensure the class is linked and registered. + (void)[WailsAppDelegate class]; + // The actual UI startup happens via UIApplicationMain in main.m +} + +void ios_app_run(void) { + // This function is no longer used - UIApplicationMain is called from main.m + // The WailsAppDelegate is automatically instantiated by UIApplicationMain + extern void LogInfo(const char* source, const char* message); + LogInfo("ios_app_run", "⚠️ This function should not be called - UIApplicationMain runs from main.m"); +} + +void ios_app_quit(void) { + dispatch_async(dispatch_get_main_queue(), ^{ + exit(0); + }); +} + +bool ios_is_dark_mode(void) { + if (@available(iOS 13.0, *)) { + UIUserInterfaceStyle style = [[UITraitCollection currentTraitCollection] userInterfaceStyle]; + return style == UIUserInterfaceStyleDark; + } + return false; +} + +void ios_set_disable_input_accessory(bool disabled) { + g_disableInputAccessory = disabled; +} + +bool ios_is_input_accessory_disabled(void) { + return g_disableInputAccessory; +} + +// Scrolling & bounce & indicators +void ios_set_disable_scroll(bool disabled) { g_disableScroll = disabled; } +bool ios_is_scroll_disabled(void) { return g_disableScroll; } +void ios_set_disable_bounce(bool disabled) { g_disableBounce = disabled; } +bool ios_is_bounce_disabled(void) { return g_disableBounce; } +void ios_set_disable_scroll_indicators(bool disabled) { g_disableScrollIndicators = disabled; } +bool ios_is_scroll_indicators_disabled(void) { return g_disableScrollIndicators; } + +// Navigation gestures +void ios_set_enable_back_forward_gestures(bool enabled) { g_enableBackForwardGestures = enabled; } +bool ios_is_back_forward_gestures_enabled(void) { return g_enableBackForwardGestures; } + +// Link previews +void ios_set_disable_link_preview(bool disabled) { g_disableLinkPreview = disabled; } +bool ios_is_link_preview_disabled(void) { return g_disableLinkPreview; } + +// Media playback +void ios_set_enable_inline_media_playback(bool enabled) { g_enableInlineMediaPlayback = enabled; } +bool ios_is_inline_media_playback_enabled(void) { return g_enableInlineMediaPlayback; } +void ios_set_enable_autoplay_without_user_action(bool enabled) { g_enableAutoplayNoUserAction = enabled; } +bool ios_is_autoplay_without_user_action_enabled(void) { return g_enableAutoplayNoUserAction; } + +// Inspector +void ios_set_disable_inspectable(bool disabled) { g_disableInspectable = disabled; } +bool ios_is_inspectable_disabled(void) { return g_disableInspectable; } + +// User agent customization +void ios_set_user_agent(const char* ua) { + if (ua == NULL) { g_userAgent = nil; return; } + g_userAgent = [NSString stringWithUTF8String:ua]; +} +const char* ios_get_user_agent(void) { + if (g_userAgent == nil) return NULL; + return [g_userAgent UTF8String]; +} +void ios_set_app_name_for_user_agent(const char* name) { + if (name == NULL) { g_appNameForUA = nil; return; } + g_appNameForUA = [NSString stringWithUTF8String:name]; +} +const char* ios_get_app_name_for_user_agent(void) { + if (g_appNameForUA == nil) return NULL; + return [g_appNameForUA UTF8String]; +} + +// Live runtime mutations (apply to existing WKWebView instances) +static void forEachViewController(void (^block)(WailsViewController *vc)) { + if (!appDelegate || !appDelegate.viewControllers) return; + void (^applyBlock)(void) = ^{ + for (WailsViewController *vc in appDelegate.viewControllers) { + if (!vc || !vc.webView) continue; + block(vc); + } + }; + if ([NSThread isMainThread]) { + applyBlock(); + } else { + dispatch_async(dispatch_get_main_queue(), applyBlock); + } +} + +void ios_runtime_set_scroll_enabled(bool enabled) { + g_disableScroll = !enabled; + forEachViewController(^(WailsViewController *vc){ + vc.webView.scrollView.scrollEnabled = enabled ? YES : NO; + }); +} + +void ios_runtime_set_bounce_enabled(bool enabled) { + g_disableBounce = !enabled; + forEachViewController(^(WailsViewController *vc){ + UIScrollView *sv = vc.webView.scrollView; + sv.bounces = enabled ? YES : NO; + sv.alwaysBounceVertical = enabled ? YES : NO; + sv.alwaysBounceHorizontal = enabled ? YES : NO; + }); +} + +void ios_runtime_set_scroll_indicators_enabled(bool enabled) { + g_disableScrollIndicators = !enabled; + forEachViewController(^(WailsViewController *vc){ + UIScrollView *sv = vc.webView.scrollView; + sv.showsVerticalScrollIndicator = enabled ? YES : NO; + sv.showsHorizontalScrollIndicator = enabled ? YES : NO; + }); +} + +void ios_runtime_set_back_forward_gestures_enabled(bool enabled) { + g_enableBackForwardGestures = enabled; + forEachViewController(^(WailsViewController *vc){ + vc.webView.allowsBackForwardNavigationGestures = enabled ? YES : NO; + }); +} + +void ios_runtime_set_link_preview_enabled(bool enabled) { + g_disableLinkPreview = !enabled; + forEachViewController(^(WailsViewController *vc){ + vc.webView.allowsLinkPreview = enabled ? YES : NO; + }); +} + +void ios_runtime_set_inspectable_enabled(bool enabled) { + g_disableInspectable = !enabled; + forEachViewController(^(WailsViewController *vc){ + BOOL inspectorOn = enabled ? YES : NO; + if (@available(iOS 16.4, *)) { + vc.webView.inspectable = inspectorOn; + } else { + @try { [vc.webView setValue:@(inspectorOn) forKey:@"inspectable"]; } @catch (__unused NSException *e) {} + } + }); +} + +void ios_runtime_set_custom_user_agent(const char* ua) { + ios_set_user_agent(ua); + NSString *uaStr = (ua ? [NSString stringWithUTF8String:ua] : nil); + forEachViewController(^(WailsViewController *vc){ + vc.webView.customUserAgent = uaStr; + }); +} + +// Forward declaration used by getters +static const char* dupCString(NSString *str); + +// Native bottom tab bar controls +void ios_native_tabs_set_enabled(bool enabled) { + g_enableNativeTabs = enabled; + NSLog(@"[ios_native_tabs_set_enabled] enabled=%d", enabled); + __block NSUInteger count = 0; + forEachViewController(^(WailsViewController *vc){ + count++; + [vc enableNativeTabs:(enabled ? YES : NO)]; + }); + NSLog(@"[ios_native_tabs_set_enabled] applied to %lu view controller(s)", (unsigned long)count); +} + +bool ios_native_tabs_is_enabled(void) { + return g_enableNativeTabs; +} + +void ios_native_tabs_select_index(int index) { + forEachViewController(^(WailsViewController *vc){ + [vc selectNativeTabIndex:(NSInteger)index]; + }); +} + +void ios_native_tabs_set_items_json(const char* json) { + if (json == NULL) { + g_nativeTabsItemsJSON = nil; + NSLog(@"[ios_native_tabs_set_items_json] JSON cleared (nil)"); + return; + } + g_nativeTabsItemsJSON = [NSString stringWithUTF8String:json]; + NSLog(@"[ios_native_tabs_set_items_json] JSON length=%lu", (unsigned long)g_nativeTabsItemsJSON.length); + // Apply to existing controllers if visible + __block NSUInteger refreshed = 0; + forEachViewController(^(WailsViewController *vc){ + if (vc.tabBar && !vc.tabBar.isHidden) { + // Re-enable to rebuild items from JSON + [vc enableNativeTabs:YES]; + refreshed++; + } + }); + NSLog(@"[ios_native_tabs_set_items_json] refreshed %lu visible tab bar(s)", (unsigned long)refreshed); +} + +const char* ios_native_tabs_get_items_json(void) { + return dupCString(g_nativeTabsItemsJSON); +} + +// App-wide background colour control +void ios_set_app_background_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a, bool isSet) { + g_appBGSet = isSet; + if (isSet) { + g_appBG_R = r; g_appBG_G = g; g_appBG_B = b; g_appBG_A = a; + } +} + +bool ios_get_app_background_color(unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) { + if (!g_appBGSet) return false; + if (r) *r = g_appBG_R; + if (g) *g = g_appBG_G; + if (b) *b = g_appBG_B; + if (a) *a = g_appBG_A; + return true; +} + + +// iOS Runtime bridges +void ios_haptics_impact(const char* cstyle) { + if (cstyle == NULL) return; + NSString *style = [NSString stringWithUTF8String:cstyle]; + dispatch_async(dispatch_get_main_queue(), ^{ + NSLog(@"[ios_haptics_impact] requested style=%@", style); + if (@available(iOS 13.0, *)) { + UIImpactFeedbackStyle feedbackStyle = UIImpactFeedbackStyleMedium; + if ([style isEqualToString:@"light"]) feedbackStyle = UIImpactFeedbackStyleLight; + else if ([style isEqualToString:@"medium"]) feedbackStyle = UIImpactFeedbackStyleMedium; + else if ([style isEqualToString:@"heavy"]) feedbackStyle = UIImpactFeedbackStyleHeavy; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + else if ([style isEqualToString:@"soft"]) feedbackStyle = UIImpactFeedbackStyleSoft; + else if ([style isEqualToString:@"rigid"]) feedbackStyle = UIImpactFeedbackStyleRigid; +#endif + UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:feedbackStyle]; + [generator prepare]; + [generator impactOccurred]; + #if TARGET_OS_SIMULATOR + NSLog(@"[ios_haptics_impact] Simulator detected: no physical haptic feedback will be felt."); + #else + NSLog(@"[ios_haptics_impact] Haptic impact triggered."); + #endif + } else { + NSLog(@"[ios_haptics_impact] iOS version < 13.0: no haptic API available."); + } + }); +} + +static const char* dupCString(NSString *str) { + if (str == nil) return NULL; + const char* utf8 = [str UTF8String]; + if (utf8 == NULL) return NULL; + size_t len = strlen(utf8) + 1; + char* out = (char*)malloc(len); + if (out) memcpy(out, utf8, len); + return out; +} + +const char* ios_device_info_json(void) { + UIDevice *device = [UIDevice currentDevice]; + struct utsname systemInfo; + uname(&systemInfo); + NSString *model = [NSString stringWithUTF8String:systemInfo.machine]; + NSString *systemName = device.systemName ?: @"iOS"; + NSString *systemVersion = device.systemVersion ?: @""; +#if TARGET_OS_SIMULATOR + BOOL isSimulator = YES; +#else + BOOL isSimulator = NO; +#endif + NSString *json = [NSString stringWithFormat: + @"{\"model\":\"%@\",\"systemName\":\"%@\",\"systemVersion\":\"%@\",\"isSimulator\":%@}", + model, systemName, systemVersion, isSimulator ? @"true" : @"false" + ]; + return dupCString(json); +} diff --git a/v3/pkg/application/application_ios_delegate.h b/v3/pkg/application/application_ios_delegate.h new file mode 100644 index 000000000..0b0849748 --- /dev/null +++ b/v3/pkg/application/application_ios_delegate.h @@ -0,0 +1,15 @@ +//go:build ios + +#ifndef application_ios_delegate_h +#define application_ios_delegate_h + +#import + +@class WailsViewController; + +@interface WailsAppDelegate : UIResponder +@property (strong, nonatomic) UIWindow *window; +@property (nonatomic, strong) NSMutableArray *viewControllers; +@end + +#endif /* application_ios_delegate_h */ \ No newline at end of file diff --git a/v3/pkg/application/application_ios_delegate.m b/v3/pkg/application/application_ios_delegate.m new file mode 100644 index 000000000..ef2a59066 --- /dev/null +++ b/v3/pkg/application/application_ios_delegate.m @@ -0,0 +1,83 @@ +//go:build ios +#import "application_ios_delegate.h" +#import "../events/events_ios.h" +#import "application_ios.h" +extern void processApplicationEvent(unsigned int, void* data); +extern void processWindowEvent(unsigned int, unsigned int); +extern bool hasListeners(unsigned int); +@implementation WailsAppDelegate +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Set global appDelegate reference and bring up a window if needed + appDelegate = self; + if (self.window == nil) { + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + UIViewController *rootVC = [[UIViewController alloc] init]; + rootVC.view.backgroundColor = [UIColor whiteColor]; + self.window.rootViewController = rootVC; + [self.window makeKeyAndVisible]; + } + // Apply app-wide background colour if configured + unsigned char r = 255, g = 255, b = 255, a = 255; + if (ios_get_app_background_color(&r, &g, &b, &a)) { + CGFloat fr = ((CGFloat)r) / 255.0; + CGFloat fg = ((CGFloat)g) / 255.0; + CGFloat fb = ((CGFloat)b) / 255.0; + CGFloat fa = ((CGFloat)a) / 255.0; + UIColor *color = [UIColor colorWithRed:fr green:fg blue:fb alpha:fa]; + self.window.backgroundColor = color; + self.window.rootViewController.view.backgroundColor = color; + } + if (!self.viewControllers) { + self.viewControllers = [NSMutableArray array]; + } + if (hasListeners(EventApplicationDidFinishLaunching)) { + processApplicationEvent(EventApplicationDidFinishLaunching, NULL); + } + return YES; +} +// GENERATED EVENTS START +- (void)applicationDidBecomeActive:(UIApplication *)application { + if( hasListeners(EventApplicationDidBecomeActive) ) { + processApplicationEvent(EventApplicationDidBecomeActive, NULL); + } +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + if( hasListeners(EventApplicationDidEnterBackground) ) { + processApplicationEvent(EventApplicationDidEnterBackground, NULL); + } +} + +- (void)applicationDidFinishLaunching:(UIApplication *)application { + if( hasListeners(EventApplicationDidFinishLaunching) ) { + processApplicationEvent(EventApplicationDidFinishLaunching, NULL); + } +} + +- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { + if( hasListeners(EventApplicationDidReceiveMemoryWarning) ) { + processApplicationEvent(EventApplicationDidReceiveMemoryWarning, NULL); + } +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + if( hasListeners(EventApplicationWillEnterForeground) ) { + processApplicationEvent(EventApplicationWillEnterForeground, NULL); + } +} + +- (void)applicationWillResignActive:(UIApplication *)application { + if( hasListeners(EventApplicationWillResignActive) ) { + processApplicationEvent(EventApplicationWillResignActive, NULL); + } +} + +- (void)applicationWillTerminate:(UIApplication *)application { + if( hasListeners(EventApplicationWillTerminate) ) { + processApplicationEvent(EventApplicationWillTerminate, NULL); + } +} + +// GENERATED EVENTS END +@end diff --git a/v3/pkg/application/application_linux.go b/v3/pkg/application/application_linux.go new file mode 100644 index 000000000..ab9c51db6 --- /dev/null +++ b/v3/pkg/application/application_linux.go @@ -0,0 +1,347 @@ +//go:build linux && !android && !server + +package application + +/* + #include "gtk/gtk.h" + #include "webkit2/webkit2.h" + static guint get_compiled_gtk_major_version() { return GTK_MAJOR_VERSION; } + static guint get_compiled_gtk_minor_version() { return GTK_MINOR_VERSION; } + static guint get_compiled_gtk_micro_version() { return GTK_MICRO_VERSION; } + static guint get_compiled_webkit_major_version() { return WEBKIT_MAJOR_VERSION; } + static guint get_compiled_webkit_minor_version() { return WEBKIT_MINOR_VERSION; } + static guint get_compiled_webkit_micro_version() { return WEBKIT_MICRO_VERSION; } +*/ +import "C" +import ( + "fmt" + "os" + "regexp" + "slices" + "strings" + "sync" + + "path/filepath" + + "github.com/godbus/dbus/v5" + "github.com/wailsapp/wails/v3/internal/operatingsystem" + "github.com/wailsapp/wails/v3/pkg/events" +) + +// sanitizeAppName sanitizes the application name to be a valid GTK/D-Bus application ID. +// Valid IDs contain only alphanumeric characters, hyphens, and underscores. +// They must not start with a digit. +var invalidAppNameChars = regexp.MustCompile(`[^a-zA-Z0-9_-]`) +var leadingDigits = regexp.MustCompile(`^[0-9]+`) + +func sanitizeAppName(name string) string { + // Replace invalid characters with underscores + name = invalidAppNameChars.ReplaceAllString(name, "_") + // Prefix with underscore if starts with digit + name = leadingDigits.ReplaceAllString(name, "_$0") + // Remove consecutive underscores + for strings.Contains(name, "__") { + name = strings.ReplaceAll(name, "__", "_") + } + // Trim leading/trailing underscores + name = strings.Trim(name, "_") + if name == "" { + name = "wailsapp" + } + return strings.ToLower(name) +} + +func init() { + // FIXME: This should be handled appropriately in the individual files most likely. + // Set GDK_BACKEND=x11 if currently unset and XDG_SESSION_TYPE is unset, unspecified or x11 to prevent warnings + if os.Getenv("GDK_BACKEND") == "" && + (os.Getenv("XDG_SESSION_TYPE") == "" || os.Getenv("XDG_SESSION_TYPE") == "unspecified" || os.Getenv("XDG_SESSION_TYPE") == "x11") { + _ = os.Setenv("GDK_BACKEND", "x11") + } + + // Disable DMA-BUF renderer on Wayland with NVIDIA to prevent "Error 71 (Protocol error)" crashes. + // This is a known WebKitGTK issue with NVIDIA proprietary drivers on Wayland. + // See: https://bugs.webkit.org/show_bug.cgi?id=262607 + if os.Getenv("WEBKIT_DISABLE_DMABUF_RENDERER") == "" && + os.Getenv("XDG_SESSION_TYPE") == "wayland" && + isNVIDIAGPU() { + _ = os.Setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "1") + } +} + +// isNVIDIAGPU checks if an NVIDIA GPU is present by looking for the nvidia kernel module. +func isNVIDIAGPU() bool { + // Check if nvidia module is loaded (most reliable for proprietary driver) + if _, err := os.Stat("/sys/module/nvidia"); err == nil { + return true + } + return false +} + +type linuxApp struct { + application pointer + parent *App + + startupActions []func() + + // Native -> uint + windowMap map[windowPointer]uint + windowMapLock sync.Mutex + + theme string + + icon pointer +} + +func (a *linuxApp) GetFlags(options Options) map[string]any { + if options.Flags == nil { + options.Flags = make(map[string]any) + } + return options.Flags +} + +func getNativeApplication() *linuxApp { + return globalApplication.impl.(*linuxApp) +} + +func (a *linuxApp) hide() { + a.hideAllWindows() +} + +func (a *linuxApp) show() { + a.showAllWindows() +} + +func (a *linuxApp) on(eventID uint) { + // TODO: Test register/unregister events + //C.registerApplicationEvent(l.application, C.uint(eventID)) +} + +func (a *linuxApp) name() string { + return appName() +} + +type rnr struct { + f func() +} + +func (r rnr) run() { + r.f() +} + +func (a *linuxApp) setApplicationMenu(menu *Menu) { + // FIXME: How do we avoid putting a menu? + if menu == nil { + // Create a default menu + menu = DefaultApplicationMenu() + globalApplication.applicationMenu = menu + } +} + +func (a *linuxApp) run() error { + + if len(os.Args) == 2 { // Case: program + 1 argument + arg1 := os.Args[1] + // Check if the argument is likely a URL from a custom protocol invocation + if strings.Contains(arg1, "://") { + a.parent.debug("Application launched with argument, potentially a URL from custom protocol", "url", arg1) + eventContext := newApplicationEventContext() + eventContext.setURL(arg1) + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Common.ApplicationLaunchedWithUrl), + ctx: eventContext, + } + } else { + // Check if the argument matches any file associations + if a.parent.options.FileAssociations != nil { + ext := filepath.Ext(arg1) + if slices.Contains(a.parent.options.FileAssociations, ext) { + a.parent.debug("File opened via file association", "file", arg1, "extension", ext) + eventContext := newApplicationEventContext() + eventContext.setOpenedWithFile(arg1) + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Common.ApplicationOpenedWithFile), + ctx: eventContext, + } + return nil + } + } + a.parent.debug("Application launched with single argument (not a URL), potential file open?", "arg", arg1) + } + } else if len(os.Args) > 2 { + // Log if multiple arguments are passed + a.parent.debug("Application launched with multiple arguments", "args", os.Args[1:]) + } + + a.parent.Event.OnApplicationEvent(events.Linux.ApplicationStartup, func(evt *ApplicationEvent) { + // TODO: What should happen here? + }) + a.setupCommonEvents() + a.monitorThemeChanges() + return appRun(a.application) +} + +func (a *linuxApp) unregisterWindow(w windowPointer) { + a.windowMapLock.Lock() + delete(a.windowMap, w) + a.windowMapLock.Unlock() + + // If this was the last window... + if len(a.windowMap) == 0 && !a.parent.options.Linux.DisableQuitOnLastWindowClosed { + a.destroy() + } +} + +func (a *linuxApp) destroy() { + if !globalApplication.shouldQuit() { + return + } + globalApplication.cleanup() + appDestroy(a.application) +} + +func (a *linuxApp) isOnMainThread() bool { + return isOnMainThread() +} + +// register our window to our parent mapping +func (a *linuxApp) registerWindow(window pointer, id uint) { + a.windowMapLock.Lock() + a.windowMap[windowPointer(window)] = id + a.windowMapLock.Unlock() +} + +func (a *linuxApp) isDarkMode() bool { + return strings.Contains(a.theme, "dark") +} + +func (a *linuxApp) getAccentColor() string { + // Linux doesn't have a unified system accent color API + // Return a default blue color + return "rgb(0,122,255)" +} + +func (a *linuxApp) monitorThemeChanges() { + go func() { + defer handlePanic() + conn, err := dbus.ConnectSessionBus() + if err != nil { + a.parent.warning( + "[WARNING] Failed to connect to session bus; monitoring for theme changes will not function: %v", + err, + ) + return + } + defer conn.Close() + + if err = conn.AddMatchSignal( + dbus.WithMatchObjectPath("/org/freedesktop/portal/desktop"), + ); err != nil { + panic(err) + } + + c := make(chan *dbus.Signal, 10) + conn.Signal(c) + + getTheme := func(body []interface{}) (string, bool) { + if len(body) < 2 { + return "", false + } + if entry, ok := body[0].(string); !ok || entry != "org.gnome.desktop.interface" { + return "", false + } + if entry, ok := body[1].(string); ok && entry == "color-scheme" { + return body[2].(dbus.Variant).Value().(string), true + } + return "", false + } + + for v := range c { + theme, ok := getTheme(v.Body) + if !ok { + continue + } + + if theme != a.theme { + a.theme = theme + event := newApplicationEvent(events.Linux.SystemThemeChanged) + event.Context().setIsDarkMode(a.isDarkMode()) + applicationEvents <- event + } + + } + }() +} + +func newPlatformApp(parent *App) *linuxApp { + name := sanitizeAppName(parent.options.Name) + app := &linuxApp{ + parent: parent, + application: appNew(name), + windowMap: map[windowPointer]uint{}, + } + + if parent.options.Linux.ProgramName != "" { + setProgramName(parent.options.Linux.ProgramName) + } + + return app +} + +// logPlatformInfo logs the platform information to the console +func (a *App) logPlatformInfo() { + info, err := operatingsystem.Info() + if err != nil { + a.error("error getting OS info: %w", err) + return + } + + wkVersion := operatingsystem.GetWebkitVersion() + platformInfo := info.AsLogSlice() + platformInfo = append(platformInfo, "Webkit2Gtk", wkVersion) + + a.info("Platform Info:", platformInfo...) +} + +//export processWindowEvent +func processWindowEvent(windowID C.uint, eventID C.uint) { + windowEvents <- &windowEvent{ + WindowID: uint(windowID), + EventID: uint(eventID), + } +} + +func buildVersionString(major, minor, micro C.uint) string { + return fmt.Sprintf("%d.%d.%d", uint(major), uint(minor), uint(micro)) +} + +func (a *App) platformEnvironment() map[string]any { + result := map[string]any{} + result["gtk3-compiled"] = buildVersionString( + C.get_compiled_gtk_major_version(), + C.get_compiled_gtk_minor_version(), + C.get_compiled_gtk_micro_version(), + ) + result["gtk3-runtime"] = buildVersionString( + C.gtk_get_major_version(), + C.gtk_get_minor_version(), + C.gtk_get_micro_version(), + ) + + result["webkit2gtk-compiled"] = buildVersionString( + C.get_compiled_webkit_major_version(), + C.get_compiled_webkit_minor_version(), + C.get_compiled_webkit_micro_version(), + ) + result["webkit2gtk-runtime"] = buildVersionString( + C.webkit_get_major_version(), + C.webkit_get_minor_version(), + C.webkit_get_micro_version(), + ) + return result +} + +func fatalHandler(errFunc func(error)) { + // Stub for windows function + return +} diff --git a/v3/pkg/application/application_options.go b/v3/pkg/application/application_options.go new file mode 100644 index 000000000..946b8733b --- /dev/null +++ b/v3/pkg/application/application_options.go @@ -0,0 +1,414 @@ +package application + +import ( + "io/fs" + "log/slog" + "net/http" + "time" + + "github.com/wailsapp/wails/v3/internal/assetserver" +) + +// Options contains the options for the application +type Options struct { + // Name is the name of the application (used in the default about box) + Name string + + // Description is the description of the application (used in the default about box) + Description string + + // Icon is the icon of the application (used in the default about box) + Icon []byte + + // Mac is the Mac specific configuration for Mac builds + Mac MacOptions + + // Windows is the Windows specific configuration for Windows builds + Windows WindowsOptions + + // Linux is the Linux specific configuration for Linux builds + Linux LinuxOptions + + // IOS is the iOS specific configuration for iOS builds + IOS IOSOptions + + // Android is the Android specific configuration for Android builds + Android AndroidOptions + + // Services allows you to bind Go methods to the frontend. + Services []Service + + // MarshalError will be called if non-nil + // to marshal to JSON the error values returned by service methods. + // + // MarshalError is not allowed to fail, + // but it may return a nil slice to fall back + // to the default error handling mechanism. + // + // If the returned slice is not nil, it must contain valid JSON. + MarshalError func(error) []byte + + // BindAliases allows you to specify alias IDs for your bound methods. + // Example: `BindAliases: map[uint32]uint32{1: 1411160069}` states that alias ID 1 maps to the Go method with ID 1411160069. + BindAliases map[uint32]uint32 + + // Logger is a slog.Logger instance used for logging Wails system messages (not application messages). + // If not defined, a default logger is used. + Logger *slog.Logger + + // LogLevel defines the log level of the Wails system logger. + LogLevel slog.Level + + // Assets are the application assets to be used. + Assets AssetOptions + + // Flags are key value pairs that are available to the frontend. + // This is also used by Wails to provide information to the frontend. + Flags map[string]any + + // PanicHandler is called when a panic occurs + PanicHandler func(*PanicDetails) + + // DisableDefaultSignalHandler disables the default signal handler + DisableDefaultSignalHandler bool + + // KeyBindings is a map of key bindings to functions + KeyBindings map[string]func(window Window) + + // OnShutdown is called when the application is about to terminate. + // This is useful for cleanup tasks. + // The shutdown process blocks until this function returns. + OnShutdown func() + + // PostShutdown is called after the application + // has finished shutting down, just before process termination. + // This is useful for testing and logging purposes + // on platforms where the Run() method does not return. + // When PostShutdown is called, the application instance is not usable anymore. + // The shutdown process blocks until this function returns. + PostShutdown func() + + // ShouldQuit is a function that is called when the user tries to quit the application. + // If the function returns true, the application will quit. + // If the function returns false, the application will not quit. + ShouldQuit func() bool + + // RawMessageHandler is called when the frontend sends a raw message. + // This is useful for implementing custom frontend-to-backend communication. + RawMessageHandler func(window Window, message string, originInfo *OriginInfo) + + // WarningHandler is called when a warning occurs + WarningHandler func(string) + + // ErrorHandler is called when an error occurs + ErrorHandler func(err error) + + // File extensions associated with the application + // Example: [".txt", ".md"] + // The '.' is required + FileAssociations []string + + // SingleInstance options for single instance functionality + SingleInstance *SingleInstanceOptions + + // Transport allows you to provide a custom IPC transport layer. + // When set, Wails will use your transport instead of the default HTTP fetch-based transport. + // This allows you to use WebSockets, custom protocols, or any other transport mechanism + // while retaining all Wails generated bindings and event communication. + // + // The default transport uses HTTP fetch requests to /wails/runtime + events via js.Exec in webview. + // If not specified, the default transport is used. + // + // Example use case: Implementing WebSocket-based or PostMessage IPC. + Transport Transport + + // Server configures the HTTP server for server mode. + // Server mode is enabled by building with the "server" build tag: + // go build -tags server + // + // In server mode, the application runs as an HTTP server without a native window. + // This enables deploying the same Wails application as a web server for: + // - Docker/container deployments + // - Server-side rendering + // - Web-only access without desktop dependencies + Server ServerOptions +} + +// ServerOptions configures the HTTP server for headless mode. +type ServerOptions struct { + // Host is the address to bind to. + // Default: "localhost" for security. Use "0.0.0.0" for all interfaces. + Host string + + // Port is the port to listen on. + // Default: 8080 + Port int + + // ReadTimeout is the maximum duration for reading the entire request. + // Default: 30 seconds + ReadTimeout time.Duration + + // WriteTimeout is the maximum duration before timing out writes of the response. + // Default: 30 seconds + WriteTimeout time.Duration + + // IdleTimeout is the maximum duration to wait for the next request. + // Default: 120 seconds + IdleTimeout time.Duration + + // ShutdownTimeout is the maximum duration to wait for active connections to close. + // Default: 30 seconds + ShutdownTimeout time.Duration + + // TLS configures HTTPS. If nil, HTTP is used. + TLS *TLSOptions +} + +// TLSOptions configures HTTPS for the headless server. +type TLSOptions struct { + // CertFile is the path to the TLS certificate file. + CertFile string + + // KeyFile is the path to the TLS private key file. + KeyFile string +} + +// AssetOptions defines the configuration of the AssetServer. +type AssetOptions struct { + // Handler which serves all the content to the WebView. + Handler http.Handler + + // Middleware is a HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default + // request handler dynamically, e.g. implement specialized Routing etc. + // The Middleware is called to build a new `http.Handler` used by the AssetSever and it also receives the default + // handler used by the AssetServer as an argument. + // + // This middleware injects itself before any of Wails internal middlewares. + // + // If not defined, the default AssetServer request chain is executed. + // + // Multiple Middlewares can be chained together with: + // ChainMiddleware(middleware ...Middleware) Middleware + Middleware Middleware + + // DisableLogging disables logging of the AssetServer. By default, the AssetServer logs every request. + DisableLogging bool +} + +// Middleware defines HTTP middleware that can be applied to the AssetServer. +// The handler passed as next is the next handler in the chain. One can decide to call the next handler +// or implement a specialized handling. +type Middleware func(next http.Handler) http.Handler + +// ChainMiddleware allows chaining multiple middlewares to one middleware. +func ChainMiddleware(middleware ...Middleware) Middleware { + return func(h http.Handler) http.Handler { + for i := len(middleware) - 1; i >= 0; i-- { + h = middleware[i](h) + } + return h + } +} + +// AssetFileServerFS returns a http handler which serves the assets from the fs.FS. +// If an external devserver has been provided 'FRONTEND_DEVSERVER_URL' the files are being served +// from the external server, ignoring the `assets`. +func AssetFileServerFS(assets fs.FS) http.Handler { + return assetserver.NewAssetFileServer(assets) +} + +// BundledAssetFileServer returns a http handler which serves the assets from the fs.FS. +// If an external devserver has been provided 'FRONTEND_DEVSERVER_URL' the files are being served +// from the external server, ignoring the `assets`. +// It also serves the compiled runtime.js file at `/wails/runtime.js`. +// It will provide the production runtime.js file from the embedded assets if the `production` tag is used. +func BundledAssetFileServer(assets fs.FS) http.Handler { + return assetserver.NewBundledAssetFileServer(assets) +} + +/******** Mac Options ********/ + +// ActivationPolicy is the activation policy for the application. +type ActivationPolicy int + +const ( + // ActivationPolicyRegular is used for applications that have a user interface, + ActivationPolicyRegular ActivationPolicy = iota + // ActivationPolicyAccessory is used for applications that do not have a main window, + // such as system tray applications or background applications. + ActivationPolicyAccessory + ActivationPolicyProhibited +) + +// MacOptions contains options for macOS applications. +type MacOptions struct { + // ActivationPolicy is the activation policy for the application. Defaults to + // applicationActivationPolicyRegular. + ActivationPolicy ActivationPolicy + // If set to true, the application will terminate when the last window is closed. + ApplicationShouldTerminateAfterLastWindowClosed bool +} + +/****** Windows Options *******/ + +// WindowsOptions contains options for Windows applications. +type WindowsOptions struct { + + // Window class name + // Default: WailsWebviewWindow + WndClass string + + // WndProcInterceptor is a function that will be called for every message sent in the application. + // Use this to hook into the main message loop. This is useful for handling custom window messages. + // If `shouldReturn` is `true` then `returnCode` will be returned by the main message loop. + // If `shouldReturn` is `false` then returnCode will be ignored and the message will be processed by the main message loop. + WndProcInterceptor func(hwnd uintptr, msg uint32, wParam, lParam uintptr) (returnCode uintptr, shouldReturn bool) + + // DisableQuitOnLastWindowClosed disables the auto quit of the application if the last window has been closed. + DisableQuitOnLastWindowClosed bool + + // Path where the WebView2 stores the user data. If empty %APPDATA%\[BinaryName.exe] will be used. + // If the path is not valid, a messagebox will be displayed with the error and the app will exit with error code. + WebviewUserDataPath string + + // Path to the directory with WebView2 executables. If empty WebView2 installed in the system will be used. + WebviewBrowserPath string + + // EnabledFeatures, DisabledFeatures and AdditionalBrowserArgs configure the WebView2 browser. + // These apply globally to ALL windows because WebView2 shares a single browser environment. + // See: https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/webview-features-flags + // AdditionalBrowserArgs must include the "--" prefix, e.g. "--remote-debugging-port=9222" + EnabledFeatures []string + DisabledFeatures []string + AdditionalBrowserArgs []string +} + +/********* Linux Options *********/ + +// LinuxOptions contains options for Linux applications. +type LinuxOptions struct { + // DisableQuitOnLastWindowClosed disables the auto quit of the application if the last window has been closed. + DisableQuitOnLastWindowClosed bool + + // ProgramName is used to set the program's name for the window manager via GTK's g_set_prgname(). + //This name should not be localized. [see the docs] + // + //When a .desktop file is created this value helps with window grouping and desktop icons when the .desktop file's Name + //property differs form the executable's filename. + // + //[see the docs]: https://docs.gtk.org/glib/func.set_prgname.html + ProgramName string +} + +/********* iOS Options *********/ + +// IOSOptions contains options for iOS applications. +type IOSOptions struct { + // DisableInputAccessoryView controls whether the iOS WKWebView shows the + // input accessory toolbar (the bar with Next/Previous/Done) above the keyboard. + // Default: false (accessory bar is shown). + // true => accessory view is disabled/hidden + // false => accessory view is enabled/shown + DisableInputAccessoryView bool + + // Scrolling & Bounce (defaults: scroll/bounce/indicators are enabled on iOS) + // Use Disable* to keep default true behavior without surprising zero-values. + DisableScroll bool + DisableBounce bool + DisableScrollIndicators bool + + // Navigation gestures (default false) + EnableBackForwardNavigationGestures bool + + // Link previews (default true on iOS) + // Use Disable* so default (false) means previews are enabled. + DisableLinkPreview bool + + // Media playback + // Inline playback (default false) -> Enable* + EnableInlineMediaPlayback bool + // Autoplay without user action (default false) -> Enable* + EnableAutoplayWithoutUserAction bool + + // Inspector / Debug (default true in dev) + // Use Disable* so default (false) keeps inspector enabled. + DisableInspectable bool + + // User agent customization + // If empty, defaults apply. ApplicationNameForUserAgent defaults to "wails.io". + UserAgent string + ApplicationNameForUserAgent string + + // App-wide background colour for the main iOS window prior to any WebView creation. + // If AppBackgroundColourSet is true, the delegate will apply this colour to the app window + // during didFinishLaunching. Otherwise, it defaults to white. + AppBackgroundColourSet bool + BackgroundColour RGBA + + // EnableNativeTabs enables a native iOS UITabBar at the bottom of the screen. + // When enabled, the native tab bar will dispatch a 'nativeTabSelected' CustomEvent + // to the window with detail: { index: number }. + // NOTE: If NativeTabsItems has one or more entries, native tabs are auto-enabled + // regardless of this flag, and the provided items will be used. + EnableNativeTabs bool + + // NativeTabsItems configures the labels and optional SF Symbol icons for the + // native UITabBar. If one or more items are provided, native tabs are automatically + // enabled. If empty and EnableNativeTabs is true, default items are used. + NativeTabsItems []NativeTabItem +} + +// NativeTabItem describes a single item in the iOS native UITabBar. +// SystemImage is the SF Symbols name to use for the icon (iOS 13+). If empty or +// unavailable on the current OS, no icon is shown. +type NativeTabItem struct { + Title string `json:"Title"` + SystemImage NativeTabIcon `json:"SystemImage"` +} + +// NativeTabIcon is a string-based enum for SF Symbols. +// It allows using predefined constants for common symbols while still accepting +// any valid SF Symbols name as a plain string. +// +// Example: +// NativeTabsItems: []NativeTabItem{ +// { Title: "Home", SystemImage: NativeTabIconHouse }, +// { Title: "Settings", SystemImage: "gearshape" }, // arbitrary string still allowed +// } +type NativeTabIcon string + +const ( + // Common icons + NativeTabIconNone NativeTabIcon = "" + NativeTabIconHouse NativeTabIcon = "house" + NativeTabIconGear NativeTabIcon = "gear" + NativeTabIconStar NativeTabIcon = "star" + NativeTabIconPerson NativeTabIcon = "person" + NativeTabIconBell NativeTabIcon = "bell" + NativeTabIconMagnify NativeTabIcon = "magnifyingglass" + NativeTabIconList NativeTabIcon = "list.bullet" + NativeTabIconFolder NativeTabIcon = "folder" +) + +/********* Android Options *********/ + +// AndroidOptions contains options for Android applications. +type AndroidOptions struct { + // DisableScroll disables scrolling in the WebView + DisableScroll bool + + // DisableBounce disables the overscroll bounce effect + DisableOverscroll bool + + // EnableZoom allows pinch-to-zoom in the WebView (default: false) + EnableZoom bool + + // UserAgent sets a custom user agent string + UserAgent string + + // BackgroundColour sets the background colour of the WebView + BackgroundColour RGBA + + // DisableHardwareAcceleration disables hardware acceleration for the WebView + DisableHardwareAcceleration bool +} diff --git a/v3/pkg/application/application_options_test.go b/v3/pkg/application/application_options_test.go new file mode 100644 index 000000000..77ddbe522 --- /dev/null +++ b/v3/pkg/application/application_options_test.go @@ -0,0 +1,301 @@ +package application + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestActivationPolicy_Constants(t *testing.T) { + if ActivationPolicyRegular != 0 { + t.Error("ActivationPolicyRegular should be 0") + } + if ActivationPolicyAccessory != 1 { + t.Error("ActivationPolicyAccessory should be 1") + } + if ActivationPolicyProhibited != 2 { + t.Error("ActivationPolicyProhibited should be 2") + } +} + +func TestNativeTabIcon_Constants(t *testing.T) { + tests := []struct { + name string + icon NativeTabIcon + expected string + }{ + {"NativeTabIconNone", NativeTabIconNone, ""}, + {"NativeTabIconHouse", NativeTabIconHouse, "house"}, + {"NativeTabIconGear", NativeTabIconGear, "gear"}, + {"NativeTabIconStar", NativeTabIconStar, "star"}, + {"NativeTabIconPerson", NativeTabIconPerson, "person"}, + {"NativeTabIconBell", NativeTabIconBell, "bell"}, + {"NativeTabIconMagnify", NativeTabIconMagnify, "magnifyingglass"}, + {"NativeTabIconList", NativeTabIconList, "list.bullet"}, + {"NativeTabIconFolder", NativeTabIconFolder, "folder"}, + } + + for _, tt := range tests { + if string(tt.icon) != tt.expected { + t.Errorf("%s = %q, want %q", tt.name, string(tt.icon), tt.expected) + } + } +} + +func TestChainMiddleware_Empty(t *testing.T) { + baseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("base")) + }) + + chained := ChainMiddleware() + handler := chained(baseHandler) + + req := httptest.NewRequest("GET", "/", nil) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("Status = %d, want %d", rec.Code, http.StatusOK) + } + if rec.Body.String() != "base" { + t.Errorf("Body = %q, want %q", rec.Body.String(), "base") + } +} + +func TestChainMiddleware_Single(t *testing.T) { + callOrder := []string{} + + middleware := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callOrder = append(callOrder, "middleware") + next.ServeHTTP(w, r) + }) + } + + baseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callOrder = append(callOrder, "base") + w.WriteHeader(http.StatusOK) + }) + + chained := ChainMiddleware(middleware) + handler := chained(baseHandler) + + req := httptest.NewRequest("GET", "/", nil) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) + + if len(callOrder) != 2 { + t.Errorf("Expected 2 calls, got %d", len(callOrder)) + } + if callOrder[0] != "middleware" { + t.Errorf("First call should be middleware, got %s", callOrder[0]) + } + if callOrder[1] != "base" { + t.Errorf("Second call should be base, got %s", callOrder[1]) + } +} + +func TestChainMiddleware_Multiple(t *testing.T) { + callOrder := []string{} + + middleware1 := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callOrder = append(callOrder, "middleware1") + next.ServeHTTP(w, r) + }) + } + + middleware2 := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callOrder = append(callOrder, "middleware2") + next.ServeHTTP(w, r) + }) + } + + middleware3 := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callOrder = append(callOrder, "middleware3") + next.ServeHTTP(w, r) + }) + } + + baseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callOrder = append(callOrder, "base") + w.WriteHeader(http.StatusOK) + }) + + chained := ChainMiddleware(middleware1, middleware2, middleware3) + handler := chained(baseHandler) + + req := httptest.NewRequest("GET", "/", nil) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) + + expected := []string{"middleware1", "middleware2", "middleware3", "base"} + if len(callOrder) != len(expected) { + t.Errorf("Expected %d calls, got %d", len(expected), len(callOrder)) + } + for i, v := range expected { + if i < len(callOrder) && callOrder[i] != v { + t.Errorf("Call %d: expected %s, got %s", i, v, callOrder[i]) + } + } +} + +func TestOptions_Defaults(t *testing.T) { + opts := Options{} + + if opts.Name != "" { + t.Error("Name should default to empty string") + } + if opts.Description != "" { + t.Error("Description should default to empty string") + } + if opts.Icon != nil { + t.Error("Icon should default to nil") + } + if opts.Logger != nil { + t.Error("Logger should default to nil") + } + if opts.DisableDefaultSignalHandler != false { + t.Error("DisableDefaultSignalHandler should default to false") + } +} + +func TestMacOptions_Defaults(t *testing.T) { + opts := MacOptions{} + + if opts.ActivationPolicy != ActivationPolicyRegular { + t.Error("ActivationPolicy should default to ActivationPolicyRegular") + } + if opts.ApplicationShouldTerminateAfterLastWindowClosed != false { + t.Error("ApplicationShouldTerminateAfterLastWindowClosed should default to false") + } +} + +func TestWindowsOptions_Defaults(t *testing.T) { + opts := WindowsOptions{} + + if opts.WndClass != "" { + t.Error("WndClass should default to empty string") + } + if opts.DisableQuitOnLastWindowClosed != false { + t.Error("DisableQuitOnLastWindowClosed should default to false") + } + if opts.WebviewUserDataPath != "" { + t.Error("WebviewUserDataPath should default to empty string") + } + if opts.WebviewBrowserPath != "" { + t.Error("WebviewBrowserPath should default to empty string") + } +} + +func TestLinuxOptions_Defaults(t *testing.T) { + opts := LinuxOptions{} + + if opts.DisableQuitOnLastWindowClosed != false { + t.Error("DisableQuitOnLastWindowClosed should default to false") + } + if opts.ProgramName != "" { + t.Error("ProgramName should default to empty string") + } +} + +func TestIOSOptions_Defaults(t *testing.T) { + opts := IOSOptions{} + + if opts.DisableInputAccessoryView != false { + t.Error("DisableInputAccessoryView should default to false") + } + if opts.DisableScroll != false { + t.Error("DisableScroll should default to false") + } + if opts.DisableBounce != false { + t.Error("DisableBounce should default to false") + } + if opts.EnableBackForwardNavigationGestures != false { + t.Error("EnableBackForwardNavigationGestures should default to false") + } + if opts.EnableNativeTabs != false { + t.Error("EnableNativeTabs should default to false") + } +} + +func TestAndroidOptions_Defaults(t *testing.T) { + opts := AndroidOptions{} + + if opts.DisableScroll != false { + t.Error("DisableScroll should default to false") + } + if opts.DisableOverscroll != false { + t.Error("DisableOverscroll should default to false") + } + if opts.EnableZoom != false { + t.Error("EnableZoom should default to false") + } + if opts.DisableHardwareAcceleration != false { + t.Error("DisableHardwareAcceleration should default to false") + } +} + +func TestAssetOptions_Defaults(t *testing.T) { + opts := AssetOptions{} + + if opts.Handler != nil { + t.Error("Handler should default to nil") + } + if opts.Middleware != nil { + t.Error("Middleware should default to nil") + } + if opts.DisableLogging != false { + t.Error("DisableLogging should default to false") + } +} + +func TestNativeTabItem_Fields(t *testing.T) { + item := NativeTabItem{ + Title: "Home", + SystemImage: NativeTabIconHouse, + } + + if item.Title != "Home" { + t.Errorf("Title = %q, want %q", item.Title, "Home") + } + if item.SystemImage != NativeTabIconHouse { + t.Errorf("SystemImage = %q, want %q", item.SystemImage, NativeTabIconHouse) + } +} + +func TestMiddleware_ShortCircuit(t *testing.T) { + shortCircuit := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusForbidden) + w.Write([]byte("forbidden")) + // Don't call next + }) + } + + baseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("base")) + }) + + chained := ChainMiddleware(shortCircuit) + handler := chained(baseHandler) + + req := httptest.NewRequest("GET", "/", nil) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) + + if rec.Code != http.StatusForbidden { + t.Errorf("Status = %d, want %d", rec.Code, http.StatusForbidden) + } + if rec.Body.String() != "forbidden" { + t.Errorf("Body = %q, want %q", rec.Body.String(), "forbidden") + } +} diff --git a/v3/pkg/application/application_production.go b/v3/pkg/application/application_production.go new file mode 100644 index 000000000..75f86b44d --- /dev/null +++ b/v3/pkg/application/application_production.go @@ -0,0 +1,18 @@ +//go:build production + +package application + +func newApplication(options Options) *App { + result := &App{ + isDebugMode: false, + options: options, + } + result.init() + return result +} + +func (a *App) logStartup() {} + +func (a *App) preRun() error { return nil } + +func (a *App) postQuit() error { return nil } diff --git a/v3/pkg/application/application_server.go b/v3/pkg/application/application_server.go new file mode 100644 index 000000000..41059242f --- /dev/null +++ b/v3/pkg/application/application_server.go @@ -0,0 +1,554 @@ +//go:build server + +package application + +import ( + "context" + "errors" + "fmt" + "net" + "net/http" + "os" + "os/signal" + "strconv" + "syscall" + "time" + "unsafe" +) + +// serverApp implements platformApp for server mode. +// It provides a minimal implementation that runs an HTTP server +// without any GUI components. +// +// Server mode is enabled by building with the "server" build tag: +// +// go build -tags server +type serverApp struct { + app *App + server *http.Server + listener net.Listener + broadcaster *WebSocketBroadcaster +} + +// newPlatformApp creates a new server-mode platform app. +// This function is only compiled when building with the "server" tag. +func newPlatformApp(app *App) *serverApp { + app.info("Server mode enabled (built with -tags server)") + return &serverApp{ + app: app, + } +} + +// parsePort parses a port string into an integer. +func parsePort(s string) (int, error) { + p, err := strconv.Atoi(s) + if err != nil { + return 0, err + } + if p < 1 || p > 65535 { + return 0, errors.New("port out of range") + } + return p, nil +} + +// run starts the HTTP server and blocks until shutdown. +func (h *serverApp) run() error { + // Set up common events + h.setupCommonEvents() + + // Create WebSocket broadcaster for events + h.broadcaster = NewWebSocketBroadcaster(h.app) + globalBroadcaster = h.broadcaster // Set global reference for browser ID lookups + h.app.wailsEventListenerLock.Lock() + h.app.wailsEventListeners = append(h.app.wailsEventListeners, h.broadcaster) + h.app.wailsEventListenerLock.Unlock() + + opts := h.app.options.Server + + // Environment variables override config (useful for Docker/containers) + host := os.Getenv("WAILS_SERVER_HOST") + if host == "" { + host = opts.Host + } + if host == "" { + host = "localhost" + } + + port := opts.Port + if envPort := os.Getenv("WAILS_SERVER_PORT"); envPort != "" { + if p, err := parsePort(envPort); err == nil { + port = p + } + } + if port == 0 { + port = 8080 + } + + readTimeout := opts.ReadTimeout + if readTimeout == 0 { + readTimeout = 30 * time.Second + } + + writeTimeout := opts.WriteTimeout + if writeTimeout == 0 { + writeTimeout = 30 * time.Second + } + + idleTimeout := opts.IdleTimeout + if idleTimeout == 0 { + idleTimeout = 120 * time.Second + } + + shutdownTimeout := opts.ShutdownTimeout + if shutdownTimeout == 0 { + shutdownTimeout = 30 * time.Second + } + + addr := fmt.Sprintf("%s:%d", host, port) + + // Create HTTP handler from asset server + handler := h.createHandler() + + h.server = &http.Server{ + Addr: addr, + Handler: handler, + ReadTimeout: readTimeout, + WriteTimeout: writeTimeout, + IdleTimeout: idleTimeout, + } + + // Create listener + var err error + h.listener, err = net.Listen("tcp", addr) + if err != nil { + return fmt.Errorf("failed to listen on %s: %w", addr, err) + } + + h.app.info("Server mode starting", "address", addr) + + // Start server in goroutine + errCh := make(chan error, 1) + go func() { + if opts.TLS != nil { + errCh <- h.server.ServeTLS(h.listener, opts.TLS.CertFile, opts.TLS.KeyFile) + } else { + errCh <- h.server.Serve(h.listener) + } + }() + + // Wait for shutdown signal or error + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + + select { + case err := <-errCh: + if err != nil && !errors.Is(err, http.ErrServerClosed) { + return err + } + case <-quit: + h.app.info("Shutdown signal received") + case <-h.app.ctx.Done(): + h.app.info("Application context cancelled") + } + + // Graceful shutdown + ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) + defer cancel() + + if err := h.server.Shutdown(ctx); err != nil { + return fmt.Errorf("server shutdown error: %w", err) + } + + h.app.info("Server stopped") + return nil +} + +// customJS is the JavaScript that sets up WebSocket event connection for server mode. +// Events FROM frontend TO backend use the existing HTTP transport. +// This WebSocket is only for receiving broadcast events FROM backend TO all frontends. +const customJS = `(function() { + var protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; + var clientId = window._wails && window._wails.clientId ? window._wails.clientId : ''; + var wsUrl = protocol + '//' + location.host + '/wails/events' + (clientId ? '?clientId=' + encodeURIComponent(clientId) : ''); + var ws; + + function connect() { + ws = new WebSocket(wsUrl); + ws.onopen = function() { + console.log('[Wails] Event WebSocket connected'); + }; + ws.onmessage = function(e) { + try { + var event = JSON.parse(e.data); + if (window._wails && window._wails.dispatchWailsEvent) { + window._wails.dispatchWailsEvent(event); + } + } catch (err) { + console.error('[Wails] Failed to parse event:', err); + } + }; + ws.onclose = function() { + console.log('[Wails] Event WebSocket disconnected, reconnecting...'); + setTimeout(connect, 1000); + }; + ws.onerror = function() { + ws.close(); + }; + } + + connect(); +})();` + +// createHandler creates the HTTP handler for server mode. +func (h *serverApp) createHandler() http.Handler { + mux := http.NewServeMux() + + // Health check endpoint + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status":"ok"}`)) + }) + + // Serve custom.js for server mode (WebSocket event connection) + mux.HandleFunc("/wails/custom.js", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/javascript") + w.WriteHeader(http.StatusOK) + w.Write([]byte(customJS)) + }) + + // WebSocket endpoint for events + mux.Handle("/wails/events", h.broadcaster) + + // Serve all other requests through the asset server + mux.Handle("/", h.app.assets) + + return mux +} + +// destroy stops the server and cleans up. +func (h *serverApp) destroy() { + if h.server != nil { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + h.server.Shutdown(ctx) + } + h.app.cleanup() +} + +// setApplicationMenu is a no-op in server mode. +func (h *serverApp) setApplicationMenu(menu *Menu) { + // No-op: server mode has no GUI +} + +// name returns the application name. +func (h *serverApp) name() string { + return h.app.options.Name +} + +// getCurrentWindowID returns 0 in server mode (no windows). +func (h *serverApp) getCurrentWindowID() uint { + return 0 +} + +// showAboutDialog is a no-op in server mode. +func (h *serverApp) showAboutDialog(name string, description string, icon []byte) { + // No-op: server mode has no GUI + h.app.warning("showAboutDialog called in server mode - operation ignored") +} + +// setIcon is a no-op in server mode. +func (h *serverApp) setIcon(icon []byte) { + // No-op: server mode has no GUI +} + +// on is a no-op in server mode. +func (h *serverApp) on(id uint) { + // No-op: server mode has no platform-specific event handling +} + +// dispatchOnMainThread executes the function directly in server mode. +func (h *serverApp) dispatchOnMainThread(id uint) { + // In server mode, there's no "main thread" concept from GUI frameworks + // Execute the function directly + mainThreadFunctionStoreLock.Lock() + fn, ok := mainThreadFunctionStore[id] + if ok { + delete(mainThreadFunctionStore, id) + } + mainThreadFunctionStoreLock.Unlock() + + if ok && fn != nil { + fn() + } +} + +// hide is a no-op in server mode. +func (h *serverApp) hide() { + // No-op: server mode has no GUI +} + +// show is a no-op in server mode. +func (h *serverApp) show() { + // No-op: server mode has no GUI +} + +// getPrimaryScreen returns nil in server mode. +func (h *serverApp) getPrimaryScreen() (*Screen, error) { + return nil, errors.New("screen information not available in server mode") +} + +// getScreens returns an error in server mode (screen info unavailable). +func (h *serverApp) getScreens() ([]*Screen, error) { + return nil, errors.New("screen information not available in server mode") +} + +// GetFlags returns the application flags for server mode. +func (h *serverApp) GetFlags(options Options) map[string]any { + flags := make(map[string]any) + flags["server"] = true + if options.Flags != nil { + for k, v := range options.Flags { + flags[k] = v + } + } + return flags +} + +// isOnMainThread always returns true in server mode. +func (h *serverApp) isOnMainThread() bool { + // In server mode, there's no main thread concept + return true +} + +// isDarkMode returns false in server mode. +func (h *serverApp) isDarkMode() bool { + return false +} + +// getAccentColor returns empty string in server mode. +func (h *serverApp) getAccentColor() string { + return "" +} + +// logPlatformInfo logs platform info for server mode. +func (a *App) logPlatformInfo() { + a.info("Platform Info:", "mode", "server") +} + +// platformEnvironment returns environment info for server mode. +func (a *App) platformEnvironment() map[string]any { + return map[string]any{ + "mode": "server", + } +} + +// fatalHandler sets up fatal error handling for server mode. +func fatalHandler(errFunc func(error)) { + // In server mode, fatal errors are handled via standard mechanisms +} + +// newClipboardImpl creates a clipboard implementation for server mode. +func newClipboardImpl() clipboardImpl { + return &serverClipboard{} +} + +// serverClipboard is a no-op clipboard for server mode. +type serverClipboard struct{} + +func (c *serverClipboard) setText(text string) bool { + return false +} + +func (c *serverClipboard) text() (string, bool) { + return "", false +} + +// newDialogImpl creates a dialog implementation for server mode. +func newDialogImpl(d *MessageDialog) messageDialogImpl { + return &serverDialog{} +} + +// serverDialog is a no-op dialog for server mode. +type serverDialog struct{} + +func (d *serverDialog) show() { + // No-op in server mode +} + +// newOpenFileDialogImpl creates an open file dialog implementation for server mode. +func newOpenFileDialogImpl(d *OpenFileDialogStruct) openFileDialogImpl { + return &serverOpenFileDialog{} +} + +// serverOpenFileDialog is a no-op open file dialog for server mode. +type serverOpenFileDialog struct{} + +func (d *serverOpenFileDialog) show() (chan string, error) { + ch := make(chan string, 1) + close(ch) + return ch, errors.New("file dialogs not available in server mode") +} + +// newSaveFileDialogImpl creates a save file dialog implementation for server mode. +func newSaveFileDialogImpl(d *SaveFileDialogStruct) saveFileDialogImpl { + return &serverSaveFileDialog{} +} + +// serverSaveFileDialog is a no-op save file dialog for server mode. +type serverSaveFileDialog struct{} + +func (d *serverSaveFileDialog) show() (chan string, error) { + ch := make(chan string, 1) + close(ch) + return ch, errors.New("file dialogs not available in server mode") +} + +// newMenuImpl creates a menu implementation for server mode. +func newMenuImpl(menu *Menu) menuImpl { + return &serverMenu{} +} + +// serverMenu is a no-op menu for server mode. +type serverMenu struct{} + +func (m *serverMenu) update() { + // No-op in server mode +} + +// newPlatformLock creates a platform-specific single instance lock for server mode. +func newPlatformLock(manager *singleInstanceManager) (platformLock, error) { + return &serverLock{}, nil +} + +// serverLock is a basic lock for server mode. +type serverLock struct{} + +func (l *serverLock) acquire(uniqueID string) error { + return nil +} + +func (l *serverLock) release() { + // No-op in server mode +} + +func (l *serverLock) notify(data string) error { + return errors.New("single instance not supported in server mode") +} + +// newSystemTrayImpl creates a system tray implementation for server mode. +func newSystemTrayImpl(s *SystemTray) systemTrayImpl { + return &serverSystemTray{parent: s} +} + +// serverSystemTray is a no-op system tray for server mode. +type serverSystemTray struct { + parent *SystemTray +} + +func (t *serverSystemTray) setLabel(label string) {} +func (t *serverSystemTray) setTooltip(tooltip string) {} +func (t *serverSystemTray) run() {} +func (t *serverSystemTray) setIcon(icon []byte) {} +func (t *serverSystemTray) setMenu(menu *Menu) {} +func (t *serverSystemTray) setIconPosition(pos IconPosition) {} +func (t *serverSystemTray) setTemplateIcon(icon []byte) {} +func (t *serverSystemTray) destroy() {} +func (t *serverSystemTray) setDarkModeIcon(icon []byte) {} +func (t *serverSystemTray) bounds() (*Rect, error) { return nil, errors.New("system tray not available in server mode") } +func (t *serverSystemTray) getScreen() (*Screen, error) { return nil, errors.New("system tray not available in server mode") } +func (t *serverSystemTray) positionWindow(w Window, o int) error { return errors.New("system tray not available in server mode") } +func (t *serverSystemTray) openMenu() {} +func (t *serverSystemTray) Show() {} +func (t *serverSystemTray) Hide() {} + +// newWindowImpl creates a webview window implementation for server mode. +func newWindowImpl(parent *WebviewWindow) *serverWebviewWindow { + return &serverWebviewWindow{parent: parent} +} + +// serverWebviewWindow is a no-op webview window for server mode. +type serverWebviewWindow struct { + parent *WebviewWindow +} + +// All webviewWindowImpl methods as no-ops for server mode +func (w *serverWebviewWindow) setTitle(title string) {} +func (w *serverWebviewWindow) setSize(width, height int) {} +func (w *serverWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) {} +func (w *serverWebviewWindow) setURL(url string) {} +func (w *serverWebviewWindow) setResizable(resizable bool) {} +func (w *serverWebviewWindow) setMinSize(width, height int) {} +func (w *serverWebviewWindow) setMaxSize(width, height int) {} +func (w *serverWebviewWindow) execJS(js string) {} +func (w *serverWebviewWindow) setBackgroundColour(color RGBA) {} +func (w *serverWebviewWindow) run() {} +func (w *serverWebviewWindow) center() {} +func (w *serverWebviewWindow) size() (int, int) { return 0, 0 } +func (w *serverWebviewWindow) width() int { return 0 } +func (w *serverWebviewWindow) height() int { return 0 } +func (w *serverWebviewWindow) destroy() {} +func (w *serverWebviewWindow) reload() {} +func (w *serverWebviewWindow) forceReload() {} +func (w *serverWebviewWindow) openDevTools() {} +func (w *serverWebviewWindow) zoomReset() {} +func (w *serverWebviewWindow) zoomIn() {} +func (w *serverWebviewWindow) zoomOut() {} +func (w *serverWebviewWindow) getZoom() float64 { return 1.0 } +func (w *serverWebviewWindow) setZoom(zoom float64) {} +func (w *serverWebviewWindow) close() {} +func (w *serverWebviewWindow) zoom() {} +func (w *serverWebviewWindow) setHTML(html string) {} +func (w *serverWebviewWindow) on(eventID uint) {} +func (w *serverWebviewWindow) minimise() {} +func (w *serverWebviewWindow) unminimise() {} +func (w *serverWebviewWindow) maximise() {} +func (w *serverWebviewWindow) unmaximise() {} +func (w *serverWebviewWindow) fullscreen() {} +func (w *serverWebviewWindow) unfullscreen() {} +func (w *serverWebviewWindow) isMinimised() bool { return false } +func (w *serverWebviewWindow) isMaximised() bool { return false } +func (w *serverWebviewWindow) isFullscreen() bool { return false } +func (w *serverWebviewWindow) isNormal() bool { return true } +func (w *serverWebviewWindow) isVisible() bool { return false } +func (w *serverWebviewWindow) isFocused() bool { return false } +func (w *serverWebviewWindow) focus() {} +func (w *serverWebviewWindow) show() {} +func (w *serverWebviewWindow) hide() {} +func (w *serverWebviewWindow) getScreen() (*Screen, error) { return nil, errors.New("screens not available in server mode") } +func (w *serverWebviewWindow) setFrameless(frameless bool) {} +func (w *serverWebviewWindow) openContextMenu(menu *Menu, data *ContextMenuData) {} +func (w *serverWebviewWindow) nativeWindow() unsafe.Pointer { return nil } +func (w *serverWebviewWindow) startDrag() error { return errors.New("drag not available in server mode") } +func (w *serverWebviewWindow) startResize(border string) error { return errors.New("resize not available in server mode") } +func (w *serverWebviewWindow) print() error { return errors.New("print not available in server mode") } +func (w *serverWebviewWindow) setEnabled(enabled bool) {} +func (w *serverWebviewWindow) physicalBounds() Rect { return Rect{} } +func (w *serverWebviewWindow) setPhysicalBounds(bounds Rect) {} +func (w *serverWebviewWindow) bounds() Rect { return Rect{} } +func (w *serverWebviewWindow) setBounds(bounds Rect) {} +func (w *serverWebviewWindow) position() (int, int) { return 0, 0 } +func (w *serverWebviewWindow) setPosition(x int, y int) {} +func (w *serverWebviewWindow) relativePosition() (int, int) { return 0, 0 } +func (w *serverWebviewWindow) setRelativePosition(x int, y int) {} +func (w *serverWebviewWindow) flash(enabled bool) {} +func (w *serverWebviewWindow) handleKeyEvent(acceleratorString string) {} +func (w *serverWebviewWindow) getBorderSizes() *LRTB { return &LRTB{} } +func (w *serverWebviewWindow) setMinimiseButtonState(state ButtonState) {} +func (w *serverWebviewWindow) setMaximiseButtonState(state ButtonState) {} +func (w *serverWebviewWindow) setCloseButtonState(state ButtonState) {} +func (w *serverWebviewWindow) isIgnoreMouseEvents() bool { return false } +func (w *serverWebviewWindow) setIgnoreMouseEvents(ignore bool) {} +func (w *serverWebviewWindow) cut() {} +func (w *serverWebviewWindow) copy() {} +func (w *serverWebviewWindow) paste() {} +func (w *serverWebviewWindow) undo() {} +func (w *serverWebviewWindow) delete() {} +func (w *serverWebviewWindow) selectAll() {} +func (w *serverWebviewWindow) redo() {} +func (w *serverWebviewWindow) showMenuBar() {} +func (w *serverWebviewWindow) hideMenuBar() {} +func (w *serverWebviewWindow) toggleMenuBar() {} +func (w *serverWebviewWindow) setMenu(menu *Menu) {} +func (w *serverWebviewWindow) snapAssist() {} +func (w *serverWebviewWindow) setContentProtection(enabled bool) {} diff --git a/v3/pkg/application/application_server_test.go b/v3/pkg/application/application_server_test.go new file mode 100644 index 000000000..0bb4780dd --- /dev/null +++ b/v3/pkg/application/application_server_test.go @@ -0,0 +1,168 @@ +//go:build server + +package application + +import ( + "context" + "net/http" + "sync" + "testing" + "time" +) + +// resetGlobalApp resets the global application state for testing +func resetGlobalApp() { + globalApplication = nil +} + +func TestServerMode_HealthEndpoint(t *testing.T) { + resetGlobalApp() + + // Create a server mode app (server mode is enabled via build tag) + app := New(Options{ + Name: "Test Server", + Server: ServerOptions{ + Host: "127.0.0.1", + Port: 18081, // Use specific port for this test + }, + Assets: AssetOptions{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) + }), + }, + }) + + // Start app in background + var wg sync.WaitGroup + wg.Add(1) + errCh := make(chan error, 1) + go func() { + defer wg.Done() + errCh <- app.Run() + }() + + // Wait for server to start + time.Sleep(200 * time.Millisecond) + + // Test health endpoint + resp, err := http.Get("http://127.0.0.1:18081/health") + if err != nil { + t.Fatalf("health check failed: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Errorf("expected status 200, got %d", resp.StatusCode) + } + + // Shutdown + app.Quit() + + // Wait for shutdown + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + select { + case err := <-errCh: + if err != nil { + t.Errorf("app.Run() returned error: %v", err) + } + case <-ctx.Done(): + t.Error("timeout waiting for app shutdown") + } +} + +func TestServerMode_AssetServing(t *testing.T) { + resetGlobalApp() + + testContent := "Hello from server mode!" + + app := New(Options{ + Name: "Test Assets", + Server: ServerOptions{ + Host: "127.0.0.1", + Port: 18082, // Use specific port for this test + }, + Assets: AssetOptions{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(testContent)) + }), + }, + }) + + errCh := make(chan error, 1) + go func() { + errCh <- app.Run() + }() + + time.Sleep(200 * time.Millisecond) + + // Test asset serving + resp, err := http.Get("http://127.0.0.1:18082/") + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Errorf("expected status 200, got %d", resp.StatusCode) + } + + app.Quit() + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + select { + case <-errCh: + case <-ctx.Done(): + t.Error("timeout waiting for app shutdown") + } +} + +func TestServerMode_Defaults(t *testing.T) { + resetGlobalApp() + + app := New(Options{ + Name: "Test Defaults", + Server: ServerOptions{ + Port: 18083, // Use specific port to avoid conflicts + }, + Assets: AssetOptions{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }), + }, + }) + + errCh := make(chan error, 1) + go func() { + errCh <- app.Run() + }() + + time.Sleep(200 * time.Millisecond) + + // Should be listening on localhost:18083 + resp, err := http.Get("http://localhost:18083/health") + if err != nil { + t.Fatalf("request to address failed: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Errorf("expected status 200, got %d", resp.StatusCode) + } + + app.Quit() + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + select { + case <-errCh: + case <-ctx.Done(): + t.Error("timeout waiting for app shutdown") + } +} diff --git a/v3/pkg/application/application_windows.go b/v3/pkg/application/application_windows.go new file mode 100644 index 000000000..3b93b9a9c --- /dev/null +++ b/v3/pkg/application/application_windows.go @@ -0,0 +1,449 @@ +//go:build windows && !server + +package application + +import ( + "errors" + "os" + "path/filepath" + "slices" + "strings" + "sync" + "sync/atomic" + "syscall" + "time" + "unsafe" + + "github.com/wailsapp/go-webview2/webviewloader" + + "github.com/wailsapp/wails/v3/internal/operatingsystem" + + "github.com/wailsapp/wails/v3/pkg/events" + "github.com/wailsapp/wails/v3/pkg/w32" +) + +var ( + wmTaskbarCreated = w32.RegisterWindowMessage(w32.MustStringToUTF16Ptr("TaskbarCreated")) +) + +type windowsApp struct { + parent *App + + windowClass w32.WNDCLASSEX + instance w32.HINSTANCE + + windowMap map[w32.HWND]*windowsWebviewWindow + windowMapLock sync.RWMutex + + systrayMap map[w32.HMENU]*windowsSystemTray + systrayMapLock sync.RWMutex + + mainThreadID w32.HANDLE + mainThreadWindowHWND w32.HWND + + // Windows hidden by application.Hide() + hiddenWindows []*windowsWebviewWindow + focusedWindow w32.HWND + + // system theme + isCurrentlyDarkMode bool + currentWindowID uint + + // Restart taskbar flag + restartingTaskbar atomic.Bool +} + +func (m *windowsApp) isDarkMode() bool { + return w32.IsCurrentlyDarkMode() +} + +func (m *windowsApp) getAccentColor() string { + accentColor, err := w32.GetAccentColor() + if err != nil { + m.parent.error("failed to get accent color: %w", err) + return "rgb(0,122,255)" + } + + return accentColor +} + +func (m *windowsApp) isOnMainThread() bool { + return m.mainThreadID == w32.GetCurrentThreadId() +} + +func (m *windowsApp) GetFlags(options Options) map[string]any { + if options.Flags == nil { + options.Flags = make(map[string]any) + } + options.Flags["system"] = map[string]any{ + "resizeHandleWidth": w32.GetSystemMetrics(w32.SM_CXSIZEFRAME), + "resizeHandleHeight": w32.GetSystemMetrics(w32.SM_CYSIZEFRAME), + } + return options.Flags +} + +func (m *windowsApp) getWindowForHWND(hwnd w32.HWND) *windowsWebviewWindow { + m.windowMapLock.RLock() + defer m.windowMapLock.RUnlock() + return m.windowMap[hwnd] +} + +func getNativeApplication() *windowsApp { + return globalApplication.impl.(*windowsApp) +} + +func (m *windowsApp) hide() { + // Get the current focussed window + m.focusedWindow = w32.GetForegroundWindow() + + // Iterate over all windows and hide them if they aren't already hidden + for _, window := range m.windowMap { + if window.isVisible() { + // Add to hidden windows + m.hiddenWindows = append(m.hiddenWindows, window) + window.hide() + } + } + // Switch focus to the next application + hwndNext := w32.GetWindow(m.mainThreadWindowHWND, w32.GW_HWNDNEXT) + w32.SetForegroundWindow(hwndNext) +} + +func (m *windowsApp) show() { + // Iterate over all windows and show them if they were previously hidden + for _, window := range m.hiddenWindows { + window.show() + } + // Show the foreground window + w32.SetForegroundWindow(m.focusedWindow) +} + +func (m *windowsApp) on(_ uint) { +} + +func (m *windowsApp) setIcon(_ []byte) { +} + +func (m *windowsApp) name() string { + // appName := C.getAppName() + // defer C.free(unsafe.Pointer(appName)) + // return C.GoString(appName) + return "" +} + +func (m *windowsApp) getCurrentWindowID() uint { + return m.currentWindowID +} + +func (m *windowsApp) setApplicationMenu(menu *Menu) { + if menu == nil { + // Create a default menu for windows + menu = DefaultApplicationMenu() + } + menu.Update() + + m.parent.applicationMenu = menu +} + +func (m *windowsApp) run() error { + m.setupCommonEvents() + for eventID := range m.parent.applicationEventListeners { + m.on(eventID) + } + // EmitEvent application started event + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Windows.ApplicationStarted), + ctx: blankApplicationEventContext, + } + + if len(os.Args) == 2 { // Case: program + 1 argument + arg1 := os.Args[1] + // Check if the argument is likely a URL from a custom protocol invocation + if strings.Contains(arg1, "://") { + m.parent.debug("Application launched with argument, potentially a URL from custom protocol", "url", arg1) + eventContext := newApplicationEventContext() + eventContext.setURL(arg1) + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Common.ApplicationLaunchedWithUrl), + ctx: eventContext, + } + } else { + // If not a URL-like string, check for file association + if m.parent.options.FileAssociations != nil { + ext := filepath.Ext(arg1) + if slices.Contains(m.parent.options.FileAssociations, ext) { + m.parent.debug("Application launched with file via file association", "file", arg1) + eventContext := newApplicationEventContext() + eventContext.setOpenedWithFile(arg1) + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Common.ApplicationOpenedWithFile), + ctx: eventContext, + } + } + } + } + } else if len(os.Args) > 2 { + // Log if multiple arguments are passed, though typical protocol/file launch is a single arg. + m.parent.debug("Application launched with multiple arguments", "args", os.Args[1:]) + } + + _ = m.runMainLoop() + + return nil +} + +func (m *windowsApp) destroy() { + if !globalApplication.shouldQuit() { + return + } + globalApplication.cleanup() + // destroy the main thread window + w32.DestroyWindow(m.mainThreadWindowHWND) + // Post a quit message to the main thread + w32.PostQuitMessage(0) +} + +func (m *windowsApp) init() { + // Register the window class + + icon := w32.LoadIconWithResourceID(m.instance, w32.IDI_APPLICATION) + + m.windowClass.Size = uint32(unsafe.Sizeof(m.windowClass)) + m.windowClass.Style = w32.CS_HREDRAW | w32.CS_VREDRAW + m.windowClass.WndProc = syscall.NewCallback(m.wndProc) + m.windowClass.Instance = m.instance + m.windowClass.Background = w32.COLOR_BTNFACE + 1 + m.windowClass.Icon = icon + m.windowClass.Cursor = w32.LoadCursorWithResourceID(0, w32.IDC_ARROW) + m.windowClass.ClassName = w32.MustStringToUTF16Ptr(m.parent.options.Windows.WndClass) + m.windowClass.MenuName = nil + m.windowClass.IconSm = icon + + if ret := w32.RegisterClassEx(&m.windowClass); ret == 0 { + panic(syscall.GetLastError()) + } + m.isCurrentlyDarkMode = w32.IsCurrentlyDarkMode() +} + +func (m *windowsApp) wndProc(hwnd w32.HWND, msg uint32, wParam, lParam uintptr) uintptr { + + // Handle the invoke callback + if msg == wmInvokeCallback { + m.invokeCallback(wParam, lParam) + return 0 + } + + // If the WndProcInterceptor is set in options, pass the message on + if m.parent.options.Windows.WndProcInterceptor != nil { + returnValue, shouldReturn := m.parent.options.Windows.WndProcInterceptor(hwnd, msg, wParam, lParam) + if shouldReturn { + return returnValue + } + } + + // Handle the main thread window + // Quit the application if requested + // Reprocess and cache screens when display settings change + if hwnd == m.mainThreadWindowHWND { + if msg == w32.WM_ENDSESSION || msg == w32.WM_DESTROY || msg == w32.WM_CLOSE { + globalApplication.Quit() + } + if msg == w32.WM_DISPLAYCHANGE || (msg == w32.WM_SETTINGCHANGE && wParam == w32.SPI_SETWORKAREA) { + err := m.processAndCacheScreens() + if err != nil { + m.parent.handleError(err) + } + } + } + + switch msg { + case wmTaskbarCreated: + if m.restartingTaskbar.Load() { + break + } + m.restartingTaskbar.Store(true) + m.reshowSystrays() + go func() { + // 1 second debounce + time.Sleep(1000) + m.restartingTaskbar.Store(false) + }() + case w32.WM_SETTINGCHANGE: + settingChanged := w32.UTF16PtrToString((*uint16)(unsafe.Pointer(lParam))) + if settingChanged == "ImmersiveColorSet" { + isDarkMode := w32.IsCurrentlyDarkMode() + if isDarkMode != m.isCurrentlyDarkMode { + eventContext := newApplicationEventContext() + eventContext.setIsDarkMode(isDarkMode) + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Windows.SystemThemeChanged), + ctx: eventContext, + } + m.isCurrentlyDarkMode = isDarkMode + } + } + return 0 + case w32.WM_POWERBROADCAST: + switch wParam { + case w32.PBT_APMPOWERSTATUSCHANGE: + applicationEvents <- newApplicationEvent(events.Windows.APMPowerStatusChange) + case w32.PBT_APMSUSPEND: + applicationEvents <- newApplicationEvent(events.Windows.APMSuspend) + case w32.PBT_APMRESUMEAUTOMATIC: + applicationEvents <- newApplicationEvent(events.Windows.APMResumeAutomatic) + case w32.PBT_APMRESUMESUSPEND: + applicationEvents <- newApplicationEvent(events.Windows.APMResumeSuspend) + case w32.PBT_POWERSETTINGCHANGE: + applicationEvents <- newApplicationEvent(events.Windows.APMPowerSettingChange) + } + return 0 + } + + if window, ok := m.windowMap[hwnd]; ok { + return window.WndProc(msg, wParam, lParam) + } + + m.systrayMapLock.Lock() + systray, ok := m.systrayMap[hwnd] + m.systrayMapLock.Unlock() + if ok { + return systray.wndProc(msg, wParam, lParam) + } + + // Dispatch the message to the appropriate window + + return w32.DefWindowProc(hwnd, msg, wParam, lParam) +} + +func (m *windowsApp) registerWindow(result *windowsWebviewWindow) { + m.windowMapLock.Lock() + m.windowMap[result.hwnd] = result + m.windowMapLock.Unlock() +} + +func (m *windowsApp) registerSystemTray(result *windowsSystemTray) { + m.systrayMapLock.Lock() + defer m.systrayMapLock.Unlock() + m.systrayMap[result.hwnd] = result +} + +func (m *windowsApp) unregisterSystemTray(result *windowsSystemTray) { + m.systrayMapLock.Lock() + defer m.systrayMapLock.Unlock() + delete(m.systrayMap, result.hwnd) +} + +func (m *windowsApp) unregisterWindow(w *windowsWebviewWindow) { + m.windowMapLock.Lock() + delete(m.windowMap, w.hwnd) + m.windowMapLock.Unlock() + + // If this was the last window... + if len(m.windowMap) == 0 && !m.parent.options.Windows.DisableQuitOnLastWindowClosed { + w32.PostQuitMessage(0) + } +} + +func (m *windowsApp) reshowSystrays() { + m.systrayMapLock.Lock() + defer m.systrayMapLock.Unlock() + for _, systray := range m.systrayMap { + if _, err := systray.show(); err != nil { + globalApplication.warning("failed to re-add system tray icon: %v", err) + } + } +} + +func setupDPIAwareness() error { + // https://learn.microsoft.com/en-us/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process + // https://learn.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows + + // Check if DPI awareness has already been set (e.g., via application manifest). + // Windows only allows setting DPI awareness once per process - either via manifest + // or API, not both. If already set, skip the API call to avoid "Access is denied" errors. + // See: https://github.com/wailsapp/wails/issues/4803 + if w32.HasGetProcessDpiAwarenessFunc() { + awareness, err := w32.GetProcessDpiAwareness() + if err == nil && awareness != w32.PROCESS_DPI_UNAWARE { + // DPI awareness already set (likely via manifest), skip API call + return nil + } + } + + if w32.HasSetProcessDpiAwarenessContextFunc() { + // This is most recent version with the best results + // supported beginning with Windows 10, version 1703 + return w32.SetProcessDpiAwarenessContext(w32.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) + } + + if w32.HasSetProcessDpiAwarenessFunc() { + // Supported beginning with Windows 8.1 + return w32.SetProcessDpiAwareness(w32.PROCESS_PER_MONITOR_DPI_AWARE) + } + + if w32.HasSetProcessDPIAwareFunc() { + // If none of the above is supported, fallback to SetProcessDPIAware + // which is supported beginning with Windows Vista + return w32.SetProcessDPIAware() + } + + return errors.New("no DPI awareness method supported") +} + +func newPlatformApp(app *App) *windowsApp { + + err := setupDPIAwareness() + if err != nil { + app.handleError(err) + } + + result := &windowsApp{ + parent: app, + instance: w32.GetModuleHandle(""), + windowMap: make(map[w32.HWND]*windowsWebviewWindow), + systrayMap: make(map[w32.HWND]*windowsSystemTray), + } + + err = result.processAndCacheScreens() + if err != nil { + app.handleFatalError(err) + } + + result.init() + result.initMainLoop() + + return result +} + +func (a *App) logPlatformInfo() { + var args []any + args = append(args, "Go-WebView2Loader", webviewloader.UsingGoWebview2Loader) + webviewVersion, err := webviewloader.GetAvailableCoreWebView2BrowserVersionString( + a.options.Windows.WebviewBrowserPath, + ) + if err != nil { + args = append(args, "WebView2", "Error: "+err.Error()) + } else { + args = append(args, "WebView2", webviewVersion) + } + + osInfo, _ := operatingsystem.Info() + args = append(args, osInfo.AsLogSlice()...) + + a.info("Platform Info:", args...) +} + +func (a *App) platformEnvironment() map[string]any { + result := map[string]any{} + webviewVersion, _ := webviewloader.GetAvailableCoreWebView2BrowserVersionString( + a.options.Windows.WebviewBrowserPath, + ) + result["Go-WebView2Loader"] = webviewloader.UsingGoWebview2Loader + result["WebView2"] = webviewVersion + return result +} + +func fatalHandler(errFunc func(error)) { + w32.Fatal = errFunc + return +} diff --git a/v3/pkg/application/assets/alpha/index.html b/v3/pkg/application/assets/alpha/index.html new file mode 100644 index 000000000..3c685de17 --- /dev/null +++ b/v3/pkg/application/assets/alpha/index.html @@ -0,0 +1,81 @@ + + + + + Wails Alpha + + + + + + +
        Alpha
        +
        +

        Documentation

        +

        Feedback

        +
        + + diff --git a/v3/pkg/application/bindings.go b/v3/pkg/application/bindings.go new file mode 100644 index 000000000..e16948399 --- /dev/null +++ b/v3/pkg/application/bindings.go @@ -0,0 +1,427 @@ +package application + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "reflect" + "runtime" + "strings" + + "github.com/wailsapp/wails/v3/internal/hash" + "github.com/wailsapp/wails/v3/internal/sliceutil" +) + +// CallOptions defines the options for a method call. +// Field order is optimized to minimize struct padding. +type CallOptions struct { + MethodName string `json:"methodName"` + Args []json.RawMessage `json:"args"` + MethodID uint32 `json:"methodID"` +} + +type ErrorKind string + +const ( + ReferenceError ErrorKind = "ReferenceError" + TypeError ErrorKind = "TypeError" + RuntimeError ErrorKind = "RuntimeError" +) + +// CallError represents an error that occurred during a method call. +// Field order is optimized to minimize struct padding. +type CallError struct { + Message string `json:"message"` + Cause any `json:"cause,omitempty"` + Kind ErrorKind `json:"kind"` +} + +func (e *CallError) Error() string { + return e.Message +} + +// Parameter defines a Go method parameter +type Parameter struct { + Name string `json:"name,omitempty"` + TypeName string `json:"type"` + ReflectType reflect.Type +} + +func newParameter(Name string, Type reflect.Type) *Parameter { + return &Parameter{ + Name: Name, + TypeName: Type.String(), + ReflectType: Type, + } +} + +// IsType returns true if the given +func (p *Parameter) IsType(typename string) bool { + return p.TypeName == typename +} + +// IsError returns true if the parameter type is an error +func (p *Parameter) IsError() bool { + return p.IsType("error") +} + +// BoundMethod defines all the data related to a Go method that is +// bound to the Wails application. +// Field order is optimized to minimize struct padding (136 bytes vs 144 bytes). +type BoundMethod struct { + Method reflect.Value `json:"-"` + Name string `json:"name"` + FQN string `json:"-"` + Comments string `json:"comments,omitempty"` + Inputs []*Parameter `json:"inputs,omitempty"` + Outputs []*Parameter `json:"outputs,omitempty"` + marshalError func(error) []byte + ID uint32 `json:"id"` + needsContext bool + isVariadic bool // cached at registration to avoid reflect call per invocation +} + +type Bindings struct { + marshalError func(error) []byte + boundMethods map[string]*BoundMethod + boundByID map[uint32]*BoundMethod + methodAliases map[uint32]uint32 +} + +func NewBindings(marshalError func(error) []byte, aliases map[uint32]uint32) *Bindings { + return &Bindings{ + marshalError: wrapErrorMarshaler(marshalError, defaultMarshalError), + boundMethods: make(map[string]*BoundMethod), + boundByID: make(map[uint32]*BoundMethod), + methodAliases: aliases, + } +} + +// Add adds the given service to the bindings. +func (b *Bindings) Add(service Service) error { + methods, err := getMethods(service.Instance()) + if err != nil { + return err + } + + marshalError := wrapErrorMarshaler(service.options.MarshalError, defaultMarshalError) + + // Validate and log methods. + for _, method := range methods { + if _, ok := b.boundMethods[method.FQN]; ok { + return fmt.Errorf("bound method '%s' is already registered. Please note that you can register at most one service of each type; additional instances must be wrapped in dedicated structs", method.FQN) + } + if boundMethod, ok := b.boundByID[method.ID]; ok { + return fmt.Errorf("oh wow, we're sorry about this! Amazingly, a hash collision was detected for method '%s' (it generates the same hash as '%s'). To use this method, please rename it. Sorry :(", method.FQN, boundMethod.FQN) + } + + // Log + attrs := []any{"fqn", method.FQN, "id", method.ID} + if alias, ok := sliceutil.FindMapKey(b.methodAliases, method.ID); ok { + attrs = append(attrs, "alias", alias) + } + globalApplication.debug("Registering bound method:", attrs...) + } + + for _, method := range methods { + // Store composite error marshaler + method.marshalError = marshalError + + // Register method + b.boundMethods[method.FQN] = method + b.boundByID[method.ID] = method + } + + return nil +} + +// Get returns the bound method with the given name +func (b *Bindings) Get(options *CallOptions) *BoundMethod { + return b.boundMethods[options.MethodName] +} + +// GetByID returns the bound method with the given ID +func (b *Bindings) GetByID(id uint32) *BoundMethod { + // Check method aliases + if b.methodAliases != nil { + if alias, ok := b.methodAliases[id]; ok { + id = alias + } + } + + return b.boundByID[id] +} + +// internalServiceMethod is a set of methods +// that are handled specially by the binding engine +// and must not be exposed to the frontend. +// +// For simplicity we exclude these by name +// without checking their signatures, +// and so does the binding generator. +var internalServiceMethods = map[string]bool{ + "ServiceName": true, + "ServiceStartup": true, + "ServiceShutdown": true, + "ServeHTTP": true, +} + +var ctxType = reflect.TypeFor[context.Context]() + +// getMethods returns the list of BoundMethod descriptors for the methods of the named pointer type provided by value. +// +// It returns an error if value is not a pointer to a named type, if a function value is supplied (binding functions is deprecated), or if a generic type is supplied. +// The returned BoundMethod slice includes only exported methods that are not listed in internalServiceMethods. Each BoundMethod has its FQN, ID (computed from the FQN), Method reflect.Value, Inputs and Outputs populated, isVariadic cached from the method signature, and needsContext set when the first parameter is context.Context. +func getMethods(value any) ([]*BoundMethod, error) { + // Create result placeholder + var result []*BoundMethod + + // Check type + if !isNamed(value) { + if isFunction(value) { + name := runtime.FuncForPC(reflect.ValueOf(value).Pointer()).Name() + return nil, fmt.Errorf("%s is a function, not a pointer to named type. Wails v2 has deprecated the binding of functions. Please define your functions as methods on a struct and bind a pointer to that struct", name) + } + + return nil, fmt.Errorf("%s is not a pointer to named type", reflect.ValueOf(value).Type().String()) + } else if !isPtr(value) { + return nil, fmt.Errorf("%s is a named type, not a pointer to named type", reflect.ValueOf(value).Type().String()) + } + + // Process Named Type + namedValue := reflect.ValueOf(value) + ptrType := namedValue.Type() + namedType := ptrType.Elem() + typeName := namedType.Name() + packagePath := namedType.PkgPath() + + if strings.Contains(namedType.String(), "[") { + return nil, fmt.Errorf("%s.%s is a generic type. Generic bound types are not supported", packagePath, namedType.String()) + } + + // Process Methods + for i := range ptrType.NumMethod() { + methodName := ptrType.Method(i).Name + method := namedValue.Method(i) + + if internalServiceMethods[methodName] { + continue + } + + fqn := fmt.Sprintf("%s.%s.%s", packagePath, typeName, methodName) + + // Iterate inputs + methodType := method.Type() + + // Create new method with cached flags + boundMethod := &BoundMethod{ + ID: hash.Fnv(fqn), + FQN: fqn, + Name: methodName, + Inputs: nil, + Outputs: nil, + Comments: "", + Method: method, + isVariadic: methodType.IsVariadic(), // cache to avoid reflect call per invocation + } + inputParamCount := methodType.NumIn() + var inputs []*Parameter + for inputIndex := 0; inputIndex < inputParamCount; inputIndex++ { + input := methodType.In(inputIndex) + if inputIndex == 0 && input.AssignableTo(ctxType) { + boundMethod.needsContext = true + } + thisParam := newParameter("", input) + inputs = append(inputs, thisParam) + } + + boundMethod.Inputs = inputs + + outputParamCount := methodType.NumOut() + var outputs []*Parameter + for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ { + output := methodType.Out(outputIndex) + thisParam := newParameter("", output) + outputs = append(outputs, thisParam) + } + boundMethod.Outputs = outputs + + // Save method in result + result = append(result, boundMethod) + + } + + return result, nil +} + +func (b *BoundMethod) String() string { + return b.FQN +} + +var errorType = reflect.TypeFor[error]() + +// Call will attempt to call this bound method with the given args. +// If the call succeeds, result will be either a non-error return value (if there is only one) +// or a slice of non-error return values (if there are more than one). +// +// If the arguments are mistyped or the call returns one or more non-nil error values, +// result is nil and err is an instance of *[CallError]. +func (b *BoundMethod) Call(ctx context.Context, args []json.RawMessage) (result any, err error) { + // Use a defer statement to capture panics + defer handlePanic(handlePanicOptions{skipEnd: 5}) + argCount := len(args) + if b.needsContext { + argCount++ + } + + if argCount != len(b.Inputs) { + err = &CallError{ + Message: fmt.Sprintf("%s expects %d arguments, got %d", b.FQN, len(b.Inputs), argCount), + Kind: TypeError, + } + return + } + + // Use stack-allocated buffer for common case (<=8 args), heap for larger + var argBuffer [8]reflect.Value + var callArgs []reflect.Value + if argCount <= len(argBuffer) { + callArgs = argBuffer[:argCount] + } else { + callArgs = make([]reflect.Value, argCount) + } + base := 0 + + if b.needsContext { + callArgs[0] = reflect.ValueOf(ctx) + base++ + } + + // Iterate over given arguments + for index, arg := range args { + value := reflect.New(b.Inputs[base+index].ReflectType) + err = json.Unmarshal(arg, value.Interface()) + if err != nil { + err = &CallError{ + Message: fmt.Sprintf("could not parse argument #%d: %s", index, err), + Cause: json.RawMessage(b.marshalError(err)), + Kind: TypeError, + } + return + } + callArgs[base+index] = value.Elem() + } + + // Do the call using cached isVariadic flag + var callResults []reflect.Value + if b.isVariadic { + callResults = b.Method.CallSlice(callArgs) + } else { + callResults = b.Method.Call(callArgs) + } + + // Process results - optimized for common case of 0-2 return values + // to avoid slice allocation + var firstResult any + var hasFirstResult bool + var nonErrorOutputs []any // only allocated if >1 non-error results + var errorOutputs []error + + for _, field := range callResults { + if field.Type() == errorType { + if field.IsNil() { + continue + } + if errorOutputs == nil { + errorOutputs = make([]error, 0, len(callResults)) + } + errorOutputs = append(errorOutputs, field.Interface().(error)) + } else if errorOutputs == nil { + // Only collect non-error outputs if no errors yet + val := field.Interface() + if !hasFirstResult { + firstResult = val + hasFirstResult = true + } else if nonErrorOutputs == nil { + // Second result - need to allocate slice + nonErrorOutputs = make([]any, 0, len(callResults)) + nonErrorOutputs = append(nonErrorOutputs, firstResult, val) + } else { + nonErrorOutputs = append(nonErrorOutputs, val) + } + } + } + + if len(errorOutputs) > 0 { + info := make([]json.RawMessage, len(errorOutputs)) + for i, err := range errorOutputs { + info[i] = b.marshalError(err) + } + + cerr := &CallError{ + Message: errors.Join(errorOutputs...).Error(), + Cause: info, + Kind: RuntimeError, + } + if len(info) == 1 { + cerr.Cause = info[0] + } + + err = cerr + } else if nonErrorOutputs != nil { + result = nonErrorOutputs + } else if hasFirstResult { + result = firstResult + } + + return +} + +// wrapErrorMarshaler returns an error marshaling functions +// that calls the primary marshaler first, +// then falls back to the secondary one. +func wrapErrorMarshaler(primary func(error) []byte, secondary func(error) []byte) func(error) []byte { + if primary == nil { + return secondary + } + + return func(err error) []byte { + result := primary(err) + if result == nil { + result = secondary(err) + } + + return result + } +} + +// defaultMarshalError implements the default error marshaling mechanism. +func defaultMarshalError(err error) []byte { + result, jsonErr := json.Marshal(&err) + if jsonErr != nil { + return nil + } + return result +} + +// isPtr returns true if the value given is a pointer. +func isPtr(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Ptr +} + +// isFunction returns true if the given value is a function +func isFunction(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Func +} + +// isNamed returns true if the given value is of named type +// or pointer to named type. +func isNamed(value interface{}) bool { + rv := reflect.ValueOf(value) + if rv.Kind() == reflect.Ptr { + rv = rv.Elem() + } + + return rv.Type().Name() != "" +} diff --git a/v3/pkg/application/bindings_bench_test.go b/v3/pkg/application/bindings_bench_test.go new file mode 100644 index 000000000..2e23cc92d --- /dev/null +++ b/v3/pkg/application/bindings_bench_test.go @@ -0,0 +1,506 @@ +//go:build bench + +package application_test + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + "github.com/wailsapp/wails/v3/internal/hash" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// BenchmarkService provides methods with varying complexity for benchmarking +type BenchmarkService struct{} + +func (s *BenchmarkService) NoArgs() {} + +func (s *BenchmarkService) StringArg(str string) string { + return str +} + +func (s *BenchmarkService) IntArg(i int) int { + return i +} + +func (s *BenchmarkService) MultipleArgs(s1 string, i int, b bool) (string, int, bool) { + return s1, i, b +} + +func (s *BenchmarkService) StructArg(p BenchPerson) BenchPerson { + return p +} + +func (s *BenchmarkService) ComplexStruct(c ComplexData) ComplexData { + return c +} + +func (s *BenchmarkService) SliceArg(items []int) []int { + return items +} + +func (s *BenchmarkService) VariadicArg(items ...string) []string { + return items +} + +func (s *BenchmarkService) WithContext(ctx context.Context, s1 string) string { + return s1 +} + +func (s *BenchmarkService) Method1() {} +func (s *BenchmarkService) Method2() {} +func (s *BenchmarkService) Method3() {} +func (s *BenchmarkService) Method4() {} +func (s *BenchmarkService) Method5() {} +func (s *BenchmarkService) Method6() {} +func (s *BenchmarkService) Method7() {} +func (s *BenchmarkService) Method8() {} +func (s *BenchmarkService) Method9() {} +func (s *BenchmarkService) Method10() {} + +type BenchPerson struct { + Name string `json:"name"` + Age int `json:"age"` + Email string `json:"email"` + Address string `json:"address"` +} + +type ComplexData struct { + ID int `json:"id"` + Name string `json:"name"` + Tags []string `json:"tags"` + Metadata map[string]interface{} `json:"metadata"` + Nested *NestedData `json:"nested"` +} + +type NestedData struct { + Value float64 `json:"value"` + Enabled bool `json:"enabled"` +} + +// Helper to create JSON args +func benchArgs(jsonArgs ...string) []json.RawMessage { + args := make([]json.RawMessage, len(jsonArgs)) + for i, j := range jsonArgs { + args[i] = json.RawMessage(j) + } + return args +} + +// BenchmarkMethodBinding measures the cost of registering services with varying method counts +func BenchmarkMethodBinding(b *testing.B) { + // Initialize global application (required for bindings) + _ = application.New(application.Options{}) + + b.Run("SingleService", func(b *testing.B) { + for b.Loop() { + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + } + }) + + b.Run("MultipleServices", func(b *testing.B) { + for b.Loop() { + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + _ = bindings.Add(application.NewService(&BenchPerson{})) // Will fail but tests the path + } + }) +} + +// BenchmarkMethodLookupByID measures method lookup by ID performance +func BenchmarkMethodLookupByID(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + // Get a valid method ID + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + methodID := method.ID + + b.ResetTimer() + for b.Loop() { + _ = bindings.GetByID(methodID) + } +} + +// BenchmarkMethodLookupByName measures method lookup by name performance +func BenchmarkMethodLookupByName(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.StringArg", + } + + b.ResetTimer() + for b.Loop() { + _ = bindings.Get(callOptions) + } +} + +// BenchmarkSimpleCall measures the cost of calling a method with a simple string argument +func BenchmarkSimpleCall(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := benchArgs(`"hello world"`) + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.Call(ctx, args) + } +} + +// BenchmarkComplexCall measures the cost of calling a method with a complex struct argument +func BenchmarkComplexCall(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.ComplexStruct", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + complexArg := `{ + "id": 12345, + "name": "Test Complex Data", + "tags": ["tag1", "tag2", "tag3", "tag4", "tag5"], + "metadata": {"key1": "value1", "key2": 42, "key3": true}, + "nested": {"value": 3.14159, "enabled": true} + }` + args := benchArgs(complexArg) + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.Call(ctx, args) + } +} + +// BenchmarkVariadicCall measures the cost of calling a variadic method +func BenchmarkVariadicCall(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.VariadicArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := benchArgs(`["one", "two", "three", "four", "five"]`) + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.Call(ctx, args) + } +} + +// BenchmarkCallWithContext measures the cost of calling a method that requires context +func BenchmarkCallWithContext(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.WithContext", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := benchArgs(`"context test"`) + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.Call(ctx, args) + } +} + +// BenchmarkJSONMarshalResult measures JSON marshaling overhead for results +func BenchmarkJSONMarshalResult(b *testing.B) { + person := BenchPerson{ + Name: "John Doe", + Age: 30, + Email: "john@example.com", + Address: "123 Main St, City, Country", + } + + b.Run("SimplePerson", func(b *testing.B) { + for b.Loop() { + _, _ = json.Marshal(person) + } + }) + + complex := ComplexData{ + ID: 12345, + Name: "Complex Test", + Tags: []string{"tag1", "tag2", "tag3", "tag4", "tag5"}, + Metadata: map[string]interface{}{ + "key1": "value1", + "key2": 42, + "key3": true, + }, + Nested: &NestedData{ + Value: 3.14159, + Enabled: true, + }, + } + + b.Run("ComplexData", func(b *testing.B) { + for b.Loop() { + _, _ = json.Marshal(complex) + } + }) +} + +// BenchmarkHashComputation measures the FNV hash computation used for method IDs +func BenchmarkHashComputation(b *testing.B) { + testCases := []struct { + name string + fqn string + }{ + {"Short", "pkg.Service.Method"}, + {"Medium", "github.com/user/project/pkg.Service.Method"}, + {"Long", "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.ComplexStruct"}, + } + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + for b.Loop() { + _ = hash.Fnv(tc.fqn) + } + }) + } +} + +// BenchmarkJSONUnmarshal measures JSON unmarshaling overhead for arguments +func BenchmarkJSONUnmarshal(b *testing.B) { + b.Run("String", func(b *testing.B) { + data := []byte(`"hello world"`) + for b.Loop() { + var s string + _ = json.Unmarshal(data, &s) + } + }) + + b.Run("Int", func(b *testing.B) { + data := []byte(`12345`) + for b.Loop() { + var i int + _ = json.Unmarshal(data, &i) + } + }) + + b.Run("Struct", func(b *testing.B) { + data := []byte(`{"name":"John","age":30,"email":"john@example.com","address":"123 Main St"}`) + for b.Loop() { + var p BenchPerson + _ = json.Unmarshal(data, &p) + } + }) + + b.Run("ComplexStruct", func(b *testing.B) { + data := []byte(`{"id":12345,"name":"Test","tags":["a","b","c"],"metadata":{"k":"v"},"nested":{"value":3.14,"enabled":true}}`) + for b.Loop() { + var c ComplexData + _ = json.Unmarshal(data, &c) + } + }) +} + +// BenchmarkMethodLookupWithAliases measures method lookup with alias resolution +func BenchmarkMethodLookupWithAliases(b *testing.B) { + _ = application.New(application.Options{}) + + // Create aliases map + aliases := make(map[uint32]uint32) + for i := uint32(0); i < 100; i++ { + aliases[i+1000] = i + } + + bindings := application.NewBindings(nil, aliases) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + b.Run("DirectLookup", func(b *testing.B) { + id := method.ID + for b.Loop() { + _ = bindings.GetByID(id) + } + }) + + b.Run("AliasLookup", func(b *testing.B) { + // Add an alias for this method + aliases[9999] = method.ID + for b.Loop() { + _ = bindings.GetByID(9999) + } + }) +} + +// BenchmarkReflectValueCall measures the overhead of reflect.Value.Call +func BenchmarkReflectValueCall(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + ctx := context.Background() + + b.Run("NoArgs", func(b *testing.B) { + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.NoArgs", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + args := benchArgs() + for b.Loop() { + _, _ = method.Call(ctx, args) + } + }) + + b.Run("MultipleArgs", func(b *testing.B) { + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.MultipleArgs", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + args := benchArgs(`"test"`, `42`, `true`) + for b.Loop() { + _, _ = method.Call(ctx, args) + } + }) +} + +// BenchmarkBindingsScaling measures how bindings performance scales with service count +func BenchmarkBindingsScaling(b *testing.B) { + _ = application.New(application.Options{}) + + // We can only add one service of each type, so we test lookup scaling + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + // Generate method names for lookup + methodNames := []string{ + "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.NoArgs", + "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.StringArg", + "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.IntArg", + "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.MultipleArgs", + "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.StructArg", + } + + b.Run("SequentialLookup", func(b *testing.B) { + for b.Loop() { + for _, name := range methodNames { + _ = bindings.Get(&application.CallOptions{MethodName: name}) + } + } + }) +} + +// BenchmarkCallErrorPath measures the cost of error handling in method calls +func BenchmarkCallErrorPath(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + ctx := context.Background() + + b.Run("WrongArgCount", func(b *testing.B) { + args := benchArgs() // No args when one is expected + for b.Loop() { + _, _ = method.Call(ctx, args) + } + }) + + b.Run("WrongArgType", func(b *testing.B) { + args := benchArgs(`123`) // Int when string is expected + for b.Loop() { + _, _ = method.Call(ctx, args) + } + }) +} + +// BenchmarkSliceArgSizes measures performance with varying slice sizes +func BenchmarkSliceArgSizes(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.SliceArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + ctx := context.Background() + + sizes := []int{1, 10, 100, 1000} + for _, size := range sizes { + b.Run(fmt.Sprintf("Size%d", size), func(b *testing.B) { + // Build slice JSON + slice := make([]int, size) + for i := range slice { + slice[i] = i + } + data, _ := json.Marshal(slice) + args := []json.RawMessage{data} + + b.ResetTimer() + for b.Loop() { + _, _ = method.Call(ctx, args) + } + }) + } +} diff --git a/v3/pkg/application/bindings_optimized_bench_test.go b/v3/pkg/application/bindings_optimized_bench_test.go new file mode 100644 index 000000000..1a9b137f5 --- /dev/null +++ b/v3/pkg/application/bindings_optimized_bench_test.go @@ -0,0 +1,469 @@ +//go:build bench + +package application + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "reflect" + "sync" + "testing" + + jsonv2 "github.com/go-json-experiment/json" +) + +// This file contains optimized versions of BoundMethod.Call for benchmarking. +// These demonstrate potential optimizations that could be applied. + +// Pools for reducing allocations +var ( + // Pool for []reflect.Value slices (sized for typical arg counts) + callArgsPool = sync.Pool{ + New: func() any { + // Pre-allocate for up to 8 args (covers vast majority of methods) + return make([]reflect.Value, 0, 8) + }, + } + + // Pool for []any slices + anySlicePool = sync.Pool{ + New: func() any { + return make([]any, 0, 4) + }, + } + + // Pool for CallError structs + callErrorPool = sync.Pool{ + New: func() any { + return &CallError{} + }, + } +) + +// CallOptimized is an optimized version of BoundMethod.Call that uses sync.Pool +func (b *BoundMethod) CallOptimized(ctx context.Context, args []json.RawMessage) (result any, err error) { + defer handlePanic(handlePanicOptions{skipEnd: 5}) + + argCount := len(args) + if b.needsContext { + argCount++ + } + + if argCount != len(b.Inputs) { + cerr := callErrorPool.Get().(*CallError) + cerr.Kind = TypeError + cerr.Message = fmt.Sprintf("%s expects %d arguments, got %d", b.FQN, len(b.Inputs), argCount) + cerr.Cause = nil + return nil, cerr + } + + // Get callArgs from pool + callArgs := callArgsPool.Get().([]reflect.Value) + callArgs = callArgs[:0] // Reset length but keep capacity + + // Ensure capacity + if cap(callArgs) < argCount { + callArgs = make([]reflect.Value, 0, argCount) + } + callArgs = callArgs[:argCount] + + base := 0 + if b.needsContext { + callArgs[0] = reflect.ValueOf(ctx) + base++ + } + + // Iterate over given arguments + for index, arg := range args { + value := reflect.New(b.Inputs[base+index].ReflectType) + err = json.Unmarshal(arg, value.Interface()) + if err != nil { + // Return callArgs to pool before returning error + callArgsPool.Put(callArgs[:0]) + + cerr := callErrorPool.Get().(*CallError) + cerr.Kind = TypeError + cerr.Message = fmt.Sprintf("could not parse argument #%d: %s", index, err) + cerr.Cause = json.RawMessage(b.marshalError(err)) + return nil, cerr + } + callArgs[base+index] = value.Elem() + } + + // Do the call - use cached isVariadic flag like production code + var callResults []reflect.Value + if b.isVariadic { + callResults = b.Method.CallSlice(callArgs) + } else { + callResults = b.Method.Call(callArgs) + } + + // Return callArgs to pool + callArgsPool.Put(callArgs[:0]) + + // Get output slice from pool + nonErrorOutputs := anySlicePool.Get().([]any) + nonErrorOutputs = nonErrorOutputs[:0] + defer func() { + anySlicePool.Put(nonErrorOutputs[:0]) + }() + + var errorOutputs []error + + for _, field := range callResults { + if field.Type() == errorType { + if field.IsNil() { + continue + } + if errorOutputs == nil { + errorOutputs = make([]error, 0, len(callResults)-len(nonErrorOutputs)) + nonErrorOutputs = nil + } + errorOutputs = append(errorOutputs, field.Interface().(error)) + } else if nonErrorOutputs != nil { + nonErrorOutputs = append(nonErrorOutputs, field.Interface()) + } + } + + if len(errorOutputs) > 0 { + info := make([]json.RawMessage, len(errorOutputs)) + for i, err := range errorOutputs { + info[i] = b.marshalError(err) + } + + cerr := &CallError{ + Kind: RuntimeError, + Message: errors.Join(errorOutputs...).Error(), + Cause: info, + } + if len(info) == 1 { + cerr.Cause = info[0] + } + return nil, cerr + } + + if len(nonErrorOutputs) == 1 { + result = nonErrorOutputs[0] + } else if len(nonErrorOutputs) > 1 { + // Need to copy since we're returning the pooled slice + resultSlice := make([]any, len(nonErrorOutputs)) + copy(resultSlice, nonErrorOutputs) + result = resultSlice + } + + return result, nil +} + +// Benchmark comparing original vs optimized Call +func BenchmarkCallOriginal(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{json.RawMessage(`"hello world"`)} + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.Call(ctx, args) + } +} + +func BenchmarkCallOptimized(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{json.RawMessage(`"hello world"`)} + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.CallOptimized(ctx, args) + } +} + +// benchService for internal tests +type benchService struct{} + +func (s *benchService) StringArg(str string) string { + return str +} + +func (s *benchService) MultipleArgs(s1 string, i int, b bool) (string, int, bool) { + return s1, i, b +} + +func BenchmarkCallOriginal_MultiArgs(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.MultipleArgs", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{ + json.RawMessage(`"test"`), + json.RawMessage(`42`), + json.RawMessage(`true`), + } + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.Call(ctx, args) + } +} + +func BenchmarkCallOptimized_MultiArgs(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.MultipleArgs", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{ + json.RawMessage(`"test"`), + json.RawMessage(`42`), + json.RawMessage(`true`), + } + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.CallOptimized(ctx, args) + } +} + +// CallWithJSONv2 uses the new JSON v2 library for unmarshaling +func (b *BoundMethod) CallWithJSONv2(ctx context.Context, args []json.RawMessage) (result any, err error) { + defer handlePanic(handlePanicOptions{skipEnd: 5}) + + argCount := len(args) + if b.needsContext { + argCount++ + } + + if argCount != len(b.Inputs) { + return nil, &CallError{ + Kind: TypeError, + Message: fmt.Sprintf("%s expects %d arguments, got %d", b.FQN, len(b.Inputs), argCount), + } + } + + // Convert inputs to values of appropriate type + callArgs := make([]reflect.Value, argCount) + base := 0 + + if b.needsContext { + callArgs[0] = reflect.ValueOf(ctx) + base++ + } + + // Iterate over given arguments - use JSON v2 for unmarshaling + for index, arg := range args { + value := reflect.New(b.Inputs[base+index].ReflectType) + err = jsonv2.Unmarshal(arg, value.Interface()) + if err != nil { + return nil, &CallError{ + Kind: TypeError, + Message: fmt.Sprintf("could not parse argument #%d: %s", index, err), + Cause: json.RawMessage(b.marshalError(err)), + } + } + callArgs[base+index] = value.Elem() + } + + // Do the call + var callResults []reflect.Value + if b.Method.Type().IsVariadic() { + callResults = b.Method.CallSlice(callArgs) + } else { + callResults = b.Method.Call(callArgs) + } + + var nonErrorOutputs = make([]any, 0, len(callResults)) + var errorOutputs []error + + for _, field := range callResults { + if field.Type() == errorType { + if field.IsNil() { + continue + } + if errorOutputs == nil { + errorOutputs = make([]error, 0, len(callResults)-len(nonErrorOutputs)) + nonErrorOutputs = nil + } + errorOutputs = append(errorOutputs, field.Interface().(error)) + } else if nonErrorOutputs != nil { + nonErrorOutputs = append(nonErrorOutputs, field.Interface()) + } + } + + if len(errorOutputs) > 0 { + info := make([]json.RawMessage, len(errorOutputs)) + for i, err := range errorOutputs { + info[i] = b.marshalError(err) + } + + cerr := &CallError{ + Kind: RuntimeError, + Message: errors.Join(errorOutputs...).Error(), + Cause: info, + } + if len(info) == 1 { + cerr.Cause = info[0] + } + return nil, cerr + } + + if len(nonErrorOutputs) == 1 { + result = nonErrorOutputs[0] + } else if len(nonErrorOutputs) > 1 { + result = nonErrorOutputs + } + + return result, nil +} + +func BenchmarkCallJSONv2(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{json.RawMessage(`"hello world"`)} + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.CallWithJSONv2(ctx, args) + } +} + +func BenchmarkCallJSONv2_MultiArgs(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.MultipleArgs", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{ + json.RawMessage(`"test"`), + json.RawMessage(`42`), + json.RawMessage(`true`), + } + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.CallWithJSONv2(ctx, args) + } +} + +// Concurrent benchmark to test pool effectiveness under load +func BenchmarkCallOriginal_Concurrent(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{json.RawMessage(`"hello world"`)} + ctx := context.Background() + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, _ = method.Call(ctx, args) + } + }) +} + +func BenchmarkCallOptimized_Concurrent(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{json.RawMessage(`"hello world"`)} + ctx := context.Background() + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, _ = method.CallOptimized(ctx, args) + } + }) +} diff --git a/v3/pkg/application/bindings_test.go b/v3/pkg/application/bindings_test.go new file mode 100644 index 000000000..d76a8efe0 --- /dev/null +++ b/v3/pkg/application/bindings_test.go @@ -0,0 +1,191 @@ +package application_test + +import ( + "context" + "encoding/json" + "errors" + "reflect" + "strings" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type TestService struct { +} + +type Person struct { + Name string `json:"name"` +} + +func (t *TestService) Nil() {} + +func (t *TestService) String(s string) string { + return s +} + +func (t *TestService) Multiple(s string, i int, b bool) (string, int, bool) { + return s, i, b +} + +func (t *TestService) Struct(p Person) Person { + return p +} + +func (t *TestService) StructNil(p Person) (Person, error) { + return p, nil +} + +func (t *TestService) StructError(p Person) (Person, error) { + return p, errors.New("error") +} + +func (t *TestService) Variadic(s ...string) []string { + return s +} + +func (t *TestService) PositionalAndVariadic(a int, _ ...string) int { + return a +} + +func (t *TestService) Slice(a []int) []int { + return a +} + +func newArgs(jsonArgs ...string) (args []json.RawMessage) { + for _, j := range jsonArgs { + args = append(args, json.RawMessage(j)) + } + return +} + +func TestBoundMethodCall(t *testing.T) { + tests := []struct { + name string + method string + args []json.RawMessage + err string + expected interface{} + }{ + { + name: "nil", + method: "Nil", + args: []json.RawMessage{}, + err: "", + expected: nil, + }, + { + name: "string", + method: "String", + args: newArgs(`"foo"`), + err: "", + expected: "foo", + }, + { + name: "multiple", + method: "Multiple", + args: newArgs(`"foo"`, "0", "false"), + err: "", + expected: []interface{}{"foo", 0, false}, + }, + { + name: "struct", + method: "Struct", + args: newArgs(`{ "name": "alice" }`), + err: "", + expected: Person{Name: "alice"}, + }, + { + name: "struct, nil error", + method: "StructNil", + args: newArgs(`{ "name": "alice" }`), + err: "", + expected: Person{Name: "alice"}, + }, + { + name: "struct, error", + method: "StructError", + args: newArgs(`{ "name": "alice" }`), + err: "error", + expected: nil, + }, + { + name: "invalid argument count", + method: "Multiple", + args: newArgs(`"foo"`), + err: "expects 3 arguments, got 1", + expected: nil, + }, + { + name: "invalid argument type", + method: "String", + args: newArgs("1"), + err: "could not parse", + expected: nil, + }, + { + name: "variadic, no arguments", + method: "Variadic", + args: newArgs(`[]`), // variadic parameters are passed as arrays + err: "", + expected: []string{}, + }, + { + name: "variadic", + method: "Variadic", + args: newArgs(`["foo", "bar"]`), + err: "", + expected: []string{"foo", "bar"}, + }, + { + name: "positional and variadic", + method: "PositionalAndVariadic", + args: newArgs("42", `[]`), + err: "", + expected: 42, + }, + { + name: "slice", + method: "Slice", + args: newArgs(`[1,2,3]`), + err: "", + expected: []int{1, 2, 3}, + }, + } + + // init globalApplication + _ = application.New(application.Options{}) + + bindings := application.NewBindings(nil, nil) + + err := bindings.Add(application.NewService(&TestService{})) + if err != nil { + t.Fatalf("bindings.Add() error = %v\n", err) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.TestService." + tt.method, + } + + method := bindings.Get(callOptions) + if method == nil { + t.Fatalf("bound method not found: %s", callOptions.MethodName) + } + + result, err := method.Call(context.TODO(), tt.args) + if (tt.err == "") != (err == nil) || (err != nil && !strings.Contains(err.Error(), tt.err)) { + expected := tt.err + if expected == "" { + expected = "nil" + } + t.Fatalf("error: %#v, expected error: %v", err, expected) + } + if !reflect.DeepEqual(result, tt.expected) { + t.Fatalf("result: %v, expected result: %v", result, tt.expected) + } + }) + } + +} diff --git a/v3/pkg/application/browser_manager.go b/v3/pkg/application/browser_manager.go new file mode 100644 index 000000000..3fe81720a --- /dev/null +++ b/v3/pkg/application/browser_manager.go @@ -0,0 +1,27 @@ +package application + +import ( + "github.com/pkg/browser" +) + +// BrowserManager manages browser-related operations +type BrowserManager struct { + app *App +} + +// newBrowserManager creates a new BrowserManager instance +func newBrowserManager(app *App) *BrowserManager { + return &BrowserManager{ + app: app, + } +} + +// OpenURL opens a URL in the default browser +func (bm *BrowserManager) OpenURL(url string) error { + return browser.OpenURL(url) +} + +// OpenFile opens a file in the default browser +func (bm *BrowserManager) OpenFile(path string) error { + return browser.OpenFile(path) +} diff --git a/v3/pkg/application/browser_window.go b/v3/pkg/application/browser_window.go new file mode 100644 index 000000000..5d3749db0 --- /dev/null +++ b/v3/pkg/application/browser_window.go @@ -0,0 +1,149 @@ +//go:build server + +package application + +import ( + "fmt" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +// BrowserWindow represents a browser client connection in server mode. +// It implements the Window interface so browser clients can be treated +// uniformly with native windows throughout the codebase. +type BrowserWindow struct { + id uint + name string + clientID string // The runtime's nanoid for this client +} + +// NewBrowserWindow creates a new browser window with the given ID. +func NewBrowserWindow(id uint, clientID string) *BrowserWindow { + return &BrowserWindow{ + id: id, + name: fmt.Sprintf("browser-%d", id), + clientID: clientID, + } +} + +// Core identification methods + +func (b *BrowserWindow) ID() uint { return b.id } +func (b *BrowserWindow) Name() string { return b.name } +func (b *BrowserWindow) ClientID() string { return b.clientID } + +// Event methods - these are meaningful for browser windows + +func (b *BrowserWindow) DispatchWailsEvent(event *CustomEvent) { + // Events are dispatched via WebSocket broadcast, not per-window +} + +func (b *BrowserWindow) EmitEvent(name string, data ...any) bool { + return globalApplication.Event.Emit(name, data...) +} + +// Logging methods + +func (b *BrowserWindow) Error(message string, args ...any) { + globalApplication.error(message, args...) +} + +func (b *BrowserWindow) Info(message string, args ...any) { + globalApplication.info(message, args...) +} + +// No-op methods - these don't apply to browser windows + +func (b *BrowserWindow) Center() {} +func (b *BrowserWindow) Close() {} +func (b *BrowserWindow) DisableSizeConstraints() {} +func (b *BrowserWindow) EnableSizeConstraints() {} +func (b *BrowserWindow) ExecJS(js string) {} +func (b *BrowserWindow) Focus() {} +func (b *BrowserWindow) ForceReload() {} +func (b *BrowserWindow) Fullscreen() Window { return b } +func (b *BrowserWindow) GetBorderSizes() *LRTB { return nil } +func (b *BrowserWindow) GetScreen() (*Screen, error) { return nil, nil } +func (b *BrowserWindow) GetZoom() float64 { return 1.0 } +func (b *BrowserWindow) handleDragAndDropMessage(filenames []string, dropTarget *DropTargetDetails) {} +func (b *BrowserWindow) InitiateFrontendDropProcessing(filenames []string, x int, y int) {} +func (b *BrowserWindow) HandleMessage(message string) {} +func (b *BrowserWindow) HandleWindowEvent(id uint) {} +func (b *BrowserWindow) Height() int { return 0 } +func (b *BrowserWindow) Hide() Window { return b } +func (b *BrowserWindow) HideMenuBar() {} +func (b *BrowserWindow) IsFocused() bool { return false } +func (b *BrowserWindow) IsFullscreen() bool { return false } +func (b *BrowserWindow) IsIgnoreMouseEvents() bool { return false } +func (b *BrowserWindow) IsMaximised() bool { return false } +func (b *BrowserWindow) IsMinimised() bool { return false } +func (b *BrowserWindow) HandleKeyEvent(acceleratorString string) {} +func (b *BrowserWindow) Maximise() Window { return b } +func (b *BrowserWindow) Minimise() Window { return b } +func (b *BrowserWindow) OnWindowEvent(eventType events.WindowEventType, callback func(event *WindowEvent)) func() { + return func() {} +} +func (b *BrowserWindow) OpenContextMenu(data *ContextMenuData) {} +func (b *BrowserWindow) Position() (int, int) { return 0, 0 } +func (b *BrowserWindow) RelativePosition() (int, int) { return 0, 0 } +func (b *BrowserWindow) Reload() {} +func (b *BrowserWindow) Resizable() bool { return false } +func (b *BrowserWindow) Restore() {} +func (b *BrowserWindow) Run() {} +func (b *BrowserWindow) SetPosition(x, y int) {} +func (b *BrowserWindow) SetAlwaysOnTop(b2 bool) Window { return b } +func (b *BrowserWindow) SetBackgroundColour(colour RGBA) Window { return b } +func (b *BrowserWindow) SetFrameless(frameless bool) Window { return b } +func (b *BrowserWindow) SetHTML(html string) Window { return b } +func (b *BrowserWindow) SetMinimiseButtonState(state ButtonState) Window { return b } +func (b *BrowserWindow) SetMaximiseButtonState(state ButtonState) Window { return b } +func (b *BrowserWindow) SetCloseButtonState(state ButtonState) Window { return b } +func (b *BrowserWindow) SetMaxSize(maxWidth, maxHeight int) Window { return b } +func (b *BrowserWindow) SetMinSize(minWidth, minHeight int) Window { return b } +func (b *BrowserWindow) SetRelativePosition(x, y int) Window { return b } +func (b *BrowserWindow) SetResizable(b2 bool) Window { return b } +func (b *BrowserWindow) SetIgnoreMouseEvents(ignore bool) Window { return b } +func (b *BrowserWindow) SetSize(width, height int) Window { return b } +func (b *BrowserWindow) SetTitle(title string) Window { return b } +func (b *BrowserWindow) SetURL(s string) Window { return b } +func (b *BrowserWindow) SetZoom(magnification float64) Window { return b } +func (b *BrowserWindow) Show() Window { return b } +func (b *BrowserWindow) ShowMenuBar() {} +func (b *BrowserWindow) Size() (width int, height int) { return 0, 0 } +func (b *BrowserWindow) OpenDevTools() {} +func (b *BrowserWindow) ToggleFullscreen() {} +func (b *BrowserWindow) ToggleMaximise() {} +func (b *BrowserWindow) ToggleMenuBar() {} +func (b *BrowserWindow) ToggleFrameless() {} +func (b *BrowserWindow) UnFullscreen() {} +func (b *BrowserWindow) UnMaximise() {} +func (b *BrowserWindow) UnMinimise() {} +func (b *BrowserWindow) Width() int { return 0 } +func (b *BrowserWindow) IsVisible() bool { return true } +func (b *BrowserWindow) Bounds() Rect { return Rect{} } +func (b *BrowserWindow) SetBounds(bounds Rect) {} +func (b *BrowserWindow) Zoom() {} +func (b *BrowserWindow) ZoomIn() {} +func (b *BrowserWindow) ZoomOut() {} +func (b *BrowserWindow) ZoomReset() Window { return b } +func (b *BrowserWindow) SetMenu(menu *Menu) {} +func (b *BrowserWindow) SnapAssist() {} +func (b *BrowserWindow) SetContentProtection(protection bool) Window { return b } +func (b *BrowserWindow) NativeWindow() unsafe.Pointer { return nil } +func (b *BrowserWindow) SetEnabled(enabled bool) {} +func (b *BrowserWindow) Flash(enabled bool) {} +func (b *BrowserWindow) Print() error { return nil } +func (b *BrowserWindow) RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func() { + return func() {} +} +func (b *BrowserWindow) shouldUnconditionallyClose() bool { return true } + +// Editing methods +func (b *BrowserWindow) cut() {} +func (b *BrowserWindow) copy() {} +func (b *BrowserWindow) paste() {} +func (b *BrowserWindow) undo() {} +func (b *BrowserWindow) redo() {} +func (b *BrowserWindow) delete() {} +func (b *BrowserWindow) selectAll() {} diff --git a/v3/pkg/application/clipboard.go b/v3/pkg/application/clipboard.go new file mode 100644 index 000000000..f21b597e2 --- /dev/null +++ b/v3/pkg/application/clipboard.go @@ -0,0 +1,26 @@ +package application + +type clipboardImpl interface { + setText(text string) bool + text() (string, bool) +} + +type Clipboard struct { + impl clipboardImpl +} + +func newClipboard() *Clipboard { + return &Clipboard{ + impl: newClipboardImpl(), + } +} + +func (c *Clipboard) SetText(text string) bool { + return InvokeSyncWithResult(func() bool { + return c.impl.setText(text) + }) +} + +func (c *Clipboard) Text() (string, bool) { + return InvokeSyncWithResultAndOther(c.impl.text) +} diff --git a/v3/pkg/application/clipboard_android.go b/v3/pkg/application/clipboard_android.go new file mode 100644 index 000000000..4c44eaa1f --- /dev/null +++ b/v3/pkg/application/clipboard_android.go @@ -0,0 +1,35 @@ +//go:build android + +package application + +type androidClipboardImpl struct{} + +func newClipboardImpl() clipboardImpl { + return &androidClipboardImpl{} +} + +func (c *androidClipboardImpl) setText(text string) bool { + // Android clipboard implementation would go here + // TODO: Implement via JNI to Android ClipboardManager + return true +} + +func (c *androidClipboardImpl) text() (string, bool) { + // Android clipboard implementation would go here + // TODO: Implement via JNI to Android ClipboardManager + return "", false +} + +// SetClipboardText sets the clipboard text on Android +func (c *ClipboardManager) SetClipboardText(text string) error { + // Android clipboard implementation would go here + // For now, return nil as a placeholder + return nil +} + +// GetClipboardText gets the clipboard text on Android +func (c *ClipboardManager) GetClipboardText() (string, error) { + // Android clipboard implementation would go here + // For now, return empty string + return "", nil +} diff --git a/v3/pkg/application/clipboard_darwin.go b/v3/pkg/application/clipboard_darwin.go new file mode 100644 index 000000000..545305913 --- /dev/null +++ b/v3/pkg/application/clipboard_darwin.go @@ -0,0 +1,56 @@ +//go:build darwin && !ios + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13 + +#import +#import + +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, bool) { + clipboardLock.RLock() + defer clipboardLock.RUnlock() + clipboardText := C.getClipboardText() + result := C.GoString(clipboardText) + return result, true +} + +func newClipboardImpl() *macosClipboard { + return &macosClipboard{} +} diff --git a/v3/pkg/application/clipboard_ios.go b/v3/pkg/application/clipboard_ios.go new file mode 100644 index 000000000..e887474c6 --- /dev/null +++ b/v3/pkg/application/clipboard_ios.go @@ -0,0 +1,33 @@ +//go:build ios + +package application + +type iosClipboardImpl struct{} + +func newClipboardImpl() clipboardImpl { + return &iosClipboardImpl{} +} + +func (c *iosClipboardImpl) setText(text string) bool { + // iOS clipboard implementation would go here + return true +} + +func (c *iosClipboardImpl) text() (string, bool) { + // iOS clipboard implementation would go here + return "", false +} + +// SetClipboardText sets the clipboard text on iOS +func (c *ClipboardManager) SetClipboardText(text string) error { + // iOS clipboard implementation would go here + // For now, return nil as a placeholder + return nil +} + +// GetClipboardText gets the clipboard text on iOS +func (c *ClipboardManager) GetClipboardText() (string, error) { + // iOS clipboard implementation would go here + // For now, return empty string + return "", nil +} diff --git a/v3/pkg/application/clipboard_linux.go b/v3/pkg/application/clipboard_linux.go new file mode 100644 index 000000000..cf1366605 --- /dev/null +++ b/v3/pkg/application/clipboard_linux.go @@ -0,0 +1,28 @@ +//go:build linux && !android && !server + +package application + +import ( + "sync" +) + +var clipboardLock sync.RWMutex + +type linuxClipboard struct{} + +func (m linuxClipboard) setText(text string) bool { + clipboardLock.Lock() + defer clipboardLock.Unlock() + clipboardSet(text) + return true +} + +func (m linuxClipboard) text() (string, bool) { + clipboardLock.RLock() + defer clipboardLock.RUnlock() + return clipboardGet(), true +} + +func newClipboardImpl() *linuxClipboard { + return &linuxClipboard{} +} diff --git a/v3/pkg/application/clipboard_manager.go b/v3/pkg/application/clipboard_manager.go new file mode 100644 index 000000000..abd0b19c1 --- /dev/null +++ b/v3/pkg/application/clipboard_manager.go @@ -0,0 +1,32 @@ +package application + +// ClipboardManager manages clipboard operations +type ClipboardManager struct { + app *App + clipboard *Clipboard +} + +// newClipboardManager creates a new ClipboardManager instance +func newClipboardManager(app *App) *ClipboardManager { + return &ClipboardManager{ + app: app, + } +} + +// SetText sets text in the clipboard +func (cm *ClipboardManager) SetText(text string) bool { + return cm.getClipboard().SetText(text) +} + +// Text gets text from the clipboard +func (cm *ClipboardManager) Text() (string, bool) { + return cm.getClipboard().Text() +} + +// getClipboard returns the clipboard instance, creating it if needed (lazy initialization) +func (cm *ClipboardManager) getClipboard() *Clipboard { + if cm.clipboard == nil { + cm.clipboard = newClipboard() + } + return cm.clipboard +} diff --git a/v3/pkg/application/clipboard_windows.go b/v3/pkg/application/clipboard_windows.go new file mode 100644 index 000000000..507eac3da --- /dev/null +++ b/v3/pkg/application/clipboard_windows.go @@ -0,0 +1,29 @@ +//go:build windows + +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/w32" + "sync" +) + +type windowsClipboard struct { + lock sync.RWMutex +} + +func (m *windowsClipboard) setText(text string) bool { + m.lock.Lock() + defer m.lock.Unlock() + return w32.SetClipboardText(text) == nil +} + +func (m *windowsClipboard) text() (string, bool) { + m.lock.Lock() + defer m.lock.Unlock() + text, err := w32.GetClipboardText() + return text, err == nil +} + +func newClipboardImpl() *windowsClipboard { + return &windowsClipboard{} +} diff --git a/v3/pkg/application/context.go b/v3/pkg/application/context.go new file mode 100644 index 000000000..8ed2cb792 --- /dev/null +++ b/v3/pkg/application/context.go @@ -0,0 +1,62 @@ +package application + +type Context struct { + // contains filtered or unexported fields + data map[string]any +} + +func newContext() *Context { + return &Context{ + data: make(map[string]any), + } +} + +const ( + clickedMenuItem string = "clickedMenuItem" + menuItemIsChecked string = "menuItemIsChecked" + contextMenuData string = "contextMenuData" +) + +func (c *Context) ClickedMenuItem() *MenuItem { + result, exists := c.data[clickedMenuItem] + if !exists { + return nil + } + return result.(*MenuItem) +} + +func (c *Context) IsChecked() bool { + result, exists := c.data[menuItemIsChecked] + if !exists { + return false + } + return result.(bool) +} +func (c *Context) ContextMenuData() string { + result := c.data[contextMenuData] + if result == nil { + return "" + } + str, ok := result.(string) + if !ok { + return "" + } + return str +} + +func (c *Context) withClickedMenuItem(menuItem *MenuItem) *Context { + c.data[clickedMenuItem] = menuItem + return c +} + +func (c *Context) withChecked(checked bool) { + c.data[menuItemIsChecked] = checked +} + +func (c *Context) withContextMenuData(data *ContextMenuData) *Context { + if data == nil { + return c + } + c.data[contextMenuData] = data.Data + return c +} diff --git a/v3/pkg/application/context_application_event.go b/v3/pkg/application/context_application_event.go new file mode 100644 index 000000000..32f392455 --- /dev/null +++ b/v3/pkg/application/context_application_event.go @@ -0,0 +1,106 @@ +package application + +import "log" + +var blankApplicationEventContext = &ApplicationEventContext{} + +const ( + CONTEXT_OPENED_FILES = "openedFiles" + CONTEXT_FILENAME = "filename" + CONTEXT_URL = "url" +) + +// ApplicationEventContext is the context of an application event +type ApplicationEventContext struct { + // contains filtered or unexported fields + data map[string]any +} + +// OpenedFiles returns the opened files from the event context if it was set +func (c ApplicationEventContext) OpenedFiles() []string { + files, ok := c.data[CONTEXT_OPENED_FILES] + if !ok { + return nil + } + result, ok := files.([]string) + if !ok { + return nil + } + return result +} + +func (c ApplicationEventContext) setOpenedFiles(files []string) { + c.data[CONTEXT_OPENED_FILES] = files +} + +func (c ApplicationEventContext) setIsDarkMode(mode bool) { + c.data["isDarkMode"] = mode +} + +func (c ApplicationEventContext) getBool(key string) bool { + mode, ok := c.data[key] + if !ok { + return false + } + result, ok := mode.(bool) + if !ok { + return false + } + return result +} + +// IsDarkMode returns true if the event context has a dark mode +func (c ApplicationEventContext) IsDarkMode() bool { + return c.getBool("isDarkMode") +} + +// HasVisibleWindows returns true if the event context has a visible window +func (c ApplicationEventContext) HasVisibleWindows() bool { + return c.getBool("hasVisibleWindows") +} + +func (c *ApplicationEventContext) setData(data map[string]any) { + c.data = data +} + +func (c *ApplicationEventContext) setOpenedWithFile(filepath string) { + c.data[CONTEXT_FILENAME] = filepath +} + +func (c *ApplicationEventContext) setURL(openedWithURL string) { + c.data[CONTEXT_URL] = openedWithURL +} + +// Filename returns the filename from the event context if it was set +func (c ApplicationEventContext) Filename() string { + filename, ok := c.data[CONTEXT_FILENAME] + if !ok { + return "" + } + result, ok := filename.(string) + if !ok { + return "" + } + return result +} + +// URL returns the URL from the event context if it was set +func (c ApplicationEventContext) URL() string { + url, ok := c.data[CONTEXT_URL] + if !ok { + log.Println("URL not found in event context") + return "" + } + result, ok := url.(string) + if !ok { + log.Println("URL not a string in event context") + return "" + } + return result +} + +func newApplicationEventContext() *ApplicationEventContext { + return &ApplicationEventContext{ + data: make(map[string]any), + } +} diff --git a/v3/pkg/application/context_menu_manager.go b/v3/pkg/application/context_menu_manager.go new file mode 100644 index 000000000..669e8aca5 --- /dev/null +++ b/v3/pkg/application/context_menu_manager.go @@ -0,0 +1,54 @@ +package application + +// ContextMenuManager manages all context menu operations +type ContextMenuManager struct { + app *App +} + +// newContextMenuManager creates a new ContextMenuManager instance +func newContextMenuManager(app *App) *ContextMenuManager { + return &ContextMenuManager{ + app: app, + } +} + +// New creates a new context menu +func (cmm *ContextMenuManager) New() *ContextMenu { + return &ContextMenu{ + Menu: NewMenu(), + } +} + +// Add adds a context menu (replaces Register for consistency) +func (cmm *ContextMenuManager) Add(name string, menu *ContextMenu) { + cmm.app.contextMenusLock.Lock() + defer cmm.app.contextMenusLock.Unlock() + cmm.app.contextMenus[name] = menu +} + +// Remove removes a context menu by name (replaces Unregister for consistency) +func (cmm *ContextMenuManager) Remove(name string) { + cmm.app.contextMenusLock.Lock() + defer cmm.app.contextMenusLock.Unlock() + delete(cmm.app.contextMenus, name) +} + +// Get retrieves a context menu by name +func (cmm *ContextMenuManager) Get(name string) (*ContextMenu, bool) { + cmm.app.contextMenusLock.RLock() + defer cmm.app.contextMenusLock.RUnlock() + menu, exists := cmm.app.contextMenus[name] + return menu, exists +} + +// GetAll returns all registered context menus as a slice +func (cmm *ContextMenuManager) GetAll() []*ContextMenu { + cmm.app.contextMenusLock.RLock() + defer cmm.app.contextMenusLock.RUnlock() + + result := make([]*ContextMenu, 0, len(cmm.app.contextMenus)) + for _, menu := range cmm.app.contextMenus { + result = append(result, menu) + } + return result +} diff --git a/v3/pkg/application/context_test.go b/v3/pkg/application/context_test.go new file mode 100644 index 000000000..268ca5af9 --- /dev/null +++ b/v3/pkg/application/context_test.go @@ -0,0 +1,120 @@ +package application + +import ( + "testing" +) + +func TestNewContext(t *testing.T) { + ctx := newContext() + if ctx == nil { + t.Fatal("newContext() returned nil") + } + if ctx.data == nil { + t.Error("newContext() should initialize data map") + } +} + +func TestContext_ClickedMenuItem_NotExists(t *testing.T) { + ctx := newContext() + result := ctx.ClickedMenuItem() + if result != nil { + t.Error("ClickedMenuItem() should return nil when not set") + } +} + +func TestContext_ClickedMenuItem_Exists(t *testing.T) { + ctx := newContext() + menuItem := &MenuItem{label: "Test"} + ctx.withClickedMenuItem(menuItem) + + result := ctx.ClickedMenuItem() + if result == nil { + t.Fatal("ClickedMenuItem() should return the menu item") + } + if result != menuItem { + t.Error("ClickedMenuItem() should return the same menu item") + } +} + +func TestContext_IsChecked_NotSet(t *testing.T) { + ctx := newContext() + if ctx.IsChecked() { + t.Error("IsChecked() should return false when not set") + } +} + +func TestContext_IsChecked_True(t *testing.T) { + ctx := newContext() + ctx.withChecked(true) + if !ctx.IsChecked() { + t.Error("IsChecked() should return true when set to true") + } +} + +func TestContext_IsChecked_False(t *testing.T) { + ctx := newContext() + ctx.withChecked(false) + if ctx.IsChecked() { + t.Error("IsChecked() should return false when set to false") + } +} + +func TestContext_ContextMenuData_Empty(t *testing.T) { + ctx := newContext() + result := ctx.ContextMenuData() + if result != "" { + t.Errorf("ContextMenuData() should return empty string when not set, got %q", result) + } +} + +func TestContext_ContextMenuData_Exists(t *testing.T) { + ctx := newContext() + data := &ContextMenuData{Data: "test-data"} + ctx.withContextMenuData(data) + + result := ctx.ContextMenuData() + if result != "test-data" { + t.Errorf("ContextMenuData() = %q, want %q", result, "test-data") + } +} + +func TestContext_ContextMenuData_NilData(t *testing.T) { + ctx := newContext() + ctx.withContextMenuData(nil) + + result := ctx.ContextMenuData() + if result != "" { + t.Errorf("ContextMenuData() should return empty string for nil data, got %q", result) + } +} + +func TestContext_ContextMenuData_WrongType(t *testing.T) { + ctx := newContext() + // Manually set wrong type to test type assertion + ctx.data[contextMenuData] = 123 + + result := ctx.ContextMenuData() + if result != "" { + t.Errorf("ContextMenuData() should return empty string for non-string type, got %q", result) + } +} + +func TestContext_WithClickedMenuItem_Chaining(t *testing.T) { + ctx := newContext() + menuItem := &MenuItem{label: "Test"} + + returnedCtx := ctx.withClickedMenuItem(menuItem) + if returnedCtx != ctx { + t.Error("withClickedMenuItem should return the same context for chaining") + } +} + +func TestContext_WithContextMenuData_Chaining(t *testing.T) { + ctx := newContext() + data := &ContextMenuData{Data: "test"} + + returnedCtx := ctx.withContextMenuData(data) + if returnedCtx != ctx { + t.Error("withContextMenuData should return the same context for chaining") + } +} diff --git a/v3/pkg/application/context_window_event.go b/v3/pkg/application/context_window_event.go new file mode 100644 index 000000000..ad4a97a8a --- /dev/null +++ b/v3/pkg/application/context_window_event.go @@ -0,0 +1,79 @@ +package application + +var blankWindowEventContext = &WindowEventContext{} + +const ( + droppedFiles = "droppedFiles" + dropTargetDetailsKey = "dropTargetDetails" +) + +type WindowEventContext struct { + // contains filtered or unexported fields + data map[string]any +} + +func (c WindowEventContext) DroppedFiles() []string { + if c.data == nil { + c.data = make(map[string]any) + } + files, ok := c.data[droppedFiles] + if !ok { + return nil + } + result, ok := files.([]string) + if !ok { + return nil + } + return result +} + +func (c WindowEventContext) setDroppedFiles(files []string) { + if c.data == nil { + c.data = make(map[string]any) + } + c.data[droppedFiles] = files +} + +func (c WindowEventContext) setCoordinates(x, y int) { + if c.data == nil { + c.data = make(map[string]any) + } + c.data["x"] = x + c.data["y"] = y +} + +func (c WindowEventContext) setDropTargetDetails(details *DropTargetDetails) { + if c.data == nil { + c.data = make(map[string]any) + } + if details == nil { + c.data[dropTargetDetailsKey] = nil + return + } + c.data[dropTargetDetailsKey] = details +} + +// DropTargetDetails retrieves information about the drop target element. +func (c WindowEventContext) DropTargetDetails() *DropTargetDetails { + if c.data == nil { + c.data = make(map[string]any) + } + details, ok := c.data[dropTargetDetailsKey] + if !ok { + return nil + } + if details == nil { + return nil + } + result, ok := details.(*DropTargetDetails) + if !ok { + return nil + } + return result +} + +func newWindowEventContext() *WindowEventContext { + return &WindowEventContext{ + data: make(map[string]any), + } +} diff --git a/v3/pkg/application/dialog_manager.go b/v3/pkg/application/dialog_manager.go new file mode 100644 index 000000000..36e5842de --- /dev/null +++ b/v3/pkg/application/dialog_manager.go @@ -0,0 +1,57 @@ +package application + +// DialogManager manages dialog-related operations +type DialogManager struct { + app *App +} + +// newDialogManager creates a new DialogManager instance +func newDialogManager(app *App) *DialogManager { + return &DialogManager{ + app: app, + } +} + +// OpenFile creates a file dialog for selecting files +func (dm *DialogManager) OpenFile() *OpenFileDialogStruct { + return newOpenFileDialog() +} + +// OpenFileWithOptions creates a file dialog with options +func (dm *DialogManager) OpenFileWithOptions(options *OpenFileDialogOptions) *OpenFileDialogStruct { + result := newOpenFileDialog() + result.SetOptions(options) + return result +} + +// SaveFile creates a save file dialog +func (dm *DialogManager) SaveFile() *SaveFileDialogStruct { + return newSaveFileDialog() +} + +// SaveFileWithOptions creates a save file dialog with options +func (dm *DialogManager) SaveFileWithOptions(options *SaveFileDialogOptions) *SaveFileDialogStruct { + result := newSaveFileDialog() + result.SetOptions(options) + return result +} + +// Info creates an information dialog +func (dm *DialogManager) Info() *MessageDialog { + return newMessageDialog(InfoDialogType) +} + +// Question creates a question dialog +func (dm *DialogManager) Question() *MessageDialog { + return newMessageDialog(QuestionDialogType) +} + +// Warning creates a warning dialog +func (dm *DialogManager) Warning() *MessageDialog { + return newMessageDialog(WarningDialogType) +} + +// Error creates an error dialog +func (dm *DialogManager) Error() *MessageDialog { + return newMessageDialog(ErrorDialogType) +} diff --git a/v3/pkg/application/dialogs.go b/v3/pkg/application/dialogs.go new file mode 100644 index 000000000..d61f64886 --- /dev/null +++ b/v3/pkg/application/dialogs.go @@ -0,0 +1,494 @@ +package application + +import ( + "strings" + "sync" +) + +type DialogType int + +var dialogMapID = make(map[uint]struct{}) +var dialogIDLock sync.RWMutex + +func getDialogID() uint { + dialogIDLock.Lock() + defer dialogIDLock.Unlock() + var dialogID uint + for { + if _, ok := dialogMapID[dialogID]; !ok { + dialogMapID[dialogID] = struct{}{} + break + } + dialogID++ + if dialogID == 0 { + panic("no more dialog IDs") + } + } + return dialogID +} + +func freeDialogID(id uint) { + dialogIDLock.Lock() + defer dialogIDLock.Unlock() + delete(dialogMapID, id) +} + +var openFileResponses = make(map[uint]chan string) +var saveFileResponses = make(map[uint]chan string) + +const ( + InfoDialogType DialogType = iota + QuestionDialogType + WarningDialogType + ErrorDialogType +) + +type Button struct { + Label string + IsCancel bool + IsDefault bool + Callback func() +} + +func (b *Button) OnClick(callback func()) *Button { + b.Callback = callback + return b +} + +func (b *Button) SetAsDefault() *Button { + b.IsDefault = true + return b +} + +func (b *Button) SetAsCancel() *Button { + b.IsCancel = true + return b +} + +type messageDialogImpl interface { + show() +} + +type MessageDialogOptions struct { + DialogType DialogType + Title string + Message string + Buttons []*Button + Icon []byte + window Window +} + +type MessageDialog struct { + MessageDialogOptions + + // platform independent + impl messageDialogImpl +} + +var defaultTitles = map[DialogType]string{ + InfoDialogType: "Information", + QuestionDialogType: "Question", + WarningDialogType: "Warning", + ErrorDialogType: "Error", +} + +func newMessageDialog(dialogType DialogType) *MessageDialog { + return &MessageDialog{ + MessageDialogOptions: MessageDialogOptions{ + DialogType: dialogType, + }, + impl: nil, + } +} + +func (d *MessageDialog) SetTitle(title string) *MessageDialog { + d.Title = title + return d +} + +func (d *MessageDialog) Show() { + if d.impl == nil { + d.impl = newDialogImpl(d) + } + InvokeSync(d.impl.show) +} + +func (d *MessageDialog) SetIcon(icon []byte) *MessageDialog { + d.Icon = icon + return d +} + +func (d *MessageDialog) AddButton(s string) *Button { + result := &Button{ + Label: s, + } + d.Buttons = append(d.Buttons, result) + return result +} + +func (d *MessageDialog) AddButtons(buttons []*Button) *MessageDialog { + d.Buttons = buttons + return d +} + +func (d *MessageDialog) AttachToWindow(window Window) *MessageDialog { + d.window = window + return d +} + +func (d *MessageDialog) SetDefaultButton(button *Button) *MessageDialog { + for _, b := range d.Buttons { + b.IsDefault = false + } + button.IsDefault = true + return d +} + +func (d *MessageDialog) SetCancelButton(button *Button) *MessageDialog { + for _, b := range d.Buttons { + b.IsCancel = false + } + button.IsCancel = true + return d +} + +func (d *MessageDialog) SetMessage(message string) *MessageDialog { + d.Message = message + return d +} + +type openFileDialogImpl interface { + show() (chan string, error) +} + +type FileFilter struct { + DisplayName string // Filter information EG: "Image Files (*.jpg, *.png)" + Pattern string // semicolon separated list of extensions, EG: "*.jpg;*.png" +} + +type OpenFileDialogOptions struct { + CanChooseDirectories bool + CanChooseFiles bool + CanCreateDirectories bool + ShowHiddenFiles bool + ResolvesAliases bool + AllowsMultipleSelection bool + HideExtension bool + CanSelectHiddenExtension bool + TreatsFilePackagesAsDirectories bool + AllowsOtherFileTypes bool + Filters []FileFilter + Window Window + + Title string + Message string + ButtonText string + Directory string +} + +type OpenFileDialogStruct struct { + id uint + canChooseDirectories bool + canChooseFiles bool + canCreateDirectories bool + showHiddenFiles bool + resolvesAliases bool + allowsMultipleSelection bool + hideExtension bool + canSelectHiddenExtension bool + treatsFilePackagesAsDirectories bool + allowsOtherFileTypes bool + filters []FileFilter + + title string + message string + buttonText string + directory string + window Window + + impl openFileDialogImpl +} + +func (d *OpenFileDialogStruct) CanChooseFiles(canChooseFiles bool) *OpenFileDialogStruct { + d.canChooseFiles = canChooseFiles + return d +} + +func (d *OpenFileDialogStruct) CanChooseDirectories(canChooseDirectories bool) *OpenFileDialogStruct { + d.canChooseDirectories = canChooseDirectories + return d +} + +func (d *OpenFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *OpenFileDialogStruct { + d.canCreateDirectories = canCreateDirectories + return d +} + +func (d *OpenFileDialogStruct) AllowsOtherFileTypes(allowsOtherFileTypes bool) *OpenFileDialogStruct { + d.allowsOtherFileTypes = allowsOtherFileTypes + return d +} + +func (d *OpenFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *OpenFileDialogStruct { + d.showHiddenFiles = showHiddenFiles + return d +} + +func (d *OpenFileDialogStruct) HideExtension(hideExtension bool) *OpenFileDialogStruct { + d.hideExtension = hideExtension + return d +} + +func (d *OpenFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *OpenFileDialogStruct { + d.treatsFilePackagesAsDirectories = treatsFilePackagesAsDirectories + return d +} + +func (d *OpenFileDialogStruct) AttachToWindow(window Window) *OpenFileDialogStruct { + d.window = window + return d +} + +func (d *OpenFileDialogStruct) ResolvesAliases(resolvesAliases bool) *OpenFileDialogStruct { + d.resolvesAliases = resolvesAliases + return d +} + +func (d *OpenFileDialogStruct) SetTitle(title string) *OpenFileDialogStruct { + d.title = title + return d +} + +func (d *OpenFileDialogStruct) PromptForSingleSelection() (string, error) { + d.allowsMultipleSelection = false + if d.impl == nil { + d.impl = newOpenFileDialogImpl(d) + } + + var result string + selections, err := InvokeSyncWithResultAndError(d.impl.show) + if err == nil { + result = <-selections + } + + return result, err +} + +// AddFilter adds a filter to the dialog. The filter is a display name and a semicolon separated list of extensions. +// EG: AddFilter("Image Files", "*.jpg;*.png") +func (d *OpenFileDialogStruct) AddFilter(displayName, pattern string) *OpenFileDialogStruct { + d.filters = append(d.filters, FileFilter{ + DisplayName: strings.TrimSpace(displayName), + Pattern: strings.TrimSpace(pattern), + }) + return d +} + +func (d *OpenFileDialogStruct) PromptForMultipleSelection() ([]string, error) { + d.allowsMultipleSelection = true + if d.impl == nil { + d.impl = newOpenFileDialogImpl(d) + } + + selections, err := InvokeSyncWithResultAndError(d.impl.show) + if err != nil { + return nil, err + } + + var result []string + for filename := range selections { + result = append(result, filename) + } + + return result, err +} + +func (d *OpenFileDialogStruct) SetMessage(message string) *OpenFileDialogStruct { + d.message = message + return d +} + +func (d *OpenFileDialogStruct) SetButtonText(text string) *OpenFileDialogStruct { + d.buttonText = text + return d +} + +func (d *OpenFileDialogStruct) SetDirectory(directory string) *OpenFileDialogStruct { + d.directory = directory + return d +} + +func (d *OpenFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *OpenFileDialogStruct { + d.canSelectHiddenExtension = canSelectHiddenExtension + return d +} + +func (d *OpenFileDialogStruct) SetOptions(options *OpenFileDialogOptions) { + d.title = options.Title + d.message = options.Message + d.buttonText = options.ButtonText + d.directory = options.Directory + d.canChooseDirectories = options.CanChooseDirectories + d.canChooseFiles = options.CanChooseFiles + d.canCreateDirectories = options.CanCreateDirectories + d.showHiddenFiles = options.ShowHiddenFiles + d.resolvesAliases = options.ResolvesAliases + d.allowsMultipleSelection = options.AllowsMultipleSelection + d.hideExtension = options.HideExtension + d.canSelectHiddenExtension = options.CanSelectHiddenExtension + d.treatsFilePackagesAsDirectories = options.TreatsFilePackagesAsDirectories + d.allowsOtherFileTypes = options.AllowsOtherFileTypes + d.filters = options.Filters + d.window = options.Window +} + +func newOpenFileDialog() *OpenFileDialogStruct { + return &OpenFileDialogStruct{ + id: getDialogID(), + canChooseDirectories: false, + canChooseFiles: true, + canCreateDirectories: true, + resolvesAliases: false, + } +} + +func newSaveFileDialog() *SaveFileDialogStruct { + return &SaveFileDialogStruct{ + id: getDialogID(), + canCreateDirectories: true, + } +} + +type SaveFileDialogOptions struct { + CanCreateDirectories bool + ShowHiddenFiles bool + CanSelectHiddenExtension bool + AllowOtherFileTypes bool + HideExtension bool + TreatsFilePackagesAsDirectories bool + Title string + Message string + Directory string + Filename string + ButtonText string + Filters []FileFilter + Window Window +} + +type SaveFileDialogStruct struct { + id uint + canCreateDirectories bool + showHiddenFiles bool + canSelectHiddenExtension bool + allowOtherFileTypes bool + hideExtension bool + treatsFilePackagesAsDirectories bool + message string + directory string + filename string + buttonText string + filters []FileFilter + + window Window + + impl saveFileDialogImpl + title string +} + +type saveFileDialogImpl interface { + show() (chan string, error) +} + +func (d *SaveFileDialogStruct) SetOptions(options *SaveFileDialogOptions) { + d.title = options.Title + d.canCreateDirectories = options.CanCreateDirectories + d.showHiddenFiles = options.ShowHiddenFiles + d.canSelectHiddenExtension = options.CanSelectHiddenExtension + d.allowOtherFileTypes = options.AllowOtherFileTypes + d.hideExtension = options.HideExtension + d.treatsFilePackagesAsDirectories = options.TreatsFilePackagesAsDirectories + d.message = options.Message + d.directory = options.Directory + d.filename = options.Filename + d.buttonText = options.ButtonText + d.filters = options.Filters + d.window = options.Window +} + +// AddFilter adds a filter to the dialog. The filter is a display name and a semicolon separated list of extensions. +// EG: AddFilter("Image Files", "*.jpg;*.png") +func (d *SaveFileDialogStruct) AddFilter(displayName, pattern string) *SaveFileDialogStruct { + d.filters = append(d.filters, FileFilter{ + DisplayName: strings.TrimSpace(displayName), + Pattern: strings.TrimSpace(pattern), + }) + return d +} + +func (d *SaveFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *SaveFileDialogStruct { + d.canCreateDirectories = canCreateDirectories + return d +} + +func (d *SaveFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *SaveFileDialogStruct { + d.canSelectHiddenExtension = canSelectHiddenExtension + return d +} + +func (d *SaveFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *SaveFileDialogStruct { + d.showHiddenFiles = showHiddenFiles + return d +} + +func (d *SaveFileDialogStruct) SetMessage(message string) *SaveFileDialogStruct { + d.message = message + return d +} + +func (d *SaveFileDialogStruct) SetDirectory(directory string) *SaveFileDialogStruct { + d.directory = directory + return d +} + +func (d *SaveFileDialogStruct) AttachToWindow(window Window) *SaveFileDialogStruct { + d.window = window + return d +} + +func (d *SaveFileDialogStruct) PromptForSingleSelection() (string, error) { + if d.impl == nil { + d.impl = newSaveFileDialogImpl(d) + } + + var result string + selections, err := InvokeSyncWithResultAndError(d.impl.show) + if err == nil { + result = <-selections + } + return result, err +} + +func (d *SaveFileDialogStruct) SetButtonText(text string) *SaveFileDialogStruct { + d.buttonText = text + return d +} + +func (d *SaveFileDialogStruct) SetFilename(filename string) *SaveFileDialogStruct { + d.filename = filename + return d +} + +func (d *SaveFileDialogStruct) AllowsOtherFileTypes(allowOtherFileTypes bool) *SaveFileDialogStruct { + d.allowOtherFileTypes = allowOtherFileTypes + return d +} + +func (d *SaveFileDialogStruct) HideExtension(hideExtension bool) *SaveFileDialogStruct { + d.hideExtension = hideExtension + return d +} + +func (d *SaveFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *SaveFileDialogStruct { + d.treatsFilePackagesAsDirectories = treatsFilePackagesAsDirectories + return d +} diff --git a/v3/pkg/application/dialogs_android.go b/v3/pkg/application/dialogs_android.go new file mode 100644 index 000000000..a962dfba0 --- /dev/null +++ b/v3/pkg/application/dialogs_android.go @@ -0,0 +1,90 @@ +//go:build android + +package application + +// dialogsImpl implements dialogs for Android +type dialogsImpl struct { + // Android-specific fields if needed +} + +func newDialogsImpl() *dialogsImpl { + return &dialogsImpl{} +} + +// Android dialog implementations would use AlertDialog +// These are placeholder implementations for now + +func (d *dialogsImpl) info(id uint, param MessageDialogOptions) { + // TODO: Implement using AlertDialog +} + +func (d *dialogsImpl) warning(id uint, param MessageDialogOptions) { + // TODO: Implement using AlertDialog +} + +func (d *dialogsImpl) error(id uint, param MessageDialogOptions) { + // TODO: Implement using AlertDialog +} + +func (d *dialogsImpl) question(id uint, param MessageDialogOptions) chan bool { + // TODO: Implement using AlertDialog + ch := make(chan bool, 1) + ch <- false + return ch +} + +func (d *dialogsImpl) openFile(id uint, param OpenFileDialogOptions) chan string { + // TODO: Implement using Android file picker intent + ch := make(chan string, 1) + ch <- "" + return ch +} + +func (d *dialogsImpl) openMultipleFiles(id uint, param OpenFileDialogOptions) chan []string { + // TODO: Implement using Android file picker intent + ch := make(chan []string, 1) + ch <- []string{} + return ch +} + +func (d *dialogsImpl) openDirectory(id uint, param OpenFileDialogOptions) chan string { + // TODO: Implement using Android file picker intent + ch := make(chan string, 1) + ch <- "" + return ch +} + +func (d *dialogsImpl) saveFile(id uint, param SaveFileDialogOptions) chan string { + // TODO: Implement using Android file picker intent + ch := make(chan string, 1) + ch <- "" + return ch +} + +type androidDialog struct { + dialog *MessageDialog +} + +func (d *androidDialog) show() { + // TODO: Implement using AlertDialog +} + +func newDialogImpl(d *MessageDialog) *androidDialog { + return &androidDialog{ + dialog: d, + } +} + +func (d *dialogsImpl) show() (chan string, error) { + ch := make(chan string, 1) + ch <- "" + return ch, nil +} + +func newOpenFileDialogImpl(_ *OpenFileDialogStruct) openFileDialogImpl { + return &dialogsImpl{} +} + +func newSaveFileDialogImpl(_ *SaveFileDialogStruct) saveFileDialogImpl { + return &dialogsImpl{} +} diff --git a/v3/pkg/application/dialogs_darwin.go b/v3/pkg/application/dialogs_darwin.go new file mode 100644 index 000000000..5ed6f4ab6 --- /dev/null +++ b/v3/pkg/application/dialogs_darwin.go @@ -0,0 +1,588 @@ +//go:build darwin && !ios + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13 -framework UniformTypeIdentifiers + +#import + +#import +#import "dialogs_darwin_delegate.h" + +extern void openFileDialogCallback(uint id, char* path); +extern void openFileDialogCallbackEnd(uint id); +extern void saveFileDialogCallback(uint id, char* path); +extern void dialogCallback(int id, int buttonPressed); + +static void showAboutBox(char* title, char *message, void *icon, int length) { + + // run on main thread + NSAlert *alert = [[NSAlert alloc] init]; + if (title != NULL) { + [alert setMessageText:[NSString stringWithUTF8String:title]]; + free(title); + } + if (message != NULL) { + [alert setInformativeText:[NSString stringWithUTF8String:message]]; + free(message); + } + if (icon != NULL) { + NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:icon length:length]]; + [alert setIcon:image]; + } + [alert setAlertStyle:NSAlertStyleInformational]; + [alert runModal]; +} + + +// Create an NSAlert +static void* createAlert(int alertType, char* title, char *message, void *icon, int length) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setAlertStyle:alertType]; + if (title != NULL) { + [alert setMessageText:[NSString stringWithUTF8String:title]]; + free(title); + } + if (message != NULL) { + [alert setInformativeText:[NSString stringWithUTF8String:message]]; + free(message); + } + if (icon != NULL) { + NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:icon length:length]]; + [alert setIcon:image]; + } else { + if(alertType == NSAlertStyleCritical || alertType == NSAlertStyleWarning) { + NSImage *image = [NSImage imageNamed:NSImageNameCaution]; + [alert setIcon:image]; + } else { + NSImage *image = [NSImage imageNamed:NSImageNameInfo]; + [alert setIcon:image]; + } + } + return alert; + +} + +static int getButtonNumber(NSModalResponse response) { + int buttonNumber = 0; + if( response == NSAlertFirstButtonReturn ) { + buttonNumber = 0; + } + else if( response == NSAlertSecondButtonReturn ) { + buttonNumber = 1; + } + else if( response == NSAlertThirdButtonReturn ) { + buttonNumber = 2; + } else { + buttonNumber = 3; + } + return buttonNumber; +} + +// Run the dialog +static void dialogRunModal(void *dialog, void *parent, int callBackID) { + NSAlert *alert = (__bridge NSAlert *)dialog; + + // If the parent is NULL, we are running a modal dialog, otherwise attach the alert to the parent + if( parent == NULL ) { + NSModalResponse response = [alert runModal]; + int returnCode = getButtonNumber(response); + dialogCallback(callBackID, returnCode); + } else { + NSWindow *window = (__bridge NSWindow *)parent; + [alert beginSheetModalForWindow:window completionHandler:^(NSModalResponse response) { + int returnCode = getButtonNumber(response); + dialogCallback(callBackID, returnCode); + }]; + } +} + +// Release the dialog +static void releaseDialog(void *dialog) { + NSAlert *alert = (__bridge NSAlert *)dialog; + [alert release]; +} + +// Add a button to the dialog +static void alertAddButton(void *dialog, char *label, bool isDefault, bool isCancel) { + NSAlert *alert = (__bridge NSAlert *)dialog; + NSButton *button = [alert addButtonWithTitle:[NSString stringWithUTF8String:label]]; + free(label); + if( isDefault ) { + [button setKeyEquivalent:@"\r"]; + } else if( isCancel ) { + [button setKeyEquivalent:@"\033"]; + } else { + [button setKeyEquivalent:@""]; + } +} + +static void processOpenFileDialogResults(NSOpenPanel *panel, NSInteger result, uint dialogID) { + const char *path = NULL; + if (result == NSModalResponseOK) { + NSArray *urls = [panel URLs]; + if ([urls count] > 0) { + NSArray *urls = [panel URLs]; + for (NSURL *url in urls) { + path = [[url path] UTF8String]; + openFileDialogCallback(dialogID, (char *)path); + } + } else { + NSURL *url = [panel URL]; + path = [[url path] UTF8String]; + openFileDialogCallback(dialogID, (char *)path); + } + } + openFileDialogCallbackEnd(dialogID); +} + + +static void showOpenFileDialog(unsigned int dialogID, + bool canChooseFiles, + bool canChooseDirectories, + bool canCreateDirectories, + bool showHiddenFiles, + bool allowsMultipleSelection, + bool resolvesAliases, + bool hideExtension, + bool treatsFilePackagesAsDirectories, + bool allowsOtherFileTypes, + char *filterPatterns, + unsigned int filterPatternsCount, + char* message, + char* directory, + char* buttonText, + + void *window) { + + // run on main thread + NSOpenPanel *panel = [NSOpenPanel openPanel]; + + // print out filterPatterns if length > 0 + if (filterPatternsCount > 0) { + OpenPanelDelegate *delegate = [[OpenPanelDelegate alloc] init]; + [panel setDelegate:delegate]; + // Initialise NSString with bytes and UTF8 encoding + NSString *filterPatternsString = [[NSString alloc] initWithBytes:filterPatterns length:filterPatternsCount encoding:NSUTF8StringEncoding]; + // Convert NSString to NSArray + delegate.allowedExtensions = [filterPatternsString componentsSeparatedByString:@";"]; + + // Use UTType if macOS 11 or higher to add file filters +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 + if (@available(macOS 11, *)) { + NSMutableArray *filterTypes = [NSMutableArray array]; + // Iterate the filtertypes, create uti's that are limited to the file extensions then add + for (NSString *filterType in delegate.allowedExtensions) { + [filterTypes addObject:[UTType typeWithFilenameExtension:filterType]]; + } + [panel setAllowedContentTypes:filterTypes]; + } +#else + [panel setAllowedFileTypes:delegate.allowedExtensions]; +#endif + + // Free the memory + free(filterPatterns); + } + + + if (message != NULL) { + [panel setMessage:[NSString stringWithUTF8String:message]]; + free(message); + } + + if (directory != NULL) { + [panel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:directory]]]; + free(directory); + } + + if (buttonText != NULL) { + [panel setPrompt:[NSString stringWithUTF8String:buttonText]]; + free(buttonText); + } + + [panel setCanChooseFiles:canChooseFiles]; + [panel setCanChooseDirectories:canChooseDirectories]; + [panel setCanCreateDirectories:canCreateDirectories]; + [panel setShowsHiddenFiles:showHiddenFiles]; + [panel setAllowsMultipleSelection:allowsMultipleSelection]; + [panel setResolvesAliases:resolvesAliases]; + [panel setExtensionHidden:hideExtension]; + [panel setTreatsFilePackagesAsDirectories:treatsFilePackagesAsDirectories]; + [panel setAllowsOtherFileTypes:allowsOtherFileTypes]; + + + + if (window != NULL) { + [panel beginSheetModalForWindow:(__bridge NSWindow *)window completionHandler:^(NSInteger result) { + processOpenFileDialogResults(panel, result, dialogID); + }]; + } else { + [panel beginWithCompletionHandler:^(NSInteger result) { + processOpenFileDialogResults(panel, result, dialogID); + }]; + } +} + +static void showSaveFileDialog(unsigned int dialogID, + bool canCreateDirectories, + bool showHiddenFiles, + bool canSelectHiddenExtension, + bool hideExtension, + bool treatsFilePackagesAsDirectories, + bool allowOtherFileTypes, + char* message, + char* directory, + char* buttonText, + char* filename, + void *window) { + + NSSavePanel *panel = [NSSavePanel savePanel]; + + if (message != NULL) { + [panel setMessage:[NSString stringWithUTF8String:message]]; + free(message); + } + + if (directory != NULL) { + [panel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:directory]]]; + free(directory); + } + + if (filename != NULL) { + [panel setNameFieldStringValue:[NSString stringWithUTF8String:filename]]; + free(filename); + } + + if (buttonText != NULL) { + [panel setPrompt:[NSString stringWithUTF8String:buttonText]]; + free(buttonText); + } + + [panel setCanCreateDirectories:canCreateDirectories]; + [panel setShowsHiddenFiles:showHiddenFiles]; + [panel setCanSelectHiddenExtension:canSelectHiddenExtension]; + [panel setExtensionHidden:hideExtension]; + [panel setTreatsFilePackagesAsDirectories:treatsFilePackagesAsDirectories]; + [panel setAllowsOtherFileTypes:allowOtherFileTypes]; + + if (window != NULL) { + [panel beginSheetModalForWindow:(__bridge NSWindow *)window completionHandler:^(NSInteger result) { + const char *path = NULL; + if (result == NSModalResponseOK) { + NSURL *url = [panel URL]; + path = [[url path] UTF8String]; + } + saveFileDialogCallback(dialogID, (char *)path); + }]; + } else { + [panel beginWithCompletionHandler:^(NSInteger result) { + const char *path = NULL; + if (result == NSModalResponseOK) { + NSURL *url = [panel URL]; + path = [[url path] UTF8String]; + } + saveFileDialogCallback(dialogID, (char *)path); + }]; + } +} + +*/ +import "C" +import ( + "strings" + "sync" + "unsafe" +) + +const NSAlertStyleWarning = C.int(0) +const NSAlertStyleInformational = C.int(1) +const NSAlertStyleCritical = C.int(2) + +var alertTypeMap = map[DialogType]C.int{ + WarningDialogType: NSAlertStyleWarning, + InfoDialogType: NSAlertStyleInformational, + ErrorDialogType: NSAlertStyleCritical, + QuestionDialogType: NSAlertStyleInformational, +} + +type dialogResultCallback func(int) + +var ( + callbacks = make(map[int]dialogResultCallback) + mutex = &sync.Mutex{} +) + +func addDialogCallback(callback dialogResultCallback) int { + mutex.Lock() + defer mutex.Unlock() + + // Find the first free integer key + var id int + for { + if _, exists := callbacks[id]; !exists { + break + } + id++ + } + + // Save the function in the map using the integer key + callbacks[id] = callback + + // Return the key + return id +} + +func removeDialogCallback(id int) { + mutex.Lock() + defer mutex.Unlock() + delete(callbacks, id) +} + +//export dialogCallback +func dialogCallback(id C.int, buttonPressed C.int) { + mutex.Lock() + callback, exists := callbacks[int(id)] + mutex.Unlock() + + if !exists { + return + } + + // Call the function with the button number + callback(int(buttonPressed)) // Replace nil with the actual slice of buttons +} + +func (m *macosApp) showAboutDialog(title string, message string, icon []byte) { + var iconData unsafe.Pointer + if icon != nil { + iconData = unsafe.Pointer(&icon[0]) + } + InvokeAsync(func() { + C.showAboutBox(C.CString(title), C.CString(message), iconData, C.int(len(icon))) + }) +} + +type macosDialog struct { + dialog *MessageDialog + + nsDialog unsafe.Pointer +} + +func (m *macosDialog) show() { + InvokeAsync(func() { + + // Mac can only have 4 Buttons on a dialog + if len(m.dialog.Buttons) > 4 { + m.dialog.Buttons = m.dialog.Buttons[:4] + } + + if m.nsDialog != nil { + C.releaseDialog(m.nsDialog) + } + var title *C.char + if m.dialog.Title != "" { + title = C.CString(m.dialog.Title) + } + var message *C.char + if m.dialog.Message != "" { + message = C.CString(m.dialog.Message) + } + var iconData unsafe.Pointer + var iconLength C.int + if len(m.dialog.Icon) > 0 { + iconData = unsafe.Pointer(&m.dialog.Icon[0]) + iconLength = C.int(len(m.dialog.Icon)) + } else { + // if it's an error, use the application Icon + if m.dialog.DialogType == ErrorDialogType { + if len(globalApplication.options.Icon) > 0 { + iconData = unsafe.Pointer(&globalApplication.options.Icon[0]) + iconLength = C.int(len(globalApplication.options.Icon)) + } + } + } + var parent unsafe.Pointer + if m.dialog.window != nil { + // get NSWindow from window + parent = m.dialog.window.NativeWindow() + } + + alertType, ok := alertTypeMap[m.dialog.DialogType] + if !ok { + alertType = C.NSAlertStyleInformational + } + + m.nsDialog = C.createAlert(alertType, title, message, iconData, iconLength) + + // Reverse the Buttons so that the default is on the right + reversedButtons := make([]*Button, len(m.dialog.Buttons)) + var count = 0 + for i := len(m.dialog.Buttons) - 1; i >= 0; i-- { + button := m.dialog.Buttons[i] + C.alertAddButton(m.nsDialog, C.CString(button.Label), C.bool(button.IsDefault), C.bool(button.IsCancel)) + reversedButtons[count] = m.dialog.Buttons[i] + count++ + } + + var callBackID int + callBackID = addDialogCallback(func(buttonPressed int) { + if len(m.dialog.Buttons) > buttonPressed { + button := reversedButtons[buttonPressed] + if button.Callback != nil { + button.Callback() + } + } + removeDialogCallback(callBackID) + }) + + C.dialogRunModal(m.nsDialog, parent, C.int(callBackID)) + + }) + +} + +func newDialogImpl(d *MessageDialog) *macosDialog { + return &macosDialog{ + dialog: d, + } +} + +type macosOpenFileDialog struct { + dialog *OpenFileDialogStruct +} + +func newOpenFileDialogImpl(d *OpenFileDialogStruct) *macosOpenFileDialog { + return &macosOpenFileDialog{ + dialog: d, + } +} + +func toCString(s string) *C.char { + if s == "" { + return nil + } + return C.CString(s) +} + +func (m *macosOpenFileDialog) show() (chan string, error) { + openFileResponses[m.dialog.id] = make(chan string) + nsWindow := unsafe.Pointer(nil) + if m.dialog.window != nil { + // get NSWindow from window + nsWindow = m.dialog.window.NativeWindow() + } + + // Massage filter patterns into macOS format + // We iterate all filter patterns, tidy them up and then join them with a semicolon + // This should produce a single string of extensions like "png;jpg;gif" + var filterPatterns string + if len(m.dialog.filters) > 0 { + var allPatterns []string + for _, filter := range m.dialog.filters { + patternComponents := strings.Split(filter.Pattern, ";") + for i, component := range patternComponents { + filterPattern := strings.TrimSpace(component) + filterPattern = strings.TrimPrefix(filterPattern, "*.") + patternComponents[i] = filterPattern + } + allPatterns = append(allPatterns, strings.Join(patternComponents, ";")) + } + filterPatterns = strings.Join(allPatterns, ";") + } + C.showOpenFileDialog(C.uint(m.dialog.id), + C.bool(m.dialog.canChooseFiles), + C.bool(m.dialog.canChooseDirectories), + C.bool(m.dialog.canCreateDirectories), + C.bool(m.dialog.showHiddenFiles), + C.bool(m.dialog.allowsMultipleSelection), + C.bool(m.dialog.resolvesAliases), + C.bool(m.dialog.hideExtension), + C.bool(m.dialog.treatsFilePackagesAsDirectories), + C.bool(m.dialog.allowsOtherFileTypes), + toCString(filterPatterns), + C.uint(len(filterPatterns)), + toCString(m.dialog.message), + toCString(m.dialog.directory), + toCString(m.dialog.buttonText), + nsWindow) + + return openFileResponses[m.dialog.id], nil +} + +//export openFileDialogCallback +func openFileDialogCallback(cid C.uint, cpath *C.char) { + path := C.GoString(cpath) + id := uint(cid) + channel, ok := openFileResponses[id] + if ok { + channel <- path + } else { + panic("No channel found for open file dialog") + } +} + +//export openFileDialogCallbackEnd +func openFileDialogCallbackEnd(cid C.uint) { + id := uint(cid) + channel, ok := openFileResponses[id] + if ok { + close(channel) + delete(openFileResponses, id) + freeDialogID(id) + } else { + panic("No channel found for open file dialog") + } +} + +type macosSaveFileDialog struct { + dialog *SaveFileDialogStruct +} + +func newSaveFileDialogImpl(d *SaveFileDialogStruct) *macosSaveFileDialog { + return &macosSaveFileDialog{ + dialog: d, + } +} + +func (m *macosSaveFileDialog) show() (chan string, error) { + saveFileResponses[m.dialog.id] = make(chan string) + nsWindow := unsafe.Pointer(nil) + if m.dialog.window != nil { + // get NSWindow from window + nsWindow = m.dialog.window.NativeWindow() + } + C.showSaveFileDialog(C.uint(m.dialog.id), + C.bool(m.dialog.canCreateDirectories), + C.bool(m.dialog.showHiddenFiles), + C.bool(m.dialog.canSelectHiddenExtension), + C.bool(m.dialog.hideExtension), + C.bool(m.dialog.treatsFilePackagesAsDirectories), + C.bool(m.dialog.allowOtherFileTypes), + toCString(m.dialog.message), + toCString(m.dialog.directory), + toCString(m.dialog.buttonText), + toCString(m.dialog.filename), + nsWindow) + return saveFileResponses[m.dialog.id], nil +} + +//export saveFileDialogCallback +func saveFileDialogCallback(cid C.uint, cpath *C.char) { + // Covert the path to a string + path := C.GoString(cpath) + id := uint(cid) + // put response on channel + channel, ok := saveFileResponses[id] + if ok { + channel <- path + close(channel) + delete(saveFileResponses, id) + freeDialogID(id) + + } else { + panic("No channel found for save file dialog") + } +} diff --git a/v3/pkg/application/dialogs_darwin_delegate.h b/v3/pkg/application/dialogs_darwin_delegate.h new file mode 100644 index 000000000..7674b3725 --- /dev/null +++ b/v3/pkg/application/dialogs_darwin_delegate.h @@ -0,0 +1,18 @@ +//go:build darwin && !ios + +#ifndef _DIALOGS_DELEGATE_H_ +#define _DIALOGS_DELEGATE_H_ + +#import + +// Conditionally import UniformTypeIdentifiers based on OS version +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 +#import +#endif + +// OpenPanel delegate to handle file filtering +@interface OpenPanelDelegate : NSObject +@property (nonatomic, strong) NSArray *allowedExtensions; +@end + +#endif \ No newline at end of file diff --git a/v3/pkg/application/dialogs_darwin_delegate.m b/v3/pkg/application/dialogs_darwin_delegate.m new file mode 100644 index 000000000..1f26b5507 --- /dev/null +++ b/v3/pkg/application/dialogs_darwin_delegate.m @@ -0,0 +1,38 @@ +//go:build darwin && !ios + +#import "dialogs_darwin_delegate.h" + +// Override shouldEnableURL +@implementation OpenPanelDelegate +- (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url { + if (url == nil) { + return NO; + } + + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL isDirectory = NO; + if ([fileManager fileExistsAtPath:url.path isDirectory:&isDirectory] && isDirectory) { + return YES; + } + + // If no extensions specified, allow all files + if (self.allowedExtensions == nil || [self.allowedExtensions count] == 0) { + return YES; + } + + NSString *extension = [url.pathExtension lowercaseString]; + if (extension == nil || [extension isEqualToString:@""]) { + return NO; + } + + // Check if the extension is in our allowed list (case insensitive) + for (NSString *allowedExt in self.allowedExtensions) { + if ([[allowedExt lowercaseString] isEqualToString:extension]) { + return YES; + } + } + + return NO; +} + +@end diff --git a/v3/pkg/application/dialogs_ios.go b/v3/pkg/application/dialogs_ios.go new file mode 100644 index 000000000..5caeed71e --- /dev/null +++ b/v3/pkg/application/dialogs_ios.go @@ -0,0 +1,90 @@ +//go:build ios + +package application + +// dialogsImpl implements dialogs for iOS +type dialogsImpl struct { + // iOS-specific fields if needed +} + +func newDialogsImpl() *dialogsImpl { + return &dialogsImpl{} +} + +// iOS dialog implementations would use UIAlertController +// These are placeholder implementations for now + +func (d *dialogsImpl) info(id uint, param MessageDialogOptions) { + // TODO: Implement using UIAlertController +} + +func (d *dialogsImpl) warning(id uint, param MessageDialogOptions) { + // TODO: Implement using UIAlertController +} + +func (d *dialogsImpl) error(id uint, param MessageDialogOptions) { + // TODO: Implement using UIAlertController +} + +func (d *dialogsImpl) question(id uint, param MessageDialogOptions) chan bool { + // TODO: Implement using UIAlertController + ch := make(chan bool, 1) + ch <- false + return ch +} + +func (d *dialogsImpl) openFile(id uint, param OpenFileDialogOptions) chan string { + // TODO: Implement using UIDocumentPickerViewController + ch := make(chan string, 1) + ch <- "" + return ch +} + +func (d *dialogsImpl) openMultipleFiles(id uint, param OpenFileDialogOptions) chan []string { + // TODO: Implement using UIDocumentPickerViewController + ch := make(chan []string, 1) + ch <- []string{} + return ch +} + +func (d *dialogsImpl) openDirectory(id uint, param OpenFileDialogOptions) chan string { + // TODO: Implement using UIDocumentPickerViewController + ch := make(chan string, 1) + ch <- "" + return ch +} + +func (d *dialogsImpl) saveFile(id uint, param SaveFileDialogOptions) chan string { + // TODO: Implement using UIDocumentPickerViewController + ch := make(chan string, 1) + ch <- "" + return ch +} + +type iosDialog struct { + dialog *MessageDialog +} + +func (d *iosDialog) show() { + // TODO: Implement using UIAlertController +} + +func newDialogImpl(d *MessageDialog) *iosDialog { + return &iosDialog{ + dialog: d, + } +} + +func (d *dialogsImpl) show() (chan string, error) { + ch := make(chan string, 1) + ch <- "" + return ch, nil +} + +func newOpenFileDialogImpl(_ *OpenFileDialogStruct) openFileDialogImpl { + return &dialogsImpl{} +} + +func newSaveFileDialogImpl(_ *SaveFileDialogStruct) saveFileDialogImpl { + return &dialogsImpl{} +} \ No newline at end of file diff --git a/v3/pkg/application/dialogs_linux.go b/v3/pkg/application/dialogs_linux.go new file mode 100644 index 000000000..1b1c8e4b4 --- /dev/null +++ b/v3/pkg/application/dialogs_linux.go @@ -0,0 +1,87 @@ +//go:build linux && !android && !server + +package application + +func (a *linuxApp) showAboutDialog(title string, message string, icon []byte) { + window, _ := globalApplication.Window.GetByID(a.getCurrentWindowID()) + var parent uintptr + if window != nil { + nativeWindow := window.NativeWindow() + if nativeWindow != nil { + parent = uintptr(nativeWindow) + } + } + about := newMessageDialog(InfoDialogType) + about.SetTitle(title). + SetMessage(message). + SetIcon(icon) + InvokeAsync(func() { + runQuestionDialog( + pointer(parent), + about, + ) + }) +} + +type linuxDialog struct { + dialog *MessageDialog +} + +func (m *linuxDialog) show() { + windowId := getNativeApplication().getCurrentWindowID() + window, _ := globalApplication.Window.GetByID(windowId) + var parent uintptr + if window != nil { + nativeWindow := window.NativeWindow() + if nativeWindow != nil { + parent = uintptr(nativeWindow) + } + } + + InvokeAsync(func() { + response := runQuestionDialog(pointer(parent), m.dialog) + if response >= 0 && response < len(m.dialog.Buttons) { + button := m.dialog.Buttons[response] + if button.Callback != nil { + go func() { + defer handlePanic() + button.Callback() + }() + } + } + }) +} + +func newDialogImpl(d *MessageDialog) *linuxDialog { + return &linuxDialog{ + dialog: d, + } +} + +type linuxOpenFileDialog struct { + dialog *OpenFileDialogStruct +} + +func newOpenFileDialogImpl(d *OpenFileDialogStruct) *linuxOpenFileDialog { + return &linuxOpenFileDialog{ + dialog: d, + } +} + +func (m *linuxOpenFileDialog) show() (chan string, error) { + return runOpenFileDialog(m.dialog) +} + +type linuxSaveFileDialog struct { + dialog *SaveFileDialogStruct +} + +func newSaveFileDialogImpl(d *SaveFileDialogStruct) *linuxSaveFileDialog { + return &linuxSaveFileDialog{ + dialog: d, + } +} + +func (m *linuxSaveFileDialog) show() (chan string, error) { + return runSaveFileDialog(m.dialog) +} diff --git a/v3/pkg/application/dialogs_test.go b/v3/pkg/application/dialogs_test.go new file mode 100644 index 000000000..60a923cb2 --- /dev/null +++ b/v3/pkg/application/dialogs_test.go @@ -0,0 +1,223 @@ +package application + +import ( + "testing" +) + +func TestGetDialogID(t *testing.T) { + // Get first dialog ID + id1 := getDialogID() + + // Get second dialog ID - should be different + id2 := getDialogID() + if id1 == id2 { + t.Error("getDialogID should return unique IDs") + } + + // Free first ID + freeDialogID(id1) + + // Get another ID - should recycle the freed id1 or be unique from id2 + id3 := getDialogID() + if id3 == id2 { + t.Error("getDialogID should not return the same ID as an active dialog") + } + // Verify recycling behavior: id3 should either be id1 (recycled) or a new unique ID + if id3 != id1 && id3 <= id2 { + t.Errorf("getDialogID returned unexpected ID: got %d, expected recycled %d or new > %d", id3, id1, id2) + } + + // Cleanup + freeDialogID(id2) + freeDialogID(id3) +} + +func TestFreeDialogID(t *testing.T) { + id := getDialogID() + freeDialogID(id) + + // Should be able to get the same ID again after freeing + newID := getDialogID() + freeDialogID(newID) + // Just verify it doesn't panic +} + +func TestButton_OnClick(t *testing.T) { + button := &Button{Label: "Test"} + called := false + + result := button.OnClick(func() { + called = true + }) + + // Should return the same button for chaining + if result != button { + t.Error("OnClick should return the same button") + } + + // Callback should be set + if button.Callback == nil { + t.Error("Callback should be set") + } + + // Call the callback + button.Callback() + if !called { + t.Error("Callback should have been called") + } +} + +func TestButton_SetAsDefault(t *testing.T) { + button := &Button{Label: "Test"} + + result := button.SetAsDefault() + + // Should return the same button for chaining + if result != button { + t.Error("SetAsDefault should return the same button") + } + + if !button.IsDefault { + t.Error("IsDefault should be true") + } +} + +func TestButton_SetAsCancel(t *testing.T) { + button := &Button{Label: "Test"} + + result := button.SetAsCancel() + + // Should return the same button for chaining + if result != button { + t.Error("SetAsCancel should return the same button") + } + + if !button.IsCancel { + t.Error("IsCancel should be true") + } +} + +func TestButton_Chaining(t *testing.T) { + button := &Button{Label: "OK"} + + button.SetAsDefault().SetAsCancel().OnClick(func() {}) + + if !button.IsDefault { + t.Error("IsDefault should be true after chaining") + } + if !button.IsCancel { + t.Error("IsCancel should be true after chaining") + } + if button.Callback == nil { + t.Error("Callback should be set after chaining") + } +} + +func TestDialogType_Constants(t *testing.T) { + // Verify dialog type constants are distinct + types := []DialogType{InfoDialogType, QuestionDialogType, WarningDialogType, ErrorDialogType} + seen := make(map[DialogType]bool) + + for _, dt := range types { + if seen[dt] { + t.Errorf("DialogType %d is duplicated", dt) + } + seen[dt] = true + } +} + +func TestMessageDialogOptions_Fields(t *testing.T) { + opts := MessageDialogOptions{ + DialogType: InfoDialogType, + Title: "Test Title", + Message: "Test Message", + Buttons: []*Button{{Label: "OK"}}, + Icon: []byte{1, 2, 3}, + } + + if opts.DialogType != InfoDialogType { + t.Error("DialogType not set correctly") + } + if opts.Title != "Test Title" { + t.Error("Title not set correctly") + } + if opts.Message != "Test Message" { + t.Error("Message not set correctly") + } + if len(opts.Buttons) != 1 { + t.Error("Buttons not set correctly") + } + if len(opts.Icon) != 3 { + t.Error("Icon not set correctly") + } +} + +func TestFileFilter_Fields(t *testing.T) { + filter := FileFilter{ + DisplayName: "Image Files (*.jpg, *.png)", + Pattern: "*.jpg;*.png", + } + + if filter.DisplayName != "Image Files (*.jpg, *.png)" { + t.Error("DisplayName not set correctly") + } + if filter.Pattern != "*.jpg;*.png" { + t.Error("Pattern not set correctly") + } +} + +func TestOpenFileDialogOptions_Fields(t *testing.T) { + opts := OpenFileDialogOptions{ + CanChooseDirectories: true, + CanChooseFiles: true, + CanCreateDirectories: true, + ShowHiddenFiles: true, + ResolvesAliases: true, + AllowsMultipleSelection: true, + Title: "Open", + Message: "Select a file", + ButtonText: "Choose", + Directory: "/home", + Filters: []FileFilter{ + {DisplayName: "All Files", Pattern: "*"}, + }, + } + + if !opts.CanChooseDirectories { + t.Error("CanChooseDirectories not set correctly") + } + if !opts.CanChooseFiles { + t.Error("CanChooseFiles not set correctly") + } + if opts.Title != "Open" { + t.Error("Title not set correctly") + } + if len(opts.Filters) != 1 { + t.Error("Filters not set correctly") + } +} + +func TestSaveFileDialogOptions_Fields(t *testing.T) { + opts := SaveFileDialogOptions{ + CanCreateDirectories: true, + ShowHiddenFiles: true, + Title: "Save", + Message: "Save as", + Directory: "/home", + Filename: "file.txt", + ButtonText: "Save", + Filters: []FileFilter{ + {DisplayName: "Text Files", Pattern: "*.txt"}, + }, + } + + if !opts.CanCreateDirectories { + t.Error("CanCreateDirectories not set correctly") + } + if opts.Title != "Save" { + t.Error("Title not set correctly") + } + if opts.Filename != "file.txt" { + t.Error("Filename not set correctly") + } +} diff --git a/v3/pkg/application/dialogs_windows.go b/v3/pkg/application/dialogs_windows.go new file mode 100644 index 000000000..73084d098 --- /dev/null +++ b/v3/pkg/application/dialogs_windows.go @@ -0,0 +1,275 @@ +//go:build windows + +package application + +import ( + "path/filepath" + "strings" + + "github.com/wailsapp/wails/v3/internal/go-common-file-dialog/cfd" + "github.com/wailsapp/wails/v3/pkg/w32" + "golang.org/x/sys/windows" +) + +func (m *windowsApp) showAboutDialog(title string, message string, _ []byte) { + about := newDialogImpl(&MessageDialog{ + MessageDialogOptions: MessageDialogOptions{ + DialogType: InfoDialogType, + Title: title, + Message: message, + }, + }) + about.UseAppIcon = true + about.show() +} + +type windowsDialog struct { + dialog *MessageDialog + + //dialogImpl unsafe.Pointer + UseAppIcon bool +} + +func (m *windowsDialog) show() { + + title := w32.MustStringToUTF16Ptr(m.dialog.Title) + message := w32.MustStringToUTF16Ptr(m.dialog.Message) + flags := calculateMessageDialogFlags(m.dialog.MessageDialogOptions) + var button int32 + var err error + + var parentWindow uintptr + if m.dialog.window != nil { + nativeWindow := m.dialog.window.NativeWindow() + if nativeWindow != nil { + parentWindow = uintptr(nativeWindow) + } + } + + if m.UseAppIcon || m.dialog.Icon != nil { + // 3 is the application icon + button, err = w32.MessageBoxWithIcon(parentWindow, message, title, 3, windows.MB_OK|windows.MB_USERICON) + if err != nil { + globalApplication.handleFatalError(err) + } + } else { + button, err = windows.MessageBox(windows.HWND(parentWindow), message, title, flags|windows.MB_SYSTEMMODAL) + if err != nil { + globalApplication.handleFatalError(err) + } + } + // This maps MessageBox return values to strings + responses := []string{"", "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "", "", "Try Again", "Continue"} + result := "Error" + if int(button) < len(responses) { + result = responses[button] + } + // Check if there's a callback for the button pressed + for _, buttonInDialog := range m.dialog.Buttons { + if buttonInDialog.Label == result { + if buttonInDialog.Callback != nil { + buttonInDialog.Callback() + } + } + } +} + +func newDialogImpl(d *MessageDialog) *windowsDialog { + return &windowsDialog{ + dialog: d, + } +} + +type windowOpenFileDialog struct { + dialog *OpenFileDialogStruct +} + +func newOpenFileDialogImpl(d *OpenFileDialogStruct) *windowOpenFileDialog { + return &windowOpenFileDialog{ + dialog: d, + } +} + +func getDefaultFolder(folder string) (string, error) { + if folder == "" { + return "", nil + } + return filepath.Abs(folder) +} + +func (m *windowOpenFileDialog) show() (chan string, error) { + + defaultFolder, err := getDefaultFolder(m.dialog.directory) + if err != nil { + return nil, err + } + + config := cfd.DialogConfig{ + Title: m.dialog.title, + Role: "PickFolder", + FileFilters: convertFilters(m.dialog.filters), + Folder: defaultFolder, + } + + var result []string + if m.dialog.allowsMultipleSelection && !m.dialog.canChooseDirectories { + temp, err := showCfdDialog( + func() (cfd.Dialog, error) { + return cfd.NewOpenMultipleFilesDialog(config) + }, true, m.dialog.window) + if err != nil { + return nil, err + } + result = temp.([]string) + } else { + if m.dialog.canChooseDirectories { + temp, err := showCfdDialog( + func() (cfd.Dialog, error) { + return cfd.NewSelectFolderDialog(config) + }, false, m.dialog.window) + if err != nil { + return nil, err + } + result = []string{temp.(string)} + } else { + temp, err := showCfdDialog( + func() (cfd.Dialog, error) { + return cfd.NewOpenFileDialog(config) + }, false, m.dialog.window) + if err != nil { + return nil, err + } + result = []string{temp.(string)} + } + } + + files := make(chan string) + go func() { + defer handlePanic() + for _, file := range result { + files <- file + } + close(files) + }() + return files, nil +} + +type windowSaveFileDialog struct { + dialog *SaveFileDialogStruct +} + +func newSaveFileDialogImpl(d *SaveFileDialogStruct) *windowSaveFileDialog { + return &windowSaveFileDialog{ + dialog: d, + } +} + +func (m *windowSaveFileDialog) show() (chan string, error) { + files := make(chan string) + defaultFolder, err := getDefaultFolder(m.dialog.directory) + if err != nil { + close(files) + return files, err + } + + config := cfd.DialogConfig{ + Title: m.dialog.title, + Role: "SaveFile", + FileFilters: convertFilters(m.dialog.filters), + FileName: m.dialog.filename, + Folder: defaultFolder, + } + + // Original PR for v2 by @almas1992: https://github.com/wailsapp/wails/pull/3205 + if len(m.dialog.filters) > 0 { + config.DefaultExtension = strings.TrimPrefix(strings.Split(m.dialog.filters[0].Pattern, ";")[0], "*") + } + + result, err := showCfdDialog( + func() (cfd.Dialog, error) { + return cfd.NewSaveFileDialog(config) + }, false, m.dialog.window) + if err != nil { + close(files) + return files, err + } + go func() { + defer handlePanic() + f, ok := result.(string) + if ok { + files <- f + } + close(files) + }() + return files, err +} + +func calculateMessageDialogFlags(options MessageDialogOptions) uint32 { + var flags uint32 + + switch options.DialogType { + case InfoDialogType: + flags = windows.MB_OK | windows.MB_ICONINFORMATION + case ErrorDialogType: + flags = windows.MB_ICONERROR | windows.MB_OK + case QuestionDialogType: + flags = windows.MB_YESNO + for _, button := range options.Buttons { + if strings.TrimSpace(strings.ToLower(button.Label)) == "no" && button.IsDefault { + flags |= windows.MB_DEFBUTTON2 + } + } + case WarningDialogType: + flags = windows.MB_OK | windows.MB_ICONWARNING + } + + return flags +} + +func convertFilters(filters []FileFilter) []cfd.FileFilter { + var result []cfd.FileFilter + for _, filter := range filters { + result = append(result, cfd.FileFilter(filter)) + } + return result +} + +func showCfdDialog(newDlg func() (cfd.Dialog, error), isMultiSelect bool, parentWindow Window) (any, error) { + dlg, err := newDlg() + if err != nil { + return nil, err + } + + // Set parent window if provided + if parentWindow != nil { + nativeWindow := parentWindow.NativeWindow() + if nativeWindow != nil { + dlg.SetParentWindowHandle(uintptr(nativeWindow)) + } + } + + defer func() { + err := dlg.Release() + if err != nil { + globalApplication.error("unable to release dialog: %w", err) + } + }() + + if multi, _ := dlg.(cfd.OpenMultipleFilesDialog); multi != nil && isMultiSelect { + paths, err := multi.ShowAndGetResults() + if err != nil { + return nil, err + } + + for i, path := range paths { + paths[i] = filepath.Clean(path) + } + return paths, nil + } + + path, err := dlg.ShowAndGetResult() + if err != nil { + return nil, err + } + return filepath.Clean(path), nil +} diff --git a/v3/pkg/application/dialogs_windows_test.go b/v3/pkg/application/dialogs_windows_test.go new file mode 100644 index 000000000..af4dabf75 --- /dev/null +++ b/v3/pkg/application/dialogs_windows_test.go @@ -0,0 +1,57 @@ +//go:build windows + +package application_test + +import ( + "path/filepath" + "testing" + + "github.com/matryer/is" +) + +func TestCleanPath(t *testing.T) { + i := is.New(t) + tests := []struct { + name string + inputPath string + expected string + }{ + { + name: "path with double separators", + inputPath: `C:\\temp\\folder`, + expected: `C:\temp\folder`, + }, + { + name: "path with forward slashes", + inputPath: `C://temp//folder`, + expected: `C:\temp\folder`, + }, + { + name: "path with trailing separator", + inputPath: `C:\\temp\\folder\\`, + expected: `C:\temp\folder`, + }, + { + name: "path with escaped tab character", + inputPath: `C:\\Users\\test\\tab.txt`, + expected: `C:\Users\test\tab.txt`, + }, + { + name: "newline character", + inputPath: `C:\\Users\\test\\newline\\n.txt`, + expected: `C:\Users\test\newline\n.txt`, + }, + { + name: "UNC path with multiple separators", + inputPath: `\\\\\\\\host\\share\\test.txt`, + expected: `\\\\host\share\test.txt`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cleaned := filepath.Clean(tt.inputPath) + i.Equal(cleaned, tt.expected) + }) + } +} diff --git a/v3/pkg/application/environment.go b/v3/pkg/application/environment.go new file mode 100644 index 000000000..68be3ba06 --- /dev/null +++ b/v3/pkg/application/environment.go @@ -0,0 +1,18 @@ +package application + +import "github.com/wailsapp/wails/v3/internal/operatingsystem" + +// EnvironmentInfo represents information about the current environment. +// +// Fields: +// - OS: the operating system that the program is running on. +// - Arch: the architecture of the operating system. +// - Debug: indicates whether debug mode is enabled. +// - OSInfo: information about the operating system. +type EnvironmentInfo struct { + OS string + Arch string + Debug bool + OSInfo *operatingsystem.OS + PlatformInfo map[string]any +} diff --git a/v3/pkg/application/environment_manager.go b/v3/pkg/application/environment_manager.go new file mode 100644 index 000000000..d32b6dd89 --- /dev/null +++ b/v3/pkg/application/environment_manager.go @@ -0,0 +1,56 @@ +package application + +import ( + "runtime" + + "github.com/wailsapp/wails/v3/internal/fileexplorer" + "github.com/wailsapp/wails/v3/internal/operatingsystem" +) + +// EnvironmentManager manages environment-related operations +type EnvironmentManager struct { + app *App +} + +// newEnvironmentManager creates a new EnvironmentManager instance +func newEnvironmentManager(app *App) *EnvironmentManager { + return &EnvironmentManager{ + app: app, + } +} + +// Info returns environment information +func (em *EnvironmentManager) Info() EnvironmentInfo { + info, _ := operatingsystem.Info() + result := EnvironmentInfo{ + OS: runtime.GOOS, + Arch: runtime.GOARCH, + Debug: em.app.isDebugMode, + OSInfo: info, + } + result.PlatformInfo = em.app.platformEnvironment() + return result +} + +// IsDarkMode returns true if the system is in dark mode +func (em *EnvironmentManager) IsDarkMode() bool { + if em.app.impl == nil { + return false + } + return em.app.impl.isDarkMode() +} + +// GetAccentColor returns the system accent color +func (em *EnvironmentManager) GetAccentColor() string { + if em.app.impl == nil { + return "rgb(0,122,255)" + } + return em.app.impl.getAccentColor() +} + +// OpenFileManager opens the file manager at the specified path, optionally selecting the file +func (em *EnvironmentManager) OpenFileManager(path string, selectFile bool) error { + return InvokeSyncWithError(func() error { + return fileexplorer.OpenFileManager(path, selectFile) + }) +} diff --git a/v3/pkg/application/errors.go b/v3/pkg/application/errors.go new file mode 100644 index 000000000..d0b7edd6b --- /dev/null +++ b/v3/pkg/application/errors.go @@ -0,0 +1,55 @@ +package application + +import ( + "fmt" + "os" + "strings" +) + +// FatalError instances are passed to the registered error handler +// in case of catastrophic, unrecoverable failures that require immediate termination. +// FatalError wraps the original error value in an informative message. +// The underlying error may be retrieved through the [FatalError.Unwrap] method. +type FatalError struct { + err error + internal bool +} + +// Internal returns true when the error was triggered from wails' internal code. +func (e *FatalError) Internal() bool { + return e.internal +} + +// Unwrap returns the original cause of the fatal error, +// for easy inspection using the [errors.As] API. +func (e *FatalError) Unwrap() error { + return e.err +} + +func (e *FatalError) Error() string { + var buffer strings.Builder + buffer.WriteString("\n\n******************************** FATAL *********************************\n") + buffer.WriteString("* There has been a catastrophic failure in your application. *\n") + if e.internal { + buffer.WriteString("* Please report this error at https://github.com/wailsapp/wails/issues *\n") + } + buffer.WriteString("**************************** Error Details *****************************\n") + buffer.WriteString(e.err.Error()) + buffer.WriteString("************************************************************************\n") + return buffer.String() +} + +func Fatal(message string, args ...any) { + err := &FatalError{ + err: fmt.Errorf(message, args...), + internal: true, + } + + if globalApplication != nil { + globalApplication.handleError(err) + } else { + fmt.Println(err) + } + + os.Exit(1) +} diff --git a/v3/pkg/application/event_manager.go b/v3/pkg/application/event_manager.go new file mode 100644 index 000000000..7555ff685 --- /dev/null +++ b/v3/pkg/application/event_manager.go @@ -0,0 +1,174 @@ +package application + +import ( + "slices" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +// EventManager manages event-related operations +type EventManager struct { + app *App +} + +// newEventManager creates a new EventManager instance +func newEventManager(app *App) *EventManager { + return &EventManager{ + app: app, + } +} + +// Emit emits a custom event with the specified name and associated data. +// It returns a boolean indicating whether the event was cancelled by a hook. +// +// If no data argument is provided, Emit emits an event with nil data. +// When there is exactly one data argument, it will be used as the custom event's data field. +// When more than one argument is provided, the event's data field will be set to the argument slice. +// +// If the given event name is registered, Emit validates the data parameter +// against the expected data type. In case of a mismatch, Emit reports an error +// to the registered error handler for the application and cancels the event. +func (em *EventManager) Emit(name string, data ...any) bool { + event := &CustomEvent{Name: name} + + if len(data) == 1 { + event.Data = data[0] + } else if len(data) > 1 { + event.Data = data + } + + if err := em.app.customEventProcessor.Emit(event); err != nil { + globalApplication.handleError(err) + } + + return event.IsCancelled() +} + +// EmitEvent emits a custom event object (internal use) +// It returns a boolean indicating whether the event was cancelled by a hook. +// +// If the given event name is registered, emitEvent validates the data parameter +// against the expected data type. In case of a mismatch, emitEvent reports an error +// to the registered error handler for the application and cancels the event. +func (em *EventManager) EmitEvent(event *CustomEvent) bool { + if err := em.app.customEventProcessor.Emit(event); err != nil { + globalApplication.handleError(err) + } + + return event.IsCancelled() +} + +// On registers a listener for custom events +func (em *EventManager) On(name string, callback func(event *CustomEvent)) func() { + return em.app.customEventProcessor.On(name, callback) +} + +// Off removes all listeners for a custom event +func (em *EventManager) Off(name string) { + em.app.customEventProcessor.Off(name) +} + +// OnMultiple registers a listener for custom events that will be called N times +func (em *EventManager) OnMultiple(name string, callback func(event *CustomEvent), counter int) { + em.app.customEventProcessor.OnMultiple(name, callback, counter) +} + +// Reset removes all custom event listeners +func (em *EventManager) Reset() { + em.app.customEventProcessor.OffAll() +} + +// OnApplicationEvent registers a listener for application events +func (em *EventManager) OnApplicationEvent(eventType events.ApplicationEventType, callback func(event *ApplicationEvent)) func() { + eventID := uint(eventType) + em.app.applicationEventListenersLock.Lock() + defer em.app.applicationEventListenersLock.Unlock() + listener := &EventListener{ + callback: callback, + } + em.app.applicationEventListeners[eventID] = append(em.app.applicationEventListeners[eventID], listener) + if em.app.impl != nil { + go func() { + defer handlePanic() + em.app.impl.on(eventID) + }() + } + + return func() { + // lock the map + em.app.applicationEventListenersLock.Lock() + defer em.app.applicationEventListenersLock.Unlock() + // Remove listener + em.app.applicationEventListeners[eventID] = slices.DeleteFunc(em.app.applicationEventListeners[eventID], func(l *EventListener) bool { + return l == listener + }) + } +} + +// RegisterApplicationEventHook registers an application event hook +func (em *EventManager) RegisterApplicationEventHook(eventType events.ApplicationEventType, callback func(event *ApplicationEvent)) func() { + eventID := uint(eventType) + em.app.applicationEventHooksLock.Lock() + defer em.app.applicationEventHooksLock.Unlock() + thisHook := &eventHook{ + callback: callback, + } + em.app.applicationEventHooks[eventID] = append(em.app.applicationEventHooks[eventID], thisHook) + + return func() { + em.app.applicationEventHooksLock.Lock() + em.app.applicationEventHooks[eventID] = slices.DeleteFunc(em.app.applicationEventHooks[eventID], func(h *eventHook) bool { + return h == thisHook + }) + em.app.applicationEventHooksLock.Unlock() + } +} + +// Dispatch dispatches an event to listeners (internal use) +func (em *EventManager) dispatch(event *CustomEvent) { + // Snapshot listeners under Lock + em.app.wailsEventListenerLock.Lock() + listeners := slices.Clone(em.app.wailsEventListeners) + em.app.wailsEventListenerLock.Unlock() + + for _, listener := range listeners { + if event.IsCancelled() { + return + } + listener.DispatchWailsEvent(event) + } +} + +// HandleApplicationEvent handles application events (internal use) +func (em *EventManager) handleApplicationEvent(event *ApplicationEvent) { + defer handlePanic() + em.app.applicationEventListenersLock.RLock() + listeners, ok := em.app.applicationEventListeners[event.Id] + em.app.applicationEventListenersLock.RUnlock() + if !ok { + return + } + + // Process Hooks + em.app.applicationEventHooksLock.RLock() + hooks, ok := em.app.applicationEventHooks[event.Id] + em.app.applicationEventHooksLock.RUnlock() + if ok { + for _, thisHook := range hooks { + thisHook.callback(event) + if event.IsCancelled() { + return + } + } + } + + for _, listener := range listeners { + go func() { + if event.IsCancelled() { + return + } + defer handlePanic() + listener.callback(event) + }() + } +} diff --git a/v3/pkg/application/events.go b/v3/pkg/application/events.go new file mode 100644 index 000000000..577001dbe --- /dev/null +++ b/v3/pkg/application/events.go @@ -0,0 +1,366 @@ +package application + +import ( + "fmt" + "reflect" + "slices" + "sync" + "sync/atomic" + + "encoding/json" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +type ApplicationEvent struct { + Id uint + ctx *ApplicationEventContext + cancelled atomic.Bool +} + +func (w *ApplicationEvent) Context() *ApplicationEventContext { + return w.ctx +} + +func newApplicationEvent(id events.ApplicationEventType) *ApplicationEvent { + return &ApplicationEvent{ + Id: uint(id), + ctx: newApplicationEventContext(), + } +} + +func (w *ApplicationEvent) Cancel() { + w.cancelled.Store(true) +} + +func (w *ApplicationEvent) IsCancelled() bool { + return w.cancelled.Load() +} + +var applicationEvents = make(chan *ApplicationEvent, 5) + +type windowEvent struct { + WindowID uint + EventID uint +} + +var windowEvents = make(chan *windowEvent, 5) + +var menuItemClicked = make(chan uint, 5) + +type CustomEvent struct { + Name string `json:"name"` + Data any `json:"data"` + + // Sender records the name of the window sending the event, + // or "" if sent from application. + Sender string `json:"sender,omitempty"` + + cancelled atomic.Bool +} + +func (e *CustomEvent) Cancel() { + e.cancelled.Store(true) +} + +func (e *CustomEvent) IsCancelled() bool { + return e.cancelled.Load() +} + +func (e *CustomEvent) ToJSON() string { + marshal, err := json.Marshal(&e) + if err != nil { + // TODO: Fatal error? log? + return "" + } + return string(marshal) +} + +// WailsEventListener is an interface for receiving all emitted Wails events. +// Used by transport layers (IPC, WebSocket) to broadcast events. +type WailsEventListener interface { + DispatchWailsEvent(event *CustomEvent) +} + +type hook struct { + callback func(*CustomEvent) +} + +// eventListener holds a callback function which is invoked when +// the event listened for is emitted. It has a counter which indicates +// how the total number of events it is interested in. A value of zero +// means it does not expire (default). +type eventListener struct { + callback func(*CustomEvent) // Function to call with emitted event data + counter int // The number of times this callback may be called. -1 = infinite + delete bool // Flag to indicate that this listener should be deleted +} + +// EventProcessor handles custom events +type EventProcessor struct { + // Go event listeners + listeners map[string][]*eventListener + notifyLock sync.RWMutex + dispatchEventToWindows func(*CustomEvent) + hooks map[string][]*hook + hookLock sync.RWMutex +} + +func NewWailsEventProcessor(dispatchEventToWindows func(*CustomEvent)) *EventProcessor { + return &EventProcessor{ + listeners: make(map[string][]*eventListener), + dispatchEventToWindows: dispatchEventToWindows, + hooks: make(map[string][]*hook), + } +} + +// On is the equivalent of Javascript's `addEventListener` +func (e *EventProcessor) On(eventName string, callback func(event *CustomEvent)) func() { + return e.registerListener(eventName, callback, -1) +} + +// OnMultiple is the same as `OnApplicationEvent` but will unregister after `count` events +func (e *EventProcessor) OnMultiple(eventName string, callback func(event *CustomEvent), counter int) func() { + return e.registerListener(eventName, callback, counter) +} + +// Once is the same as `OnApplicationEvent` but will unregister after the first event +func (e *EventProcessor) Once(eventName string, callback func(event *CustomEvent)) func() { + return e.registerListener(eventName, callback, 1) +} + +// Emit sends an event to all listeners. +// +// If the event is globally registered, it validates associated data +// against the expected data type. In case of mismatches, +// it cancels the event and returns an error. +func (e *EventProcessor) Emit(thisEvent *CustomEvent) error { + if thisEvent == nil { + return nil + } + + // Validate data type; in case of mismatches cancel and report error. + if err := validateCustomEvent(thisEvent); err != nil { + thisEvent.Cancel() + return err + } + + // If we have any hooks, run them first and check if the event was cancelled + if e.hooks != nil { + if hooks, ok := e.hooks[thisEvent.Name]; ok { + for _, thisHook := range hooks { + thisHook.callback(thisEvent) + if thisEvent.IsCancelled() { + return nil + } + } + } + } + + go func() { + defer handlePanic() + e.dispatchEventToListeners(thisEvent) + }() + go func() { + defer handlePanic() + e.dispatchEventToWindows(thisEvent) + }() + + return nil +} + +func (e *EventProcessor) Off(eventName string) { + e.unRegisterListener(eventName) +} + +func (e *EventProcessor) OffAll() { + e.notifyLock.Lock() + defer e.notifyLock.Unlock() + e.listeners = make(map[string][]*eventListener) +} + +// registerListener provides a means of subscribing to events of type "eventName" +func (e *EventProcessor) registerListener(eventName string, callback func(*CustomEvent), counter int) func() { + // Create new eventListener + thisListener := &eventListener{ + callback: callback, + counter: counter, + delete: false, + } + e.notifyLock.Lock() + // Append the new listener to the listeners slice + e.listeners[eventName] = append(e.listeners[eventName], thisListener) + e.notifyLock.Unlock() + return func() { + e.notifyLock.Lock() + defer e.notifyLock.Unlock() + + if _, ok := e.listeners[eventName]; !ok { + return + } + e.listeners[eventName] = slices.DeleteFunc(e.listeners[eventName], func(l *eventListener) bool { + return l == thisListener + }) + } +} + +// RegisterHook provides a means of registering methods to be called before emitting the event +func (e *EventProcessor) RegisterHook(eventName string, callback func(*CustomEvent)) func() { + // Create new hook + thisHook := &hook{ + callback: callback, + } + e.hookLock.Lock() + // Append the new listener to the listeners slice + e.hooks[eventName] = append(e.hooks[eventName], thisHook) + e.hookLock.Unlock() + return func() { + e.hookLock.Lock() + defer e.hookLock.Unlock() + + if _, ok := e.hooks[eventName]; !ok { + return + } + e.hooks[eventName] = slices.DeleteFunc(e.hooks[eventName], func(h *hook) bool { + return h == thisHook + }) + } +} + +// unRegisterListener provides a means of unsubscribing to events of type "eventName" +func (e *EventProcessor) unRegisterListener(eventName string) { + e.notifyLock.Lock() + defer e.notifyLock.Unlock() + delete(e.listeners, eventName) +} + +// dispatchEventToListeners calls all registered listeners event name +func (e *EventProcessor) dispatchEventToListeners(event *CustomEvent) { + + e.notifyLock.Lock() + defer e.notifyLock.Unlock() + + listeners := e.listeners[event.Name] + if listeners == nil { + return + } + + // We have a dirty flag to indicate that there are items to delete + itemsToDelete := false + + // Callback in goroutine + for _, listener := range listeners { + if listener.counter > 0 { + listener.counter-- + } + go func() { + if event.IsCancelled() { + return + } + defer handlePanic() + listener.callback(event) + }() + + if listener.counter == 0 { + listener.delete = true + itemsToDelete = true + } + } + + // Do we have items to delete? + if itemsToDelete == true { + e.listeners[event.Name] = slices.DeleteFunc(listeners, func(l *eventListener) bool { + return l.delete == true + }) + } +} + +// Void will be translated by the binding generator to the TypeScript type 'void'. +// It can be used as an event data type to register events that must not have any associated data. +type Void interface { + sentinel() +} + +var registeredEvents sync.Map +var voidType = reflect.TypeFor[Void]() + +// RegisterEvent registers a custom event name and associated data type. +// Events may be registered at most once. +// Duplicate calls for the same event name trigger a panic. +// +// The binding generator emits typing information for all registered custom events. +// [App.EmitEvent] and [Window.EmitEvent] check the data type for registered events. +// Data types are matched exactly and no conversion is performed. +// +// It is recommended to call RegisterEvent directly, +// with constant arguments, and only from init functions. +// Indirect calls or instantiations are not discoverable by the binding generator. +func RegisterEvent[Data any](name string) { + if events.IsKnownEvent(name) { + panic(fmt.Errorf("'%s' is a known system event name", name)) + } + if typ, ok := registeredEvents.Load(name); ok { + panic(fmt.Errorf("event '%s' is already registered with data type %s", name, typ)) + } + + registeredEvents.Store(name, reflect.TypeFor[Data]()) + eventRegistered(name) +} + +func validateCustomEvent(event *CustomEvent) error { + r, ok := registeredEvents.Load(event.Name) + if !ok { + warnAboutUnregisteredEvent(event.Name) + return nil + } + + typ := r.(reflect.Type) + + if typ == voidType { + if event.Data == nil { + return nil + } + } else if typ.Kind() == reflect.Interface { + if reflect.TypeOf(event.Data).Implements(typ) { + return nil + } + } else { + if reflect.TypeOf(event.Data) == typ { + return nil + } + } + + return fmt.Errorf( + "data of type %s for event '%s' does not match registered data type %s", + reflect.TypeOf(event.Data), + event.Name, + typ, + ) +} + +func decodeEventData(name string, data []byte) (result any, err error) { + r, ok := registeredEvents.Load(name) + if !ok { + // Unregistered events unmarshal to any. + err = json.Unmarshal(data, &result) + return + } + + typ := r.(reflect.Type) + + if typ == voidType { + // When typ is voidType, perform a null check + err = json.Unmarshal(data, &result) + if err == nil && result != nil { + err = fmt.Errorf("non-null data for event '%s' does not match registered data type %s", name, typ) + } + } else { + value := reflect.New(typ.(reflect.Type)) + err = json.Unmarshal(data, value.Interface()) + if err == nil { + result = value.Elem().Interface() + } + } + + return +} diff --git a/v3/pkg/application/events_bench_test.go b/v3/pkg/application/events_bench_test.go new file mode 100644 index 000000000..c6ed4341f --- /dev/null +++ b/v3/pkg/application/events_bench_test.go @@ -0,0 +1,380 @@ +//go:build bench + +package application_test + +import ( + "fmt" + "sync" + "sync/atomic" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// mockWindowDispatcher implements a no-op dispatcher for benchmarking +type mockWindowDispatcher struct { + count atomic.Int64 +} + +func (m *mockWindowDispatcher) dispatchEventToWindows(event *application.CustomEvent) { + m.count.Add(1) +} + +// BenchmarkEventEmit measures event emission with varying listener counts +func BenchmarkEventEmit(b *testing.B) { + listenerCounts := []int{0, 1, 10, 100} + + for _, count := range listenerCounts { + b.Run(fmt.Sprintf("Listeners%d", count), func(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + + // Register listeners + for i := 0; i < count; i++ { + processor.On("benchmark-event", func(event *application.CustomEvent) { + // Minimal work + _ = event.Data + }) + } + + event := &application.CustomEvent{ + Name: "benchmark-event", + Data: "test payload", + } + + b.ResetTimer() + for b.Loop() { + _ = processor.Emit(event) + } + }) + } +} + +// BenchmarkEventRegistration measures the cost of registering event listeners +func BenchmarkEventRegistration(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + + b.Run("SingleRegistration", func(b *testing.B) { + for b.Loop() { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + processor.On("test-event", func(event *application.CustomEvent) {}) + } + }) + + b.Run("MultipleRegistrations", func(b *testing.B) { + for b.Loop() { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + for i := 0; i < 10; i++ { + processor.On(fmt.Sprintf("test-event-%d", i), func(event *application.CustomEvent) {}) + } + } + }) + + b.Run("SameEventMultipleListeners", func(b *testing.B) { + for b.Loop() { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + for i := 0; i < 10; i++ { + processor.On("test-event", func(event *application.CustomEvent) {}) + } + } + }) +} + +// BenchmarkEventUnregistration measures the cost of unregistering event listeners +func BenchmarkEventUnregistration(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + + b.Run("SingleUnregister", func(b *testing.B) { + for b.Loop() { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + cancel := processor.On("test-event", func(event *application.CustomEvent) {}) + cancel() + } + }) + + b.Run("UnregisterFromMany", func(b *testing.B) { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + // Pre-register many listeners + cancels := make([]func(), 100) + for i := 0; i < 100; i++ { + cancels[i] = processor.On("test-event", func(event *application.CustomEvent) {}) + } + + b.ResetTimer() + for i := 0; b.Loop(); i++ { + // Re-register to have something to unregister + if i%100 == 0 { + for j := 0; j < 100; j++ { + cancels[j] = processor.On("test-event", func(event *application.CustomEvent) {}) + } + } + cancels[i%100]() + } + }) + + b.Run("OffAllListeners", func(b *testing.B) { + for b.Loop() { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + for i := 0; i < 10; i++ { + processor.On("test-event", func(event *application.CustomEvent) {}) + } + processor.Off("test-event") + } + }) +} + +// BenchmarkHookExecution measures the cost of hook execution during emit +func BenchmarkHookExecution(b *testing.B) { + hookCounts := []int{0, 1, 5, 10} + + for _, count := range hookCounts { + b.Run(fmt.Sprintf("Hooks%d", count), func(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + + // Register hooks + for i := 0; i < count; i++ { + processor.RegisterHook("benchmark-event", func(event *application.CustomEvent) { + // Minimal work - don't cancel + _ = event.Data + }) + } + + event := &application.CustomEvent{ + Name: "benchmark-event", + Data: "test payload", + } + + b.ResetTimer() + for b.Loop() { + _ = processor.Emit(event) + } + }) + } +} + +// BenchmarkConcurrentEmit measures event emission under concurrent load +func BenchmarkConcurrentEmit(b *testing.B) { + concurrencyLevels := []int{1, 4, 16} + + for _, concurrency := range concurrencyLevels { + b.Run(fmt.Sprintf("Goroutines%d", concurrency), func(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + + // Register a few listeners + for i := 0; i < 5; i++ { + processor.On("benchmark-event", func(event *application.CustomEvent) { + _ = event.Data + }) + } + + b.ResetTimer() + b.SetParallelism(concurrency) + b.RunParallel(func(pb *testing.PB) { + event := &application.CustomEvent{ + Name: "benchmark-event", + Data: "test payload", + } + for pb.Next() { + _ = processor.Emit(event) + } + }) + }) + } +} + +// BenchmarkEventToJSON measures CustomEvent JSON serialization +func BenchmarkEventToJSON(b *testing.B) { + b.Run("SimpleData", func(b *testing.B) { + event := &application.CustomEvent{ + Name: "test-event", + Data: "simple string payload", + } + for b.Loop() { + _ = event.ToJSON() + } + }) + + b.Run("ComplexData", func(b *testing.B) { + event := &application.CustomEvent{ + Name: "test-event", + Data: map[string]interface{}{ + "id": 12345, + "name": "Test Event", + "tags": []string{"tag1", "tag2", "tag3"}, + "enabled": true, + "nested": map[string]interface{}{ + "value": 3.14159, + }, + }, + } + for b.Loop() { + _ = event.ToJSON() + } + }) + + b.Run("WithSender", func(b *testing.B) { + event := &application.CustomEvent{ + Name: "test-event", + Data: "payload", + Sender: "main-window", + } + for b.Loop() { + _ = event.ToJSON() + } + }) +} + +// BenchmarkAtomicCancel measures the atomic cancel/check operations +func BenchmarkAtomicCancel(b *testing.B) { + b.Run("Cancel", func(b *testing.B) { + for b.Loop() { + event := &application.CustomEvent{ + Name: "test", + Data: nil, + } + event.Cancel() + } + }) + + b.Run("IsCancelled", func(b *testing.B) { + event := &application.CustomEvent{ + Name: "test", + Data: nil, + } + for b.Loop() { + _ = event.IsCancelled() + } + }) + + b.Run("CancelAndCheck", func(b *testing.B) { + for b.Loop() { + event := &application.CustomEvent{ + Name: "test", + Data: nil, + } + event.Cancel() + _ = event.IsCancelled() + } + }) +} + +// BenchmarkEventProcessorCreation measures processor instantiation +func BenchmarkEventProcessorCreation(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + for b.Loop() { + _ = application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + } +} + +// BenchmarkOnceEvent measures the Once registration and auto-unregistration +func BenchmarkOnceEvent(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + + b.Run("RegisterAndTrigger", func(b *testing.B) { + for b.Loop() { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + var wg sync.WaitGroup + wg.Add(1) + processor.Once("once-event", func(event *application.CustomEvent) { + wg.Done() + }) + _ = processor.Emit(&application.CustomEvent{Name: "once-event", Data: nil}) + wg.Wait() + } + }) +} + +// BenchmarkOnMultipleEvent measures the OnMultiple registration +func BenchmarkOnMultipleEvent(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + + b.Run("ThreeEvents", func(b *testing.B) { + for b.Loop() { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + var wg sync.WaitGroup + wg.Add(3) + processor.OnMultiple("multi-event", func(event *application.CustomEvent) { + wg.Done() + }, 3) + for i := 0; i < 3; i++ { + _ = processor.Emit(&application.CustomEvent{Name: "multi-event", Data: nil}) + } + wg.Wait() + } + }) +} + +// BenchmarkMixedEventOperations simulates realistic event usage patterns +func BenchmarkMixedEventOperations(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + + b.Run("RegisterEmitUnregister", func(b *testing.B) { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + + for b.Loop() { + cancel := processor.On("mixed-event", func(event *application.CustomEvent) { + _ = event.Data + }) + _ = processor.Emit(&application.CustomEvent{Name: "mixed-event", Data: "test"}) + cancel() + } + }) + + b.Run("HookAndEmit", func(b *testing.B) { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + processor.RegisterHook("hooked-event", func(event *application.CustomEvent) { + // Validation hook + if event.Data == nil { + event.Cancel() + } + }) + processor.On("hooked-event", func(event *application.CustomEvent) { + _ = event.Data + }) + + event := &application.CustomEvent{Name: "hooked-event", Data: "valid"} + + b.ResetTimer() + for b.Loop() { + _ = processor.Emit(event) + } + }) +} + +// BenchmarkEventNameLookup measures the map lookup performance for event names +func BenchmarkEventNameLookup(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + + // Register events with different name lengths + shortName := "evt" + mediumName := "application:user:action" + longName := "com.mycompany.myapp.module.submodule.event.type.action" + + processor.On(shortName, func(event *application.CustomEvent) {}) + processor.On(mediumName, func(event *application.CustomEvent) {}) + processor.On(longName, func(event *application.CustomEvent) {}) + + b.Run("ShortName", func(b *testing.B) { + event := &application.CustomEvent{Name: shortName, Data: nil} + for b.Loop() { + _ = processor.Emit(event) + } + }) + + b.Run("MediumName", func(b *testing.B) { + event := &application.CustomEvent{Name: mediumName, Data: nil} + for b.Loop() { + _ = processor.Emit(event) + } + }) + + b.Run("LongName", func(b *testing.B) { + event := &application.CustomEvent{Name: longName, Data: nil} + for b.Loop() { + _ = processor.Emit(event) + } + }) +} diff --git a/v3/pkg/application/events_common_android.go b/v3/pkg/application/events_common_android.go new file mode 100644 index 000000000..b0aa0b912 --- /dev/null +++ b/v3/pkg/application/events_common_android.go @@ -0,0 +1,23 @@ +//go:build android + +package application + +import "github.com/wailsapp/wails/v3/pkg/events" + +// Map platform events → common events (same pattern as macOS & others) +var commonApplicationEventMap = map[events.ApplicationEventType]events.ApplicationEventType{ + events.Android.ActivityCreated: events.Common.ApplicationStarted, +} + +// setupCommonEvents forwards Android platform events to their common counterparts +func (a *androidApp) setupCommonEvents() { + for sourceEvent, targetEvent := range commonApplicationEventMap { + sourceEvent := sourceEvent + targetEvent := targetEvent + a.parent.Event.OnApplicationEvent(sourceEvent, func(event *ApplicationEvent) { + event.Id = uint(targetEvent) + androidLogf("info", "[events_common_android.go] Forwarding Android event %d → common %d", sourceEvent, targetEvent) + applicationEvents <- event + }) + } +} diff --git a/v3/pkg/application/events_common_darwin.go b/v3/pkg/application/events_common_darwin.go new file mode 100644 index 000000000..8bc3a8af7 --- /dev/null +++ b/v3/pkg/application/events_common_darwin.go @@ -0,0 +1,36 @@ +//go:build darwin && !ios + +package application + +import "github.com/wailsapp/wails/v3/pkg/events" + +var commonApplicationEventMap = map[events.ApplicationEventType]events.ApplicationEventType{ + events.Mac.ApplicationDidFinishLaunching: events.Common.ApplicationStarted, + events.Mac.ApplicationDidChangeTheme: events.Common.ThemeChanged, +} + +func (m *macosApp) setupCommonEvents() { + for sourceEvent, targetEvent := range commonApplicationEventMap { + sourceEvent := sourceEvent + targetEvent := targetEvent + m.parent.Event.OnApplicationEvent(sourceEvent, func(event *ApplicationEvent) { + event.Id = uint(targetEvent) + applicationEvents <- event + }) + } + + // Handle dock icon click (applicationShouldHandleReopen) to show windows + // when there are no visible windows. This provides the expected macOS UX + // where clicking the dock icon shows a hidden app's window. + // Issue #4583: Apps with StartHidden: true should show when dock icon is clicked. + m.parent.Event.OnApplicationEvent(events.Mac.ApplicationShouldHandleReopen, func(event *ApplicationEvent) { + if !event.Context().HasVisibleWindows() { + // Show all windows that are not visible + for _, window := range m.parent.Window.GetAll() { + if !window.IsVisible() { + window.Show() + } + } + } + }) +} diff --git a/v3/pkg/application/events_common_ios.go b/v3/pkg/application/events_common_ios.go new file mode 100644 index 000000000..eadfc2afb --- /dev/null +++ b/v3/pkg/application/events_common_ios.go @@ -0,0 +1,24 @@ +//go:build ios + +package application + +import "github.com/wailsapp/wails/v3/pkg/events" + +// Map platform events → common events (same pattern as macOS & others) +var commonApplicationEventMap = map[events.ApplicationEventType]events.ApplicationEventType{ + events.IOS.ApplicationDidFinishLaunching: events.Common.ApplicationStarted, +} + +// setupCommonEvents forwards iOS platform events to their common counterparts +func (i *iosApp) setupCommonEvents() { + for sourceEvent, targetEvent := range commonApplicationEventMap { + sourceEvent := sourceEvent + targetEvent := targetEvent + i.parent.Event.OnApplicationEvent(sourceEvent, func(event *ApplicationEvent) { + event.Id = uint(targetEvent) + // Log the forwarding so we can see every emitted event in iOS NSLog + iosConsoleLogf("info", " [events_common_ios.go] Forwarding iOS event %d → common %d", sourceEvent, targetEvent) + applicationEvents <- event + }) + } +} \ No newline at end of file diff --git a/v3/pkg/application/events_common_linux.go b/v3/pkg/application/events_common_linux.go new file mode 100644 index 000000000..ddff94b7f --- /dev/null +++ b/v3/pkg/application/events_common_linux.go @@ -0,0 +1,21 @@ +//go:build linux && !android && !server + +package application + +import "github.com/wailsapp/wails/v3/pkg/events" + +var commonApplicationEventMap = map[events.ApplicationEventType]events.ApplicationEventType{ + events.Linux.ApplicationStartup: events.Common.ApplicationStarted, + events.Linux.SystemThemeChanged: events.Common.ThemeChanged, +} + +func (a *linuxApp) setupCommonEvents() { + for sourceEvent, targetEvent := range commonApplicationEventMap { + sourceEvent := sourceEvent + targetEvent := targetEvent + a.parent.Event.OnApplicationEvent(sourceEvent, func(event *ApplicationEvent) { + event.Id = uint(targetEvent) + applicationEvents <- event + }) + } +} diff --git a/v3/pkg/application/events_common_server.go b/v3/pkg/application/events_common_server.go new file mode 100644 index 000000000..bde9941eb --- /dev/null +++ b/v3/pkg/application/events_common_server.go @@ -0,0 +1,10 @@ +//go:build server + +package application + +// setupCommonEvents sets up common application events for server mode. +// In server mode, there are no platform-specific events to map, +// so this is a no-op. +func (h *serverApp) setupCommonEvents() { + // No-op: server mode has no platform-specific events to map +} diff --git a/v3/pkg/application/events_common_windows.go b/v3/pkg/application/events_common_windows.go new file mode 100644 index 000000000..1c4dd55e6 --- /dev/null +++ b/v3/pkg/application/events_common_windows.go @@ -0,0 +1,21 @@ +//go:build windows + +package application + +import "github.com/wailsapp/wails/v3/pkg/events" + +var commonApplicationEventMap = map[events.ApplicationEventType]events.ApplicationEventType{ + events.Windows.SystemThemeChanged: events.Common.ThemeChanged, + events.Windows.ApplicationStarted: events.Common.ApplicationStarted, +} + +func (m *windowsApp) setupCommonEvents() { + for sourceEvent, targetEvent := range commonApplicationEventMap { + sourceEvent := sourceEvent + targetEvent := targetEvent + m.parent.Event.OnApplicationEvent(sourceEvent, func(event *ApplicationEvent) { + event.Id = uint(targetEvent) + applicationEvents <- event + }) + } +} diff --git a/v3/pkg/application/events_loose.go b/v3/pkg/application/events_loose.go new file mode 100644 index 000000000..f00715c51 --- /dev/null +++ b/v3/pkg/application/events_loose.go @@ -0,0 +1,7 @@ +//go:build production || !strictevents + +package application + +func eventRegistered(name string) {} + +func warnAboutUnregisteredEvent(name string) {} diff --git a/v3/pkg/application/events_strict.go b/v3/pkg/application/events_strict.go new file mode 100644 index 000000000..adc19dbd3 --- /dev/null +++ b/v3/pkg/application/events_strict.go @@ -0,0 +1,27 @@ +//go:build !production && strictevents + +package application + +import ( + "sync" +) + +var knownUnregisteredEvents sync.Map + +func eventRegistered(name string) { + knownUnregisteredEvents.Delete(name) +} + +func warnAboutUnregisteredEvent(name string) { + // Perform a load first to avoid thrashing the map with unnecessary swaps. + if _, known := knownUnregisteredEvents.Load(name); known { + return + } + + // Perform a swap to synchronize with concurrent emissions. + if _, known := knownUnregisteredEvents.Swap(name, true); known { + return + } + + globalApplication.warning("unregistered event name '%s'", name) +} diff --git a/v3/pkg/application/events_test.go b/v3/pkg/application/events_test.go new file mode 100644 index 000000000..c1e621d9c --- /dev/null +++ b/v3/pkg/application/events_test.go @@ -0,0 +1,135 @@ +package application_test + +import ( + "sync" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + + "github.com/matryer/is" +) + +type mockNotifier struct { + Events []*application.CustomEvent +} + +func (m *mockNotifier) dispatchEventToWindows(event *application.CustomEvent) { + m.Events = append(m.Events, event) +} + +func (m *mockNotifier) Reset() { + m.Events = []*application.CustomEvent{} +} + +func Test_EventsOn(t *testing.T) { + i := is.New(t) + notifier := &mockNotifier{} + eventProcessor := application.NewWailsEventProcessor(notifier.dispatchEventToWindows) + + // Test OnApplicationEvent + eventName := "test" + counter := 0 + var wg sync.WaitGroup + wg.Add(1) + unregisterFn := eventProcessor.On(eventName, func(event *application.CustomEvent) { + // This is called in a goroutine + counter++ + wg.Done() + }) + _ = eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + wg.Wait() + i.Equal(1, counter) + + // Unregister + notifier.Reset() + unregisterFn() + counter = 0 + _ = eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + i.Equal(0, counter) + +} + +func Test_EventsOnce(t *testing.T) { + i := is.New(t) + notifier := &mockNotifier{} + eventProcessor := application.NewWailsEventProcessor(notifier.dispatchEventToWindows) + + // Test OnApplicationEvent + eventName := "test" + counter := 0 + var wg sync.WaitGroup + wg.Add(1) + unregisterFn := eventProcessor.Once(eventName, func(event *application.CustomEvent) { + // This is called in a goroutine + counter++ + wg.Done() + }) + _ = eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + _ = eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + wg.Wait() + i.Equal(1, counter) + + // Unregister + notifier.Reset() + unregisterFn() + counter = 0 + _ = eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + i.Equal(0, counter) + +} +func Test_EventsOnMultiple(t *testing.T) { + i := is.New(t) + notifier := &mockNotifier{} + eventProcessor := application.NewWailsEventProcessor(notifier.dispatchEventToWindows) + + // Test OnApplicationEvent + eventName := "test" + counter := 0 + var wg sync.WaitGroup + wg.Add(2) + unregisterFn := eventProcessor.OnMultiple(eventName, func(event *application.CustomEvent) { + // This is called in a goroutine + counter++ + wg.Done() + }, 2) + _ = eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + _ = eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + _ = eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + wg.Wait() + i.Equal(2, counter) + + // Unregister + notifier.Reset() + unregisterFn() + counter = 0 + _ = eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + i.Equal(0, counter) + +} diff --git a/v3/pkg/application/image.go b/v3/pkg/application/image.go new file mode 100644 index 000000000..81c519739 --- /dev/null +++ b/v3/pkg/application/image.go @@ -0,0 +1,37 @@ +package application + +import ( + "bytes" + "image" + "image/draw" + "image/png" +) + +func pngToImage(data []byte) (*image.RGBA, error) { + img, err := png.Decode(bytes.NewReader(data)) + if err != nil { + return nil, err + } + + bounds := img.Bounds() + rgba := image.NewRGBA(bounds) + draw.Draw(rgba, bounds, img, bounds.Min, draw.Src) + return rgba, nil +} + +func ToARGB(img *image.RGBA) (int, int, []byte) { + w, h := img.Bounds().Dx(), img.Bounds().Dy() + data := make([]byte, w*h*4) + i := 0 + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + r, g, b, a := img.At(x, y).RGBA() + data[i] = byte(a) + data[i+1] = byte(r) + data[i+2] = byte(g) + data[i+3] = byte(b) + i += 4 + } + } + return w, h, data +} diff --git a/v3/pkg/application/init_android.go b/v3/pkg/application/init_android.go new file mode 100644 index 000000000..6c50e82c6 --- /dev/null +++ b/v3/pkg/application/init_android.go @@ -0,0 +1,9 @@ +//go:build android + +package application + +func init() { + // On Android, we don't call runtime.LockOSThread() + // The Android runtime handles thread management via JNI + // and calling LockOSThread can interfere with the JNI environment +} diff --git a/v3/pkg/application/init_desktop.go b/v3/pkg/application/init_desktop.go new file mode 100644 index 000000000..a840fed53 --- /dev/null +++ b/v3/pkg/application/init_desktop.go @@ -0,0 +1,11 @@ +//go:build !ios + +package application + +import "runtime" + +func init() { + // Lock the main thread for desktop platforms + // This ensures UI operations happen on the main thread + runtime.LockOSThread() +} \ No newline at end of file diff --git a/v3/pkg/application/init_ios.go b/v3/pkg/application/init_ios.go new file mode 100644 index 000000000..63c054294 --- /dev/null +++ b/v3/pkg/application/init_ios.go @@ -0,0 +1,9 @@ +//go:build ios + +package application + +func init() { + // On iOS, we don't call runtime.LockOSThread() + // The iOS runtime handles thread management differently + // and calling LockOSThread can interfere with signal handling +} diff --git a/v3/pkg/application/internal/tests/services/common.go b/v3/pkg/application/internal/tests/services/common.go new file mode 100644 index 000000000..9d35da69e --- /dev/null +++ b/v3/pkg/application/internal/tests/services/common.go @@ -0,0 +1,168 @@ +package services + +import ( + "context" + "fmt" + "sync/atomic" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Config struct { + Id int + T *testing.T + Seq *atomic.Int64 + Options application.ServiceOptions + StartupErr bool + ShutdownErr bool +} + +func Configure[T any, P interface { + *T + Configure(Config) +}](srv P, c Config) application.Service { + srv.Configure(c) + return application.NewServiceWithOptions(srv, c.Options) +} + +type Error struct { + Id int +} + +func (e *Error) Error() string { + return fmt.Sprintf("service #%d mock failure", e.Id) +} + +type Startupper struct { + Config + startup int64 +} + +func (s *Startupper) Configure(c Config) { + s.Config = c +} + +func (s *Startupper) Id() int { + return s.Config.Id +} + +func (s *Startupper) StartupSeq() int64 { + return s.startup +} + +func (s *Startupper) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + if s.startup != 0 { + s.T.Errorf("Double startup for service #%d: first at seq=%d, then at seq=%d", s.Id(), s.startup, s.Seq.Load()) + return nil + } + + s.startup = s.Seq.Add(1) + + if diff := cmp.Diff(s.Options, options); diff != "" { + s.T.Errorf("Options mismatch for service #%d (-want +got):\n%s", s.Id(), diff) + } + + if s.StartupErr { + return &Error{Id: s.Id()} + } else { + return nil + } +} + +type Shutdowner struct { + Config + shutdown int64 +} + +func (s *Shutdowner) Configure(c Config) { + s.Config = c +} + +func (s *Shutdowner) Id() int { + return s.Config.Id +} + +func (s *Shutdowner) ShutdownSeq() int64 { + return s.shutdown +} + +func (s *Shutdowner) ServiceShutdown() error { + if s.shutdown != 0 { + s.T.Errorf("Double shutdown for service #%d: first at seq=%d, then at seq=%d", s.Id(), s.shutdown, s.Seq.Load()) + return nil + } + + s.shutdown = s.Seq.Add(1) + + if s.ShutdownErr { + return &Error{Id: s.Id()} + } else { + return nil + } +} + +type StartupShutdowner struct { + Config + startup int64 + shutdown int64 + ctx context.Context +} + +func (s *StartupShutdowner) Configure(c Config) { + s.Config = c +} + +func (s *StartupShutdowner) Id() int { + return s.Config.Id +} + +func (s *StartupShutdowner) StartupSeq() int64 { + return s.startup +} + +func (s *StartupShutdowner) ShutdownSeq() int64 { + return s.shutdown +} + +func (s *StartupShutdowner) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + if s.startup != 0 { + s.T.Errorf("Double startup for service #%d: first at seq=%d, then at seq=%d", s.Id(), s.startup, s.Seq.Load()) + return nil + } + + s.startup = s.Seq.Add(1) + s.ctx = ctx + + if diff := cmp.Diff(s.Options, options); diff != "" { + s.T.Errorf("Options mismatch for service #%d (-want +got):\n%s", s.Id(), diff) + } + + if s.StartupErr { + return &Error{Id: s.Id()} + } else { + return nil + } +} + +func (s *StartupShutdowner) ServiceShutdown() error { + if s.shutdown != 0 { + s.T.Errorf("Double shutdown for service #%d: first at seq=%d, then at seq=%d", s.Id(), s.shutdown, s.Seq.Load()) + return nil + } + + s.shutdown = s.Seq.Add(1) + + select { + case <-s.ctx.Done(): + default: + s.T.Errorf("Service #%d shut down before context cancellation", s.Id()) + } + + if s.ShutdownErr { + return &Error{Id: s.Id()} + } else { + return nil + } +} diff --git a/v3/pkg/application/internal/tests/services/shutdown/shutdown_test.go b/v3/pkg/application/internal/tests/services/shutdown/shutdown_test.go new file mode 100644 index 000000000..ad8df141c --- /dev/null +++ b/v3/pkg/application/internal/tests/services/shutdown/shutdown_test.go @@ -0,0 +1,92 @@ +package shutdown + +import ( + "sync" + "sync/atomic" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests" + svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func TestMain(m *testing.M) { + apptest.Main(m) +} + +type ( + Service1 struct{ svctest.Shutdowner } + Service2 struct{ svctest.Shutdowner } + Service3 struct{ svctest.Shutdowner } + Service4 struct{ svctest.Shutdowner } + Service5 struct{ svctest.Shutdowner } + Service6 struct{ svctest.Shutdowner } +) + +func TestServiceShutdown(t *testing.T) { + var seq atomic.Int64 + + services := []application.Service{ + svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}), + svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq}), + svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}), + svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq}), + svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq}), + svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq}), + } + + app := apptest.New(t, application.Options{ + Services: services[:3], + }) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + app.RegisterService(services[3]) + wg.Done() + }() + go func() { + app.RegisterService(services[4]) + wg.Done() + }() + wg.Wait() + + app.RegisterService(services[5]) + + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) { + app.Quit() + }) + + err := apptest.Run(t, app) + if err != nil { + t.Fatal(err) + } + + if count := seq.Load(); count != int64(len(services)) { + t.Errorf("Wrong shutdown call count: wanted %d, got %d", len(services), count) + } + + validate(t, services[0], 5) + validate(t, services[1], 4) + validate(t, services[2], 2, 3) + validate(t, services[3], 1) + validate(t, services[4], 1) + validate(t, services[5], 0) +} + +func validate(t *testing.T, svc application.Service, prev ...int64) { + id := svc.Instance().(interface{ Id() int }).Id() + seq := svc.Instance().(interface{ ShutdownSeq() int64 }).ShutdownSeq() + + if seq == 0 { + t.Errorf("Service #%d did not shut down", id) + return + } + + for _, p := range prev { + if seq <= p { + t.Errorf("Wrong shutdown sequence number for service #%d: wanted >%d, got %d", id, p, seq) + } + } +} diff --git a/v3/pkg/application/internal/tests/services/shutdownerror/shutdownerror_test.go b/v3/pkg/application/internal/tests/services/shutdownerror/shutdownerror_test.go new file mode 100644 index 000000000..42949b037 --- /dev/null +++ b/v3/pkg/application/internal/tests/services/shutdownerror/shutdownerror_test.go @@ -0,0 +1,123 @@ +package shutdownerror + +import ( + "errors" + "slices" + "sync" + "sync/atomic" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests" + svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func TestMain(m *testing.M) { + apptest.Main(m) +} + +type ( + Service1 struct{ svctest.Shutdowner } + Service2 struct{ svctest.Shutdowner } + Service3 struct{ svctest.Shutdowner } + Service4 struct{ svctest.Shutdowner } + Service5 struct{ svctest.Shutdowner } + Service6 struct{ svctest.Shutdowner } +) + +func TestServiceShutdownError(t *testing.T) { + var seq atomic.Int64 + + services := []application.Service{ + svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}), + svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq, ShutdownErr: true}), + svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}), + svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq}), + svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq, ShutdownErr: true}), + svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq, ShutdownErr: true}), + } + + expectedShutdownErrors := []int{5, 4, 1} + var errCount atomic.Int64 + + var app *application.App + app = apptest.New(t, application.Options{ + Services: services[:3], + ErrorHandler: func(err error) { + var mock *svctest.Error + if !errors.As(err, &mock) { + app.Logger.Error(err.Error()) + return + } + + i := int(errCount.Add(1) - 1) + if i < len(expectedShutdownErrors) && mock.Id == expectedShutdownErrors[i] { + return + } + + cut := min(i, len(expectedShutdownErrors)) + if slices.Contains(expectedShutdownErrors[:cut], mock.Id) { + t.Errorf("Late or duplicate shutdown error for service #%d", mock.Id) + } else if slices.Contains(expectedShutdownErrors[cut:], mock.Id) { + t.Errorf("Early shutdown error for service #%d", mock.Id) + } else { + t.Errorf("Unexpected shutdown error for service #%d", mock.Id) + } + }, + }) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + app.RegisterService(services[3]) + wg.Done() + }() + go func() { + app.RegisterService(services[4]) + wg.Done() + }() + wg.Wait() + + app.RegisterService(services[5]) + + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) { + app.Quit() + }) + + err := apptest.Run(t, app) + if err != nil { + t.Fatal(err) + } + + if ec := errCount.Load(); ec != int64(len(expectedShutdownErrors)) { + t.Errorf("Wrong shutdown error count: wanted %d, got %d", len(expectedShutdownErrors), ec) + } + + if count := seq.Load(); count != int64(len(services)) { + t.Errorf("Wrong shutdown call count: wanted %d, got %d", len(services), count) + } + + validate(t, services[0], 5) + validate(t, services[1], 4) + validate(t, services[2], 2, 3) + validate(t, services[3], 1) + validate(t, services[4], 1) + validate(t, services[5], 0) +} + +func validate(t *testing.T, svc application.Service, prev ...int64) { + id := svc.Instance().(interface{ Id() int }).Id() + seq := svc.Instance().(interface{ ShutdownSeq() int64 }).ShutdownSeq() + + if seq == 0 { + t.Errorf("Service #%d did not shut down", id) + return + } + + for _, p := range prev { + if seq <= p { + t.Errorf("Wrong shutdown sequence number for service #%d: wanted >%d, got %d", id, p, seq) + } + } +} diff --git a/v3/pkg/application/internal/tests/services/startup/startup_test.go b/v3/pkg/application/internal/tests/services/startup/startup_test.go new file mode 100644 index 000000000..bf5c2ac96 --- /dev/null +++ b/v3/pkg/application/internal/tests/services/startup/startup_test.go @@ -0,0 +1,102 @@ +package startup + +import ( + "sync" + "sync/atomic" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests" + svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func TestMain(m *testing.M) { + apptest.Main(m) +} + +type ( + Service1 struct{ svctest.Startupper } + Service2 struct{ svctest.Startupper } + Service3 struct{ svctest.Startupper } + Service4 struct{ svctest.Startupper } + Service5 struct{ svctest.Startupper } + Service6 struct{ svctest.Startupper } +) + +func TestServiceStartup(t *testing.T) { + var seq atomic.Int64 + + services := []application.Service{ + svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}), + svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq, Options: application.ServiceOptions{ + Name: "I am service 2", + }}), + svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq, Options: application.ServiceOptions{ + Route: "/mounted/here", + }}), + svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq}), + svctest.Configure(&Service5{}, svctest.Config{Id: 4, Seq: &seq, Options: application.ServiceOptions{ + Name: "I am service 5", + Route: "/mounted/there", + }}), + svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq, Options: application.ServiceOptions{ + Name: "I am service 6", + Route: "/mounted/elsewhere", + }}), + } + + app := apptest.New(t, application.Options{ + Services: services[:3], + }) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + app.RegisterService(services[3]) + wg.Done() + }() + go func() { + app.RegisterService(services[4]) + wg.Done() + }() + wg.Wait() + + app.RegisterService(services[5]) + + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) { + app.Quit() + }) + + err := apptest.Run(t, app) + if err != nil { + t.Fatal(err) + } + + if count := seq.Load(); count != int64(len(services)) { + t.Errorf("Wrong startup call count: wanted %d, got %d", len(services), count) + } + + validate(t, services[0], 0) + validate(t, services[1], 1) + validate(t, services[2], 2) + validate(t, services[3], 3) + validate(t, services[4], 3) + validate(t, services[5], 4, 5) +} + +func validate(t *testing.T, svc application.Service, prev ...int64) { + id := svc.Instance().(interface{ Id() int }).Id() + seq := svc.Instance().(interface{ StartupSeq() int64 }).StartupSeq() + + if seq == 0 { + t.Errorf("Service #%d did not start up", id) + return + } + + for _, p := range prev { + if seq <= p { + t.Errorf("Wrong startup sequence number for service #%d: wanted >%d, got %d", id, p, seq) + } + } +} diff --git a/v3/pkg/application/internal/tests/services/startuperror/startuperror_test.go b/v3/pkg/application/internal/tests/services/startuperror/startuperror_test.go new file mode 100644 index 000000000..a4a3c5bc4 --- /dev/null +++ b/v3/pkg/application/internal/tests/services/startuperror/startuperror_test.go @@ -0,0 +1,114 @@ +package startuperror + +import ( + "errors" + "sync" + "sync/atomic" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests" + svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func TestMain(m *testing.M) { + apptest.Main(m) +} + +type ( + Service1 struct{ svctest.Startupper } + Service2 struct{ svctest.Startupper } + Service3 struct{ svctest.Startupper } + Service4 struct{ svctest.Startupper } + Service5 struct{ svctest.Startupper } + Service6 struct{ svctest.Startupper } +) + +func TestServiceStartupError(t *testing.T) { + var seq atomic.Int64 + + services := []application.Service{ + svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}), + svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq}), + svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}), + svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq, StartupErr: true}), + svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq, StartupErr: true}), + svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq, StartupErr: true}), + } + + app := apptest.New(t, application.Options{ + Services: services[:3], + }) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + app.RegisterService(services[3]) + wg.Done() + }() + go func() { + app.RegisterService(services[4]) + wg.Done() + }() + wg.Wait() + + app.RegisterService(services[5]) + + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) { + t.Errorf("Application started") + app.Quit() + }) + + var mock *svctest.Error + + err := apptest.Run(t, app) + if err != nil { + if !errors.As(err, &mock) { + t.Fatal(err) + } + } + + if mock == nil { + t.Fatal("Wanted startup error for service #3 or #4, got none") + } else if mock.Id != 3 && mock.Id != 4 { + t.Errorf("Wanted startup error for service #3 or #4, got #%d", mock.Id) + } + + if count := seq.Load(); count != 4 { + t.Errorf("Wrong startup call count: wanted %d, got %d", 4, count) + } + + validate(t, services[0], 0) + validate(t, services[1], 1) + validate(t, services[2], 2) + validate(t, services[mock.Id], 3) + + notStarted := 3 + if mock.Id == 3 { + notStarted = 4 + } + + if seq := services[notStarted].Instance().(interface{ StartupSeq() int64 }).StartupSeq(); seq != 0 { + t.Errorf("Service #%d started up unexpectedly at seq=%d", notStarted, seq) + } + if seq := services[5].Instance().(interface{ StartupSeq() int64 }).StartupSeq(); seq != 0 { + t.Errorf("Service #5 started up unexpectedly at seq=%d", seq) + } +} + +func validate(t *testing.T, svc application.Service, prev ...int64) { + id := svc.Instance().(interface{ Id() int }).Id() + seq := svc.Instance().(interface{ StartupSeq() int64 }).StartupSeq() + + if seq == 0 { + t.Errorf("Service #%d did not start up", id) + return + } + + for _, p := range prev { + if seq <= p { + t.Errorf("Wrong startup sequence number for service #%d: wanted >%d, got %d", id, p, seq) + } + } +} diff --git a/v3/pkg/application/internal/tests/services/startupshutdown/startupshutdown_test.go b/v3/pkg/application/internal/tests/services/startupshutdown/startupshutdown_test.go new file mode 100644 index 000000000..5f8cfc365 --- /dev/null +++ b/v3/pkg/application/internal/tests/services/startupshutdown/startupshutdown_test.go @@ -0,0 +1,102 @@ +package startupshutdown + +import ( + "sync" + "sync/atomic" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests" + svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func TestMain(m *testing.M) { + apptest.Main(m) +} + +type ( + Service1 struct{ svctest.StartupShutdowner } + Service2 struct{ svctest.StartupShutdowner } + Service3 struct{ svctest.StartupShutdowner } + Service4 struct{ svctest.StartupShutdowner } + Service5 struct{ svctest.StartupShutdowner } + Service6 struct{ svctest.StartupShutdowner } +) + +func TestServiceStartupShutdown(t *testing.T) { + var seq atomic.Int64 + + services := []application.Service{ + svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}), + svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq}), + svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}), + svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq}), + svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq}), + svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq}), + } + + app := apptest.New(t, application.Options{ + Services: services[:3], + }) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + app.RegisterService(services[3]) + wg.Done() + }() + go func() { + app.RegisterService(services[4]) + wg.Done() + }() + wg.Wait() + + app.RegisterService(services[5]) + + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) { + if count := seq.Load(); count != int64(len(services)) { + t.Errorf("Wrong startup call count: wanted %d, got %d", len(services), count) + } + seq.Store(0) + app.Quit() + }) + + err := apptest.Run(t, app) + if err != nil { + t.Fatal(err) + } + + if count := seq.Load(); count != int64(len(services)) { + t.Errorf("Wrong shutdown call count: wanted %d, got %d", len(services), count) + } + + bound := int64(len(services)) + 1 + validate(t, services[0], bound) + validate(t, services[1], bound) + validate(t, services[2], bound) + validate(t, services[3], bound) + validate(t, services[4], bound) + validate(t, services[5], bound) +} + +func validate(t *testing.T, svc application.Service, bound int64) { + id := svc.Instance().(interface{ Id() int }).Id() + startup := svc.Instance().(interface{ StartupSeq() int64 }).StartupSeq() + shutdown := svc.Instance().(interface{ ShutdownSeq() int64 }).ShutdownSeq() + + if startup == 0 && shutdown == 0 { + t.Errorf("Service #%d did not start nor shut down", id) + return + } else if startup == 0 { + t.Errorf("Service #%d started, but did not shut down", id) + return + } else if shutdown == 0 { + t.Errorf("Service #%d shut down, but did not start", id) + return + } + + if shutdown != bound-startup { + t.Errorf("Wrong sequence numbers for service #%d: wanted either %d..%d or %d..%d, got %d..%d", id, startup, bound-startup, bound-shutdown, shutdown, startup, shutdown) + } +} diff --git a/v3/pkg/application/internal/tests/services/startupshutdownerror/startupshutdownerror_test.go b/v3/pkg/application/internal/tests/services/startupshutdownerror/startupshutdownerror_test.go new file mode 100644 index 000000000..0ca135269 --- /dev/null +++ b/v3/pkg/application/internal/tests/services/startupshutdownerror/startupshutdownerror_test.go @@ -0,0 +1,140 @@ +package startupshutdownerror + +import ( + "errors" + "slices" + "sync" + "sync/atomic" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests" + svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func TestMain(m *testing.M) { + apptest.Main(m) +} + +type ( + Service1 struct{ svctest.StartupShutdowner } + Service2 struct{ svctest.StartupShutdowner } + Service3 struct{ svctest.StartupShutdowner } + Service4 struct{ svctest.StartupShutdowner } + Service5 struct{ svctest.StartupShutdowner } + Service6 struct{ svctest.StartupShutdowner } +) + +func TestServiceStartupShutdownError(t *testing.T) { + var seq atomic.Int64 + + services := []application.Service{ + svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}), + svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq, ShutdownErr: true}), + svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}), + svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq, StartupErr: true, ShutdownErr: true}), + svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq, StartupErr: true, ShutdownErr: true}), + svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq, StartupErr: true, ShutdownErr: true}), + } + + expectedShutdownErrors := []int{1} + var errCount atomic.Int64 + + var app *application.App + app = apptest.New(t, application.Options{ + Services: services[:3], + ErrorHandler: func(err error) { + var mock *svctest.Error + if !errors.As(err, &mock) { + app.Logger.Error(err.Error()) + return + } + + i := int(errCount.Add(1) - 1) + if i < len(expectedShutdownErrors) && mock.Id == expectedShutdownErrors[i] { + return + } + + cut := min(i, len(expectedShutdownErrors)) + if slices.Contains(expectedShutdownErrors[:cut], mock.Id) { + t.Errorf("Late or duplicate shutdown error for service #%d", mock.Id) + } else if slices.Contains(expectedShutdownErrors[cut:], mock.Id) { + t.Errorf("Early shutdown error for service #%d", mock.Id) + } else { + t.Errorf("Unexpected shutdown error for service #%d", mock.Id) + } + }, + }) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + app.RegisterService(services[3]) + wg.Done() + }() + go func() { + app.RegisterService(services[4]) + wg.Done() + }() + wg.Wait() + + app.RegisterService(services[5]) + + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) { + t.Errorf("Application started") + app.Quit() + }) + + var mock *svctest.Error + + err := apptest.Run(t, app) + if err != nil { + if !errors.As(err, &mock) { + t.Fatal(err) + } + } + + if mock == nil { + t.Fatal("Wanted error for service #3 or #4, got none") + } else if mock.Id != 3 && mock.Id != 4 { + t.Errorf("Wanted error for service #3 or #4, got #%d", mock.Id) + } + + if ec := errCount.Load(); ec != int64(len(expectedShutdownErrors)) { + t.Errorf("Wrong shutdown error count: wanted %d, got %d", len(expectedShutdownErrors), ec) + } + + if count := seq.Load(); count != 4+3 { + t.Errorf("Wrong startup+shutdown call count: wanted %d+%d, got %d", 4, 3, count) + } + + validate(t, services[0], true, true) + validate(t, services[1], true, true) + validate(t, services[2], true, true) + validate(t, services[3], mock.Id == 3, false) + validate(t, services[4], mock.Id == 4, false) + validate(t, services[5], false, false) +} + +func validate(t *testing.T, svc application.Service, startup bool, shutdown bool) { + id := svc.Instance().(interface{ Id() int }).Id() + startupSeq := svc.Instance().(interface{ StartupSeq() int64 }).StartupSeq() + shutdownSeq := svc.Instance().(interface{ ShutdownSeq() int64 }).ShutdownSeq() + + if startup != (startupSeq != 0) { + if startupSeq == 0 { + t.Errorf("Service #%d did not start up", id) + } else { + t.Errorf("Unexpected startup for service #%d at seq=%d", id, startupSeq) + } + } + + if shutdown != (shutdownSeq != 0) { + if shutdownSeq == 0 { + t.Errorf("Service #%d did not shut down", id) + } else { + t.Errorf("Unexpected shutdown for service #%d at seq=%d", id, shutdownSeq) + } + } +} diff --git a/v3/pkg/application/internal/tests/utils.go b/v3/pkg/application/internal/tests/utils.go new file mode 100644 index 000000000..ef94ef5ba --- /dev/null +++ b/v3/pkg/application/internal/tests/utils.go @@ -0,0 +1,74 @@ +package tests + +import ( + "errors" + "os" + "runtime" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +var appChan chan *application.App = make(chan *application.App, 1) +var errChan chan error = make(chan error, 1) +var endChan chan error = make(chan error, 1) + +func init() { runtime.LockOSThread() } + +func New(t *testing.T, options application.Options) *application.App { + var app *application.App + + app = application.Get() + if app != nil { + return app + } + + if options.Name == "" { + options.Name = t.Name() + } + + errorHandler := options.ErrorHandler + options.ErrorHandler = func(err error) { + if fatal := (*application.FatalError)(nil); errors.As(err, &fatal) { + endChan <- err + select {} // Block forever + } else if errorHandler != nil { + errorHandler(err) + } else { + app.Logger.Error(err.Error()) + } + } + + postShutdown := options.PostShutdown + options.PostShutdown = func() { + if postShutdown != nil { + postShutdown() + } + endChan <- nil + select {} // Block forever + } + + return application.New(options) +} + +func Run(t *testing.T, app *application.App) error { + appChan <- app + select { + case err := <-errChan: + return err + case fatal := <-endChan: + if fatal != nil { + t.Fatal(fatal) + } + return fatal + } +} + +func Main(m *testing.M) { + go func() { + os.Exit(m.Run()) + }() + + errChan <- (<-appChan).Run() + select {} // Block forever +} diff --git a/v3/pkg/application/ios_runtime_api.go b/v3/pkg/application/ios_runtime_api.go new file mode 100644 index 000000000..54434a0ee --- /dev/null +++ b/v3/pkg/application/ios_runtime_api.go @@ -0,0 +1,14 @@ +//go:build ios + +package application + +// Exported API for use by applications to mutate iOS WKWebView at runtime. +// These call into the internal platform-specific implementations. + +func IOSSetScrollEnabled(enabled bool) { iosSetScrollEnabled(enabled) } +func IOSSetBounceEnabled(enabled bool) { iosSetBounceEnabled(enabled) } +func IOSSetScrollIndicatorsEnabled(enabled bool) { iosSetScrollIndicatorsEnabled(enabled) } +func IOSSetBackForwardGesturesEnabled(enabled bool) { iosSetBackForwardGesturesEnabled(enabled) } +func IOSSetLinkPreviewEnabled(enabled bool) { iosSetLinkPreviewEnabled(enabled) } +func IOSSetInspectableEnabled(enabled bool) { iosSetInspectableEnabled(enabled) } +func IOSSetCustomUserAgent(ua string) { iosSetCustomUserAgent(ua) } diff --git a/v3/pkg/application/ios_runtime_ios.go b/v3/pkg/application/ios_runtime_ios.go new file mode 100644 index 000000000..6a388d9a9 --- /dev/null +++ b/v3/pkg/application/ios_runtime_ios.go @@ -0,0 +1,79 @@ +//go:build ios + +package application + +/* +#cgo CFLAGS: -x objective-c -fmodules -fobjc-arc +#cgo LDFLAGS: -framework UIKit +#include +#include "application_ios.h" +*/ +import "C" +import ( + "unsafe" + + "encoding/json" +) + +// iosHapticsImpact triggers an iOS haptic impact using the provided style. +// The style parameter specifies the impact style name understood by the native haptic engine. +func iosHapticsImpact(style string) { + cstr := C.CString(style) + defer C.free(unsafe.Pointer(cstr)) + C.ios_haptics_impact(cstr) +} + +type deviceInfo struct { + Model string `json:"model"` + SystemName string `json:"systemName"` + SystemVersion string `json:"systemVersion"` + IsSimulator bool `json:"isSimulator"` +} + +func iosDeviceInfo() deviceInfo { + ptr := C.ios_device_info_json() + if ptr == nil { + return deviceInfo{} + } + defer C.free(unsafe.Pointer(ptr)) + goStr := C.GoString(ptr) + var out deviceInfo + _ = json.Unmarshal([]byte(goStr), &out) + return out +} + +// iosSetScrollEnabled sets whether scrolling is enabled in the iOS runtime. +func iosSetScrollEnabled(enabled bool) { C.ios_runtime_set_scroll_enabled(C.bool(enabled)) } +// iosSetBounceEnabled sets whether scroll bounce (rubber-band) behavior is enabled at runtime. +// If enabled is true, scrollable content will bounce when pulled past its edges; if false, that bounce is disabled. +func iosSetBounceEnabled(enabled bool) { C.ios_runtime_set_bounce_enabled(C.bool(enabled)) } +// iosSetScrollIndicatorsEnabled configures whether the iOS runtime shows scroll indicators. +// The enabled parameter controls visibility: true shows indicators, false hides them. +func iosSetScrollIndicatorsEnabled(enabled bool) { + C.ios_runtime_set_scroll_indicators_enabled(C.bool(enabled)) +} +// iosSetBackForwardGesturesEnabled enables back-forward navigation gestures when enabled is true and disables them when enabled is false. +func iosSetBackForwardGesturesEnabled(enabled bool) { + C.ios_runtime_set_back_forward_gestures_enabled(C.bool(enabled)) +} +// iosSetLinkPreviewEnabled sets whether link previews are enabled in the iOS runtime. +// Pass true to enable link previews, false to disable them. +func iosSetLinkPreviewEnabled(enabled bool) { C.ios_runtime_set_link_preview_enabled(C.bool(enabled)) } +// iosSetInspectableEnabled sets whether runtime web content inspection is enabled. +// When enabled is true the runtime allows inspection of web content; when false inspection is disabled. +func iosSetInspectableEnabled(enabled bool) { C.ios_runtime_set_inspectable_enabled(C.bool(enabled)) } +// iosSetCustomUserAgent sets the runtime's custom User-Agent string. +// If ua is an empty string, the custom User-Agent is cleared. +func iosSetCustomUserAgent(ua string) { + var cstr *C.char + if ua != "" { + cstr = C.CString(ua) + defer C.free(unsafe.Pointer(cstr)) + } + C.ios_runtime_set_custom_user_agent(cstr) +} + +// Native tabs +func iosSetNativeTabsEnabled(enabled bool) { C.ios_native_tabs_set_enabled(C.bool(enabled)) } +func iosNativeTabsIsEnabled() bool { return bool(C.ios_native_tabs_is_enabled()) } +func iosSelectNativeTab(index int) { C.ios_native_tabs_select_index(C.int(index)) } \ No newline at end of file diff --git a/v3/pkg/application/ios_runtime_stub.go b/v3/pkg/application/ios_runtime_stub.go new file mode 100644 index 000000000..09de75b56 --- /dev/null +++ b/v3/pkg/application/ios_runtime_stub.go @@ -0,0 +1,27 @@ +//go:build !ios + +package application + +func iosHapticsImpact(style string) { + // no-op on non-iOS +} + +type deviceInfo struct { + Model string `json:"model"` + SystemName string `json:"systemName"` + SystemVersion string `json:"systemVersion"` + IsSimulator bool `json:"isSimulator"` +} + +func iosDeviceInfo() deviceInfo { + return deviceInfo{} +} + +// Live mutation stubs +func iosSetScrollEnabled(enabled bool) {} +func iosSetBounceEnabled(enabled bool) {} +func iosSetScrollIndicatorsEnabled(enabled bool) {} +func iosSetBackForwardGesturesEnabled(enabled bool) {} +func iosSetLinkPreviewEnabled(enabled bool) {} +func iosSetInspectableEnabled(enabled bool) {} +func iosSetCustomUserAgent(ua string) {} diff --git a/v3/pkg/application/json_libs_bench_test.go b/v3/pkg/application/json_libs_bench_test.go new file mode 100644 index 000000000..c3e7e4918 --- /dev/null +++ b/v3/pkg/application/json_libs_bench_test.go @@ -0,0 +1,314 @@ +//go:build bench + +// Disabled: goccy/go-json causes Windows panics. See PR #4859. + +package application_test + +/* +import ( + "encoding/json" + "testing" + + "github.com/bytedance/sonic" + gojson "github.com/goccy/go-json" + jsoniter "github.com/json-iterator/go" +) + +// Test structures matching real Wails binding patterns + +type SimpleBindingArg struct { + Name string `json:"name"` + Value int `json:"value"` +} + +type ComplexBindingArg struct { + ID int `json:"id"` + Name string `json:"name"` + Tags []string `json:"tags"` + Metadata map[string]interface{} `json:"metadata"` + Nested *NestedBindingArg `json:"nested,omitempty"` +} + +type NestedBindingArg struct { + Value float64 `json:"value"` + Enabled bool `json:"enabled"` +} + +// Test data simulating frontend calls +var ( + simpleJSON = []byte(`{"name":"test","value":42}`) + + complexJSON = []byte(`{"id":12345,"name":"Test Complex Data","tags":["tag1","tag2","tag3","tag4","tag5"],"metadata":{"key1":"value1","key2":42,"key3":true},"nested":{"value":3.14159,"enabled":true}}`) + + stringJSON = []byte(`"hello world this is a test string"`) + + multiArgsJSON = [][]byte{ + []byte(`"arg1"`), + []byte(`42`), + []byte(`true`), + []byte(`{"key":"value"}`), + } +) + +// Configure jsoniter for maximum compatibility +var jsoniterStd = jsoniter.ConfigCompatibleWithStandardLibrary + +// ============================================================================ +// UNMARSHAL BENCHMARKS - This is the HOT PATH (bindings.go:289) +// ============================================================================ + +// --- Simple struct unmarshal --- + +func BenchmarkUnmarshal_Simple_StdLib(b *testing.B) { + for b.Loop() { + var arg SimpleBindingArg + _ = json.Unmarshal(simpleJSON, &arg) + } +} + +func BenchmarkUnmarshal_Simple_GoJSON(b *testing.B) { + for b.Loop() { + var arg SimpleBindingArg + _ = gojson.Unmarshal(simpleJSON, &arg) + } +} + +func BenchmarkUnmarshal_Simple_JSONIter(b *testing.B) { + for b.Loop() { + var arg SimpleBindingArg + _ = jsoniterStd.Unmarshal(simpleJSON, &arg) + } +} + +func BenchmarkUnmarshal_Simple_Sonic(b *testing.B) { + for b.Loop() { + var arg SimpleBindingArg + _ = sonic.Unmarshal(simpleJSON, &arg) + } +} + +// --- Complex struct unmarshal --- + +func BenchmarkUnmarshal_Complex_StdLib(b *testing.B) { + for b.Loop() { + var arg ComplexBindingArg + _ = json.Unmarshal(complexJSON, &arg) + } +} + +func BenchmarkUnmarshal_Complex_GoJSON(b *testing.B) { + for b.Loop() { + var arg ComplexBindingArg + _ = gojson.Unmarshal(complexJSON, &arg) + } +} + +func BenchmarkUnmarshal_Complex_JSONIter(b *testing.B) { + for b.Loop() { + var arg ComplexBindingArg + _ = jsoniterStd.Unmarshal(complexJSON, &arg) + } +} + +func BenchmarkUnmarshal_Complex_Sonic(b *testing.B) { + for b.Loop() { + var arg ComplexBindingArg + _ = sonic.Unmarshal(complexJSON, &arg) + } +} + +// --- String unmarshal (most common single arg) --- + +func BenchmarkUnmarshal_String_StdLib(b *testing.B) { + for b.Loop() { + var arg string + _ = json.Unmarshal(stringJSON, &arg) + } +} + +func BenchmarkUnmarshal_String_GoJSON(b *testing.B) { + for b.Loop() { + var arg string + _ = gojson.Unmarshal(stringJSON, &arg) + } +} + +func BenchmarkUnmarshal_String_JSONIter(b *testing.B) { + for b.Loop() { + var arg string + _ = jsoniterStd.Unmarshal(stringJSON, &arg) + } +} + +func BenchmarkUnmarshal_String_Sonic(b *testing.B) { + for b.Loop() { + var arg string + _ = sonic.Unmarshal(stringJSON, &arg) + } +} + +// --- Interface{} unmarshal (dynamic typing) --- + +func BenchmarkUnmarshal_Interface_StdLib(b *testing.B) { + for b.Loop() { + var arg interface{} + _ = json.Unmarshal(complexJSON, &arg) + } +} + +func BenchmarkUnmarshal_Interface_GoJSON(b *testing.B) { + for b.Loop() { + var arg interface{} + _ = gojson.Unmarshal(complexJSON, &arg) + } +} + +func BenchmarkUnmarshal_Interface_JSONIter(b *testing.B) { + for b.Loop() { + var arg interface{} + _ = jsoniterStd.Unmarshal(complexJSON, &arg) + } +} + +func BenchmarkUnmarshal_Interface_Sonic(b *testing.B) { + for b.Loop() { + var arg interface{} + _ = sonic.Unmarshal(complexJSON, &arg) + } +} + +// --- Multi-arg unmarshal (simulating typical method call) --- + +func BenchmarkUnmarshal_MultiArgs_StdLib(b *testing.B) { + for b.Loop() { + var s string + var i int + var bl bool + var m map[string]string + _ = json.Unmarshal(multiArgsJSON[0], &s) + _ = json.Unmarshal(multiArgsJSON[1], &i) + _ = json.Unmarshal(multiArgsJSON[2], &bl) + _ = json.Unmarshal(multiArgsJSON[3], &m) + } +} + +func BenchmarkUnmarshal_MultiArgs_GoJSON(b *testing.B) { + for b.Loop() { + var s string + var i int + var bl bool + var m map[string]string + _ = gojson.Unmarshal(multiArgsJSON[0], &s) + _ = gojson.Unmarshal(multiArgsJSON[1], &i) + _ = gojson.Unmarshal(multiArgsJSON[2], &bl) + _ = gojson.Unmarshal(multiArgsJSON[3], &m) + } +} + +func BenchmarkUnmarshal_MultiArgs_JSONIter(b *testing.B) { + for b.Loop() { + var s string + var i int + var bl bool + var m map[string]string + _ = jsoniterStd.Unmarshal(multiArgsJSON[0], &s) + _ = jsoniterStd.Unmarshal(multiArgsJSON[1], &i) + _ = jsoniterStd.Unmarshal(multiArgsJSON[2], &bl) + _ = jsoniterStd.Unmarshal(multiArgsJSON[3], &m) + } +} + +func BenchmarkUnmarshal_MultiArgs_Sonic(b *testing.B) { + for b.Loop() { + var s string + var i int + var bl bool + var m map[string]string + _ = sonic.Unmarshal(multiArgsJSON[0], &s) + _ = sonic.Unmarshal(multiArgsJSON[1], &i) + _ = sonic.Unmarshal(multiArgsJSON[2], &bl) + _ = sonic.Unmarshal(multiArgsJSON[3], &m) + } +} + +// ============================================================================ +// MARSHAL BENCHMARKS - Result serialization +// ============================================================================ + +type BindingResult struct { + Success bool `json:"success"` + Data interface{} `json:"data,omitempty"` + Error string `json:"error,omitempty"` +} + +var simpleResult = BindingResult{ + Success: true, + Data: "hello world", +} + +var complexResult = BindingResult{ + Success: true, + Data: ComplexBindingArg{ + ID: 12345, + Name: "Result Data", + Tags: []string{"a", "b", "c"}, + Metadata: map[string]interface{}{ + "processed": true, + "count": 100, + }, + Nested: &NestedBindingArg{Value: 2.718, Enabled: true}, + }, +} + +// --- Simple result marshal --- + +func BenchmarkMarshal_Simple_StdLib(b *testing.B) { + for b.Loop() { + _, _ = json.Marshal(simpleResult) + } +} + +func BenchmarkMarshal_Simple_GoJSON(b *testing.B) { + for b.Loop() { + _, _ = gojson.Marshal(simpleResult) + } +} + +func BenchmarkMarshal_Simple_JSONIter(b *testing.B) { + for b.Loop() { + _, _ = jsoniterStd.Marshal(simpleResult) + } +} + +func BenchmarkMarshal_Simple_Sonic(b *testing.B) { + for b.Loop() { + _, _ = sonic.Marshal(simpleResult) + } +} + +// --- Complex result marshal --- + +func BenchmarkMarshal_Complex_StdLib(b *testing.B) { + for b.Loop() { + _, _ = json.Marshal(complexResult) + } +} + +func BenchmarkMarshal_Complex_GoJSON(b *testing.B) { + for b.Loop() { + _, _ = gojson.Marshal(complexResult) + } +} + +func BenchmarkMarshal_Complex_JSONIter(b *testing.B) { + for b.Loop() { + _, _ = jsoniterStd.Marshal(complexResult) + } +} + +func BenchmarkMarshal_Complex_Sonic(b *testing.B) { + for b.Loop() { + _, _ = sonic.Marshal(complexResult) + } +} +*/ diff --git a/v3/pkg/application/json_v2_bench_test.go b/v3/pkg/application/json_v2_bench_test.go new file mode 100644 index 000000000..d7b45193c --- /dev/null +++ b/v3/pkg/application/json_v2_bench_test.go @@ -0,0 +1,270 @@ +//go:build bench && goexperiment.jsonv2 + +package application_test + +import ( + "encoding/json" + "encoding/json/jsontext" + jsonv2 "encoding/json/v2" + "testing" +) + +// Benchmark structures matching real Wails usage patterns + +type SimpleArg struct { + Name string `json:"name"` + Value int `json:"value"` +} + +type ComplexArg struct { + ID int `json:"id"` + Name string `json:"name"` + Tags []string `json:"tags"` + Metadata map[string]interface{} `json:"metadata"` + Nested *NestedArg `json:"nested,omitempty"` +} + +type NestedArg struct { + Value float64 `json:"value"` + Enabled bool `json:"enabled"` +} + +type CallResult struct { + Success bool `json:"success"` + Data interface{} `json:"data,omitempty"` + Error string `json:"error,omitempty"` +} + +// Test data +var ( + simpleArgJSON = []byte(`{"name":"test","value":42}`) + complexArgJSON = []byte(`{ + "id": 12345, + "name": "Test Complex Data", + "tags": ["tag1", "tag2", "tag3", "tag4", "tag5"], + "metadata": {"key1": "value1", "key2": 42, "key3": true}, + "nested": {"value": 3.14159, "enabled": true} + }`) + + simpleResult = CallResult{ + Success: true, + Data: "hello world", + } + + complexResult = CallResult{ + Success: true, + Data: ComplexArg{ + ID: 12345, + Name: "Result Data", + Tags: []string{"a", "b", "c"}, + Metadata: map[string]interface{}{ + "processed": true, + "count": 100, + }, + Nested: &NestedArg{Value: 2.718, Enabled: true}, + }, + } +) + +// === UNMARSHAL BENCHMARKS (argument parsing) === + +func BenchmarkJSONv1_Unmarshal_Simple(b *testing.B) { + for b.Loop() { + var arg SimpleArg + _ = json.Unmarshal(simpleArgJSON, &arg) + } +} + +func BenchmarkJSONv2_Unmarshal_Simple(b *testing.B) { + for b.Loop() { + var arg SimpleArg + _ = jsonv2.Unmarshal(simpleArgJSON, &arg) + } +} + +func BenchmarkJSONv1_Unmarshal_Complex(b *testing.B) { + for b.Loop() { + var arg ComplexArg + _ = json.Unmarshal(complexArgJSON, &arg) + } +} + +func BenchmarkJSONv2_Unmarshal_Complex(b *testing.B) { + for b.Loop() { + var arg ComplexArg + _ = jsonv2.Unmarshal(complexArgJSON, &arg) + } +} + +func BenchmarkJSONv1_Unmarshal_Interface(b *testing.B) { + for b.Loop() { + var arg interface{} + _ = json.Unmarshal(complexArgJSON, &arg) + } +} + +func BenchmarkJSONv2_Unmarshal_Interface(b *testing.B) { + for b.Loop() { + var arg interface{} + _ = jsonv2.Unmarshal(complexArgJSON, &arg) + } +} + +// === MARSHAL BENCHMARKS (result serialization) === + +func BenchmarkJSONv1_Marshal_Simple(b *testing.B) { + for b.Loop() { + _, _ = json.Marshal(simpleResult) + } +} + +func BenchmarkJSONv2_Marshal_Simple(b *testing.B) { + for b.Loop() { + _, _ = jsonv2.Marshal(simpleResult) + } +} + +func BenchmarkJSONv1_Marshal_Complex(b *testing.B) { + for b.Loop() { + _, _ = json.Marshal(complexResult) + } +} + +func BenchmarkJSONv2_Marshal_Complex(b *testing.B) { + for b.Loop() { + _, _ = jsonv2.Marshal(complexResult) + } +} + +// === RAW MESSAGE HANDLING (common in Wails bindings) === + +func BenchmarkJSONv1_RawMessage_Unmarshal(b *testing.B) { + raw := json.RawMessage(complexArgJSON) + for b.Loop() { + var arg ComplexArg + _ = json.Unmarshal(raw, &arg) + } +} + +func BenchmarkJSONv2_RawMessage_Unmarshal(b *testing.B) { + raw := jsontext.Value(complexArgJSON) + for b.Loop() { + var arg ComplexArg + _ = jsonv2.Unmarshal(raw, &arg) + } +} + +// === SLICE ARGUMENTS (common pattern) === + +var sliceArgJSON = []byte(`[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]`) +var largeSliceArgJSON = func() []byte { + data, _ := json.Marshal(make([]int, 100)) + return data +}() + +func BenchmarkJSONv1_Unmarshal_Slice(b *testing.B) { + for b.Loop() { + var arg []int + _ = json.Unmarshal(sliceArgJSON, &arg) + } +} + +func BenchmarkJSONv2_Unmarshal_Slice(b *testing.B) { + for b.Loop() { + var arg []int + _ = jsonv2.Unmarshal(sliceArgJSON, &arg) + } +} + +func BenchmarkJSONv1_Unmarshal_LargeSlice(b *testing.B) { + for b.Loop() { + var arg []int + _ = json.Unmarshal(largeSliceArgJSON, &arg) + } +} + +func BenchmarkJSONv2_Unmarshal_LargeSlice(b *testing.B) { + for b.Loop() { + var arg []int + _ = jsonv2.Unmarshal(largeSliceArgJSON, &arg) + } +} + +// === STRING ARGUMENT (most common) === + +var stringArgJSON = []byte(`"hello world this is a test string"`) + +func BenchmarkJSONv1_Unmarshal_String(b *testing.B) { + for b.Loop() { + var arg string + _ = json.Unmarshal(stringArgJSON, &arg) + } +} + +func BenchmarkJSONv2_Unmarshal_String(b *testing.B) { + for b.Loop() { + var arg string + _ = jsonv2.Unmarshal(stringArgJSON, &arg) + } +} + +// === MULTIPLE ARGUMENTS (simulating method call) === + +var multiArgJSON = [][]byte{ + []byte(`"arg1"`), + []byte(`42`), + []byte(`true`), + []byte(`{"key": "value"}`), +} + +func BenchmarkJSONv1_Unmarshal_MultiArgs(b *testing.B) { + for b.Loop() { + var s string + var i int + var bl bool + var m map[string]string + _ = json.Unmarshal(multiArgJSON[0], &s) + _ = json.Unmarshal(multiArgJSON[1], &i) + _ = json.Unmarshal(multiArgJSON[2], &bl) + _ = json.Unmarshal(multiArgJSON[3], &m) + } +} + +func BenchmarkJSONv2_Unmarshal_MultiArgs(b *testing.B) { + for b.Loop() { + var s string + var i int + var bl bool + var m map[string]string + _ = jsonv2.Unmarshal(multiArgJSON[0], &s) + _ = jsonv2.Unmarshal(multiArgJSON[1], &i) + _ = jsonv2.Unmarshal(multiArgJSON[2], &bl) + _ = jsonv2.Unmarshal(multiArgJSON[3], &m) + } +} + +// === ERROR RESPONSE MARSHALING === + +type ErrorResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Details string `json:"details,omitempty"` +} + +var errorResp = ErrorResponse{ + Code: 500, + Message: "Internal server error", + Details: "Something went wrong while processing the request", +} + +func BenchmarkJSONv1_Marshal_Error(b *testing.B) { + for b.Loop() { + _, _ = json.Marshal(errorResp) + } +} + +func BenchmarkJSONv2_Marshal_Error(b *testing.B) { + for b.Loop() { + _, _ = jsonv2.Marshal(errorResp) + } +} diff --git a/v3/pkg/application/key_binding_manager.go b/v3/pkg/application/key_binding_manager.go new file mode 100644 index 000000000..64346d740 --- /dev/null +++ b/v3/pkg/application/key_binding_manager.go @@ -0,0 +1,66 @@ +package application + +// KeyBindingManager manages all key binding operations +type KeyBindingManager struct { + app *App +} + +// newKeyBindingManager creates a new KeyBindingManager instance +func newKeyBindingManager(app *App) *KeyBindingManager { + return &KeyBindingManager{ + app: app, + } +} + +// Add adds a key binding +func (kbm *KeyBindingManager) Add(accelerator string, callback func(window Window)) { + kbm.app.keyBindingsLock.Lock() + defer kbm.app.keyBindingsLock.Unlock() + kbm.app.keyBindings[accelerator] = callback +} + +// Remove removes a key binding +func (kbm *KeyBindingManager) Remove(accelerator string) { + kbm.app.keyBindingsLock.Lock() + defer kbm.app.keyBindingsLock.Unlock() + delete(kbm.app.keyBindings, accelerator) +} + +// Process processes a key binding and returns true if handled +func (kbm *KeyBindingManager) Process(accelerator string, window Window) bool { + kbm.app.keyBindingsLock.RLock() + callback, exists := kbm.app.keyBindings[accelerator] + kbm.app.keyBindingsLock.RUnlock() + + if exists && callback != nil { + callback(window) + return true + } + return false +} + +// KeyBinding represents a key binding with its accelerator and callback +type KeyBinding struct { + Accelerator string + Callback func(window Window) +} + +// GetAll returns all registered key bindings as a slice +func (kbm *KeyBindingManager) GetAll() []*KeyBinding { + kbm.app.keyBindingsLock.RLock() + defer kbm.app.keyBindingsLock.RUnlock() + + result := make([]*KeyBinding, 0, len(kbm.app.keyBindings)) + for accelerator, callback := range kbm.app.keyBindings { + result = append(result, &KeyBinding{ + Accelerator: accelerator, + Callback: callback, + }) + } + return result +} + +// HandleWindowKeyEvent handles window key events (internal use) +func (kbm *KeyBindingManager) handleWindowKeyEvent(event *windowKeyEvent) { + kbm.app.handleWindowKeyEvent(event) +} diff --git a/v3/pkg/application/keys.go b/v3/pkg/application/keys.go new file mode 100644 index 000000000..375b76289 --- /dev/null +++ b/v3/pkg/application/keys.go @@ -0,0 +1,220 @@ +package application + +import ( + "fmt" + "runtime" + "slices" + "strconv" + "strings" +) + +// modifier is actually a string +type modifier int + +const ( + // CmdOrCtrlKey represents Command on Mac and Control on other platforms + CmdOrCtrlKey modifier = 0 << iota + // OptionOrAltKey represents Option on Mac and Alt on other platforms + OptionOrAltKey modifier = 1 << iota + // ShiftKey represents the shift key on all systems + ShiftKey modifier = 2 << iota + // SuperKey represents Command on Mac and the Windows key on the other platforms + SuperKey modifier = 3 << iota + // ControlKey represents the control key on all systems + ControlKey modifier = 4 << iota +) + +func (m modifier) String() string { + return modifierStringMap[runtime.GOOS][m] +} + +var modifierStringMap = map[string]map[modifier]string{ + "windows": { + CmdOrCtrlKey: "Ctrl", + ControlKey: "Ctrl", + OptionOrAltKey: "Alt", + ShiftKey: "Shift", + SuperKey: "Win", + }, + "darwin": { + CmdOrCtrlKey: "Cmd", + ControlKey: "Ctrl", + OptionOrAltKey: "Option", + ShiftKey: "Shift", + SuperKey: "Cmd", + }, + "linux": { + CmdOrCtrlKey: "Ctrl", + ControlKey: "Ctrl", + OptionOrAltKey: "Alt", + ShiftKey: "Shift", + SuperKey: "Super", + }, +} + +var modifierMap = map[string]modifier{ + "cmdorctrl": CmdOrCtrlKey, + "cmd": CmdOrCtrlKey, + "command": CmdOrCtrlKey, + "ctrl": ControlKey, + "optionoralt": OptionOrAltKey, + "alt": OptionOrAltKey, + "option": OptionOrAltKey, + "shift": ShiftKey, + "super": SuperKey, +} + +// accelerator holds the keyboard shortcut for a menu item +type accelerator struct { + Key string + Modifiers []modifier +} + +func (a *accelerator) clone() *accelerator { + result := *a + return &result +} + +func (a *accelerator) String() string { + var result []string + // Sort modifiers + for _, modifier := range a.Modifiers { + result = append(result, modifier.String()) + } + slices.Sort(result) + if len(a.Key) > 0 { + result = append(result, strings.ToUpper(a.Key)) + } + return strings.Join(result, "+") +} + +var namedKeys = map[string]struct{}{ + "backspace": {}, + "tab": {}, + "return": {}, + "enter": {}, + "escape": {}, + "left": {}, + "right": {}, + "up": {}, + "down": {}, + "space": {}, + "delete": {}, + "home": {}, + "end": {}, + "page up": {}, + "page down": {}, + "f1": {}, + "f2": {}, + "f3": {}, + "f4": {}, + "f5": {}, + "f6": {}, + "f7": {}, + "f8": {}, + "f9": {}, + "f10": {}, + "f11": {}, + "f12": {}, + "f13": {}, + "f14": {}, + "f15": {}, + "f16": {}, + "f17": {}, + "f18": {}, + "f19": {}, + "f20": {}, + "f21": {}, + "f22": {}, + "f23": {}, + "f24": {}, + "f25": {}, + "f26": {}, + "f27": {}, + "f28": {}, + "f29": {}, + "f30": {}, + "f31": {}, + "f32": {}, + "f33": {}, + "f34": {}, + "f35": {}, + "numlock": {}, +} + +func parseKey(key string) (string, bool) { + + // Lowercase! + key = strings.ToLower(key) + + // Check special case + if key == "plus" { + return "+", true + } + + // Handle named keys + _, namedKey := namedKeys[key] + if namedKey { + return key, true + } + + // Check we only have a single character + if len(key) != 1 { + return "", false + } + + runeKey := rune(key[0]) + + // This may be too inclusive + if strconv.IsPrint(runeKey) { + return key, true + } + + return "", false + +} + +// parseAccelerator parses a string into an accelerator +func parseAccelerator(shortcut string) (*accelerator, error) { + + var result accelerator + + // Split the shortcut by + + components := strings.Split(shortcut, "+") + + // If we only have one it should be a key + // We require components + if len(components) == 0 { + return nil, fmt.Errorf("no components given to validateComponents") + } + + modifiers := map[modifier]struct{}{} + + // Check components + for index, component := range components { + + // If last component + if index == len(components)-1 { + processedKey, validKey := parseKey(component) + if !validKey { + return nil, fmt.Errorf("'%s' is not a valid key", component) + } + result.Key = strings.ToLower(processedKey) + continue + } + + // Not last component - needs to be modifier + lowercaseComponent := strings.ToLower(component) + thisModifier, valid := modifierMap[lowercaseComponent] + if !valid { + return nil, fmt.Errorf("'%s' is not a valid modifier", component) + } + // Save this data + modifiers[thisModifier] = struct{}{} + } + // return the keys as a slice + for thisModifier := range modifiers { + result.Modifiers = append(result.Modifiers, thisModifier) + } + return &result, nil +} diff --git a/v3/pkg/application/keys_android.go b/v3/pkg/application/keys_android.go new file mode 100644 index 000000000..d63b07aa6 --- /dev/null +++ b/v3/pkg/application/keys_android.go @@ -0,0 +1,9 @@ +//go:build android + +package application + +// Android keyboard handling stub + +func acceleratorToString(accelerator *accelerator) string { + return "" +} diff --git a/v3/pkg/application/keys_darwin.go b/v3/pkg/application/keys_darwin.go new file mode 100644 index 000000000..c76db0c5d --- /dev/null +++ b/v3/pkg/application/keys_darwin.go @@ -0,0 +1,28 @@ +//go:build darwin && !ios + +package application + +const ( + NSEventModifierFlagShift = 1 << 17 // Set if Shift key is pressed. + NSEventModifierFlagControl = 1 << 18 // Set if Control key is pressed. + NSEventModifierFlagOption = 1 << 19 // Set if Option or Alternate key is pressed. + NSEventModifierFlagCommand = 1 << 20 // Set if Command key is pressed. +) + +// macModifierMap maps accelerator modifiers to macOS modifiers. +var macModifierMap = map[modifier]int{ + CmdOrCtrlKey: NSEventModifierFlagCommand, + ControlKey: NSEventModifierFlagControl, + OptionOrAltKey: NSEventModifierFlagOption, + ShiftKey: NSEventModifierFlagShift, + SuperKey: NSEventModifierFlagCommand, +} + +// toMacModifier converts the accelerator to a macOS modifier. +func toMacModifier(modifiers []modifier) int { + result := 0 + for _, modifier := range modifiers { + result |= macModifierMap[modifier] + } + return result +} diff --git a/v3/pkg/application/keys_ios.go b/v3/pkg/application/keys_ios.go new file mode 100644 index 000000000..3a3332b0b --- /dev/null +++ b/v3/pkg/application/keys_ios.go @@ -0,0 +1,20 @@ +//go:build ios + +package application + +// iOS key codes - these would map to UIKeyCommand +const ( + KeyReturn = "Return" + KeyEscape = "Escape" + KeyDelete = "Delete" + KeyTab = "Tab" + KeySpace = "Space" + KeyUp = "Up" + KeyDown = "Down" + KeyLeft = "Left" + KeyRight = "Right" + KeyHome = "Home" + KeyEnd = "End" + KeyPageUp = "PageUp" + KeyPageDown = "PageDown" +) \ No newline at end of file diff --git a/v3/pkg/application/keys_linux.go b/v3/pkg/application/keys_linux.go new file mode 100644 index 000000000..542fba95f --- /dev/null +++ b/v3/pkg/application/keys_linux.go @@ -0,0 +1,165 @@ +//go:build linux && !android && !server + +package application + +var VirtualKeyCodes = map[uint]string{ + 0xff08: "backspace", + 0xff09: "tab", + 0xff0a: "linefeed", + 0xff0b: "clear", + 0xff0d: "return", + 0xff13: "pause", + 0xff14: "scrolllock", + 0xff15: "sysreq", + 0xff1b: "escape", + 0xffff: "delete", + 0xff50: "home", + 0xff51: "left", + + 0xffe1: "lshift", + 0xffe2: "rshift", + 0xffe3: "lcontrol", + 0xffe4: "rcontrol", + 0xffeb: "lmeta", + 0xffec: "rmeta", + 0xffed: "lalt", + 0xffee: "ralt", + // Multi-Lang + 0xff21: "kanji", + 0xff22: "muhenkan", + 0xff24: "henkan", + 0xff25: "hiragana", + 0xff26: "katakana", + 0xff27: "hiragana/katakana", + 0xff28: "zenkaku", + 0xff29: "hankaku", + 0xff2a: "zenkaku/hankaku", + 0xff2b: "touroku", + 0xff2c: "massyo", + 0xff2d: "kana lock", + 0xff2e: "kana shift", + 0xff2f: "eisu shift", + 0xff30: "eisu toggle", + 0xff37: "kanji bangou", + + // Directions + 0xff52: "up", + 0xff53: "right", + 0xff54: "down", + 0xff55: "pageup", + 0xff56: "pagedown", + 0xff57: "end", + 0xff58: "begin", + + + // Alphabet + 0x41: "a", + 0x42: "b", + 0x43: "c", + 0x44: "d", + 0x45: "e", + 0x46: "f", + 0x47: "g", + 0x48: "h", + 0x49: "i", + 0x4a: "j", + 0x4b: "k", + 0x4c: "l", + 0x4d: "m", + 0x4e: "n", + 0x4f: "o", + 0x50: "p", + 0x51: "q", + 0x52: "r", + 0x53: "s", + 0x54: "t", + 0x55: "u", + 0x56: "v", + 0x57: "w", + 0x58: "x", + 0x59: "y", + 0x5a: "z", + + 0x5b: "lbracket", + 0x5c: "backslash", + 0x5d: "rbracket", + 0x5e: "caret", + 0x5f: "underscore", + 0x60: "grave", + // Capital Alphabet + 0x61: "a", + 0x62: "b", + 0x63: "c", + 0x64: "d", + 0x65: "e", + 0x66: "f", + 0x67: "g", + 0x68: "h", + 0x69: "i", + 0x6a: "j", + 0x6b: "k", + 0x6c: "l", + 0x6d: "m", + 0x6e: "n", + 0x6f: "o", + 0x70: "p", + 0x71: "q", + 0x72: "r", + 0x73: "s", + 0x74: "t", + 0x75: "u", + 0x76: "v", + 0x77: "w", + 0x78: "x", + 0x79: "y", + 0x7a: "z", + 0x7b: "lbrace", + 0x7c: "pipe", + 0x7d: "rbrace", + 0x7e: "tilde", + 0xa1: "exclam", + 0xa2: "cent", + 0xa3: "sterling", + 0xa4: "currency", + 0xa5: "yen", + 0xa6: "brokenbar", + 0xa7: "section", + 0xa8: "diaeresis", + 0xa9: "copyright", + 0xaa: "ordfeminine", + 0xab: "guillemotleft", + 0xad: "hyphen", + 0xae: "registered", + 0xaf: "macron", + 0xb0: "degree", + 0xb1: "plusminus", + 0xb2: "twosuperior", + + // Function Keys + 0xffbe: "f1", + 0xffbf: "f2", + 0xffc0: "f3", + 0xffc1: "f4", + 0xffc2: "f5", + 0xffc3: "f6", + 0xffc4: "f7", + 0xffc5: "f8", + 0xffc6: "f9", + 0xffc7: "f10", + 0xffc8: "f11", + 0xffc9: "f12", + 0xffca: "f13", + 0xffcb: "f14", + 0xffcc: "f15", + 0xffcd: "f16", + 0xffce: "f17", + 0xffcf: "f18", + 0xffd0: "f19", + 0xffd1: "f20", + 0xffd2: "f21", + 0xffd3: "f22", + 0xffd4: "f23", + 0xffd5: "f24", + + +} diff --git a/v3/pkg/application/keys_test.go b/v3/pkg/application/keys_test.go new file mode 100644 index 000000000..3651395ed --- /dev/null +++ b/v3/pkg/application/keys_test.go @@ -0,0 +1,293 @@ +package application + +import ( + "runtime" + "strings" + "testing" +) + +func TestModifier_Constants(t *testing.T) { + // Verify modifier constants are distinct + modifiers := []modifier{CmdOrCtrlKey, OptionOrAltKey, ShiftKey, SuperKey, ControlKey} + seen := make(map[modifier]bool) + for _, m := range modifiers { + if seen[m] { + t.Errorf("Duplicate modifier value: %d", m) + } + seen[m] = true + } + + // CmdOrCtrlKey should be 0 (the base value) + if CmdOrCtrlKey != 0 { + t.Error("CmdOrCtrlKey should be 0") + } +} + +func TestParseKey_Valid(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"a", "a"}, + {"A", "a"}, + {"z", "z"}, + {"0", "0"}, + {"9", "9"}, + {"+", "+"}, // Single + is a valid printable character + {"plus", "+"}, + {"Plus", "+"}, + {"PLUS", "+"}, + {"backspace", "backspace"}, + {"Backspace", "backspace"}, + {"BACKSPACE", "backspace"}, + {"tab", "tab"}, + {"return", "return"}, + {"enter", "enter"}, + {"escape", "escape"}, + {"left", "left"}, + {"right", "right"}, + {"up", "up"}, + {"down", "down"}, + {"space", "space"}, + {"delete", "delete"}, + {"home", "home"}, + {"end", "end"}, + {"page up", "page up"}, + {"page down", "page down"}, + {"f1", "f1"}, + {"F1", "f1"}, + {"f12", "f12"}, + {"f35", "f35"}, + {"numlock", "numlock"}, + } + + for _, tt := range tests { + result, valid := parseKey(tt.input) + if tt.expected == "" { + if valid { + t.Errorf("parseKey(%q) should be invalid", tt.input) + } + } else { + if !valid { + t.Errorf("parseKey(%q) should be valid", tt.input) + } + if result != tt.expected { + t.Errorf("parseKey(%q) = %q, want %q", tt.input, result, tt.expected) + } + } + } +} + +func TestParseKey_Invalid(t *testing.T) { + tests := []string{ + "abc", // multiple chars + "", // empty + "notakey", // not a named key + "ctrl+a", // shortcut syntax + "backspac", // misspelled + } + + for _, tt := range tests { + _, valid := parseKey(tt) + if valid { + t.Errorf("parseKey(%q) should be invalid", tt) + } + } +} + +func TestParseAccelerator_Valid(t *testing.T) { + tests := []struct { + input string + key string + modCount int + }{ + {"a", "a", 0}, + {"Ctrl+A", "a", 1}, + {"ctrl+a", "a", 1}, + {"Ctrl+Shift+A", "a", 2}, + {"ctrl+shift+a", "a", 2}, + {"Cmd+A", "a", 1}, + {"Command+A", "a", 1}, + {"CmdOrCtrl+A", "a", 1}, + {"Alt+A", "a", 1}, + {"Option+A", "a", 1}, + {"OptionOrAlt+A", "a", 1}, + {"Shift+A", "a", 1}, + {"Super+A", "a", 1}, + {"Ctrl+Shift+Alt+A", "a", 3}, + {"Ctrl+plus", "+", 1}, + {"F1", "f1", 0}, + {"Ctrl+F12", "f12", 1}, + {"Ctrl+Shift+F1", "f1", 2}, + {"Ctrl+backspace", "backspace", 1}, + {"Ctrl+escape", "escape", 1}, + } + + for _, tt := range tests { + acc, err := parseAccelerator(tt.input) + if err != nil { + t.Errorf("parseAccelerator(%q) returned error: %v", tt.input, err) + continue + } + if acc.Key != tt.key { + t.Errorf("parseAccelerator(%q).Key = %q, want %q", tt.input, acc.Key, tt.key) + } + if len(acc.Modifiers) != tt.modCount { + t.Errorf("parseAccelerator(%q) has %d modifiers, want %d", tt.input, len(acc.Modifiers), tt.modCount) + } + } +} + +func TestParseAccelerator_Invalid(t *testing.T) { + tests := []struct { + input string + errMsg string + }{ + {"", "no components"}, + {"Ctrl+", "not a valid key"}, + {"Ctrl+abc", "not a valid key"}, + {"NotAModifier+A", "not a valid modifier"}, + {"Ctrl+Shift+notakey", "not a valid key"}, + } + + for _, tt := range tests { + _, err := parseAccelerator(tt.input) + if err == nil { + t.Errorf("parseAccelerator(%q) should return error", tt.input) + } + } +} + +func TestParseAccelerator_DuplicateModifiers(t *testing.T) { + // Duplicate modifiers should be deduplicated + acc, err := parseAccelerator("Ctrl+Ctrl+A") + if err != nil { + t.Errorf("parseAccelerator returned error: %v", err) + return + } + if len(acc.Modifiers) != 1 { + t.Errorf("Duplicate modifiers should be deduplicated, got %d modifiers", len(acc.Modifiers)) + } +} + +func TestAccelerator_Clone(t *testing.T) { + original := &accelerator{ + Key: "a", + Modifiers: []modifier{ControlKey, ShiftKey}, + } + + clone := original.clone() + + if clone == original { + t.Error("Clone should return a different pointer") + } + if clone.Key != original.Key { + t.Error("Clone should have same Key") + } + // Note: the slice reference is copied, so modifying the clone's slice would affect original + // This is a shallow clone +} + +func TestAccelerator_String(t *testing.T) { + // Test key-only accelerator (platform-independent) + acc := &accelerator{Key: "a", Modifiers: []modifier{}} + result := acc.String() + if result != "A" { + t.Errorf("accelerator.String() = %q, want %q", result, "A") + } + + // Test with ControlKey modifier - output varies by platform + acc = &accelerator{Key: "a", Modifiers: []modifier{ControlKey}} + result = acc.String() + // On macOS: "Ctrl+A", on Linux/Windows: "Ctrl+A" + // The representation should contain the key and be non-empty + if !strings.HasSuffix(result, "+A") && result != "A" { + t.Errorf("accelerator.String() = %q, expected to end with '+A'", result) + } + if result == "" { + t.Error("accelerator.String() should not return empty") + } + + // Test function key with modifier + acc = &accelerator{Key: "f1", Modifiers: []modifier{ControlKey}} + result = acc.String() + if !strings.HasSuffix(result, "+F1") { + t.Errorf("accelerator.String() = %q, expected to end with '+F1'", result) + } +} + +func TestAccelerator_String_PlatformSpecific(t *testing.T) { + // This test documents the expected platform-specific behavior + acc := &accelerator{Key: "a", Modifiers: []modifier{ControlKey}} + result := acc.String() + + switch runtime.GOOS { + case "darwin": + // On macOS, Ctrl key is represented as "Ctrl" (distinct from Cmd) + if !strings.Contains(result, "Ctrl") && !strings.Contains(result, "⌃") { + t.Logf("On macOS, got %q for ControlKey modifier", result) + } + case "linux", "windows": + if !strings.Contains(result, "Ctrl") { + t.Errorf("On %s, expected 'Ctrl' in result, got %q", runtime.GOOS, result) + } + } +} + +func TestAccelerator_StringWithMultipleModifiers(t *testing.T) { + acc := &accelerator{ + Key: "a", + Modifiers: []modifier{ShiftKey, ControlKey}, + } + + result := acc.String() + + // Result should contain both modifiers and the key + if result == "" { + t.Error("String() should not return empty") + } + // The modifiers are sorted, so order is deterministic +} + +func TestModifierMap_Contains(t *testing.T) { + expectedMappings := map[string]modifier{ + "cmdorctrl": CmdOrCtrlKey, + "cmd": CmdOrCtrlKey, + "command": CmdOrCtrlKey, + "ctrl": ControlKey, + "optionoralt": OptionOrAltKey, + "alt": OptionOrAltKey, + "option": OptionOrAltKey, + "shift": ShiftKey, + "super": SuperKey, + } + + for key, expected := range expectedMappings { + actual, ok := modifierMap[key] + if !ok { + t.Errorf("modifierMap should contain key %q", key) + } + if actual != expected { + t.Errorf("modifierMap[%q] = %v, want %v", key, actual, expected) + } + } +} + +func TestNamedKeys_Contains(t *testing.T) { + expectedKeys := []string{ + "backspace", "tab", "return", "enter", "escape", + "left", "right", "up", "down", "space", "delete", + "home", "end", "page up", "page down", + "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", + "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", + "f21", "f22", "f23", "f24", "f25", "f26", "f27", "f28", "f29", "f30", + "f31", "f32", "f33", "f34", "f35", + "numlock", + } + + for _, key := range expectedKeys { + if _, ok := namedKeys[key]; !ok { + t.Errorf("namedKeys should contain %q", key) + } + } +} diff --git a/v3/pkg/application/keys_windows.go b/v3/pkg/application/keys_windows.go new file mode 100644 index 000000000..8011a837d --- /dev/null +++ b/v3/pkg/application/keys_windows.go @@ -0,0 +1,230 @@ +//go:build windows + +package application + +var VirtualKeyCodes = map[uint]string{ + 0x01: "lbutton", + 0x02: "rbutton", + 0x03: "cancel", + 0x04: "mbutton", + 0x05: "xbutton1", + 0x06: "xbutton2", + 0x08: "back", + 0x09: "tab", + 0x0C: "clear", + 0x0D: "return", + 0x10: "shift", + 0x11: "ctrl", + 0x12: "menu", + 0x13: "pause", + 0x14: "capital", + 0x15: "kana", + 0x17: "junja", + 0x18: "final", + 0x19: "hanja", + 0x1B: "escape", + 0x1C: "convert", + 0x1D: "nonconvert", + 0x1E: "accept", + 0x1F: "modechange", + 0x20: "space", + 0x21: "prior", + 0x22: "next", + 0x23: "end", + 0x24: "home", + 0x25: "left", + 0x26: "up", + 0x27: "right", + 0x28: "down", + 0x29: "select", + 0x2A: "print", + 0x2B: "execute", + 0x2C: "snapshot", + 0x2D: "insert", + 0x2E: "delete", + 0x2F: "help", + 0x30: "0", + 0x31: "1", + 0x32: "2", + 0x33: "3", + 0x34: "4", + 0x35: "5", + 0x36: "6", + 0x37: "7", + 0x38: "8", + 0x39: "9", + 0x41: "a", + 0x42: "b", + 0x43: "c", + 0x44: "d", + 0x45: "e", + 0x46: "f", + 0x47: "g", + 0x48: "h", + 0x49: "i", + 0x4A: "j", + 0x4B: "k", + 0x4C: "l", + 0x4D: "m", + 0x4E: "n", + 0x4F: "o", + 0x50: "p", + 0x51: "q", + 0x52: "r", + 0x53: "s", + 0x54: "t", + 0x55: "u", + 0x56: "v", + 0x57: "w", + 0x58: "x", + 0x59: "y", + 0x5A: "z", + 0x5B: "lwin", + 0x5C: "rwin", + 0x5D: "apps", + 0x5F: "sleep", + 0x60: "numpad0", + 0x61: "numpad1", + 0x62: "numpad2", + 0x63: "numpad3", + 0x64: "numpad4", + 0x65: "numpad5", + 0x66: "numpad6", + 0x67: "numpad7", + 0x68: "numpad8", + 0x69: "numpad9", + 0x6A: "multiply", + 0x6B: "add", + 0x6C: "separator", + 0x6D: "subtract", + 0x6E: "decimal", + 0x6F: "divide", + 0x70: "f1", + 0x71: "f2", + 0x72: "f3", + 0x73: "f4", + 0x74: "f5", + 0x75: "f6", + 0x76: "f7", + 0x77: "f8", + 0x78: "f9", + 0x79: "f10", + 0x7A: "f11", + 0x7B: "f12", + 0x7C: "f13", + 0x7D: "f14", + 0x7E: "f15", + 0x7F: "f16", + 0x80: "f17", + 0x81: "f18", + 0x82: "f19", + 0x83: "f20", + 0x84: "f21", + 0x85: "f22", + 0x86: "f23", + 0x87: "f24", + 0x88: "navigation_view", + 0x89: "navigation_menu", + 0x8A: "navigation_up", + 0x8B: "navigation_down", + 0x8C: "navigation_left", + 0x8D: "navigation_right", + 0x8E: "navigation_accept", + 0x8F: "navigation_cancel", + 0x90: "numlock", + 0x91: "scroll", + 0x92: "oem_nec_equal", + 0x93: "oem_fj_masshou", + 0x94: "oem_fj_touroku", + 0x95: "oem_fj_loya", + 0x96: "oem_fj_roya", + 0xA0: "lshift", + 0xA1: "rshift", + 0xA2: "lcontrol", + 0xA3: "rcontrol", + 0xA4: "lmenu", + 0xA5: "rmenu", + 0xA6: "browser_back", + 0xA7: "browser_forward", + 0xA8: "browser_refresh", + 0xA9: "browser_stop", + 0xAA: "browser_search", + 0xAB: "browser_favorites", + 0xAC: "browser_home", + 0xAD: "volume_mute", + 0xAE: "volume_down", + 0xAF: "volume_up", + 0xB0: "media_next_track", + 0xB1: "media_prev_track", + 0xB2: "media_stop", + 0xB3: "media_play_pause", + 0xB4: "launch_mail", + 0xB5: "launch_media_select", + 0xB6: "launch_app1", + 0xB7: "launch_app2", + 0xBA: "oem_1", + 0xBB: "oem_plus", + 0xBC: "oem_comma", + 0xBD: "oem_minus", + 0xBE: "oem_period", + 0xBF: "oem_2", + 0xC0: "oem_3", + 0xC3: "gamepad_a", + 0xC4: "gamepad_b", + 0xC5: "gamepad_x", + 0xC6: "gamepad_y", + 0xC7: "gamepad_right_shoulder", + 0xC8: "gamepad_left_shoulder", + 0xC9: "gamepad_left_trigger", + 0xCA: "gamepad_right_trigger", + 0xCB: "gamepad_dpad_up", + 0xCC: "gamepad_dpad_down", + 0xCD: "gamepad_dpad_left", + 0xCE: "gamepad_dpad_right", + 0xCF: "gamepad_menu", + 0xD0: "gamepad_view", + 0xD1: "gamepad_left_thumbstick_button", + 0xD2: "gamepad_right_thumbstick_button", + 0xD3: "gamepad_left_thumbstick_up", + 0xD4: "gamepad_left_thumbstick_down", + 0xD5: "gamepad_left_thumbstick_right", + 0xD6: "gamepad_left_thumbstick_left", + 0xD7: "gamepad_right_thumbstick_up", + 0xD8: "gamepad_right_thumbstick_down", + 0xD9: "gamepad_right_thumbstick_right", + 0xDA: "gamepad_right_thumbstick_left", + 0xDB: "oem_4", + 0xDC: "oem_5", + 0xDD: "oem_6", + 0xDE: "oem_7", + 0xDF: "oem_8", + 0xE1: "oem_ax", + 0xE2: "oem_102", + 0xE3: "ico_help", + 0xE4: "ico_00", + 0xE5: "processkey", + 0xE6: "ico_clear", + 0xE7: "packet", + 0xE9: "oem_reset", + 0xEA: "oem_jump", + 0xEB: "oem_pa1", + 0xEC: "oem_pa2", + 0xED: "oem_pa3", + 0xEE: "oem_wsctrl", + 0xEF: "oem_cusel", + 0xF0: "oem_attn", + 0xF1: "oem_finish", + 0xF2: "oem_copy", + 0xF3: "oem_auto", + 0xF4: "oem_enlw", + 0xF5: "oem_backtab", + 0xF6: "attn", + 0xF7: "crsel", + 0xF8: "exsel", + 0xF9: "ereof", + 0xFA: "play", + 0xFB: "zoom", + 0xFC: "noname", + 0xFD: "pa1", + 0xFE: "oem_clear", +} diff --git a/v3/pkg/application/linux_cgo.go b/v3/pkg/application/linux_cgo.go new file mode 100644 index 000000000..ba34fa31a --- /dev/null +++ b/v3/pkg/application/linux_cgo.go @@ -0,0 +1,2288 @@ +//go:build linux && cgo && !android && !server + +package application + +import ( + "fmt" + "strings" + "sync" + "time" + "unsafe" + + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 gdk-3.0 + +#include +#include +#include +#include +#include +#include +// Use NON_UNIQUE to allow multiple instances of the application to run. +// This matches the behavior of gtk_init/gtk_main used in v2. +#define APPLICATION_DEFAULT_FLAGS G_APPLICATION_NON_UNIQUE + +typedef struct CallbackID +{ + unsigned int value; +} CallbackID; + +extern void dispatchOnMainThreadCallback(unsigned int); + +static gboolean dispatchCallback(gpointer data) { + struct CallbackID *args = data; + unsigned int cid = args->value; + dispatchOnMainThreadCallback(cid); + free(args); + + return G_SOURCE_REMOVE; +}; + +static void dispatchOnMainThread(unsigned int id) { + CallbackID *args = malloc(sizeof(CallbackID)); + args->value = id; + g_idle_add((GSourceFunc)dispatchCallback, (gpointer)args); +} + +typedef struct WindowEvent { + uint id; + uint event; +} WindowEvent; + +static void save_window_id(void *object, uint value) +{ + g_object_set_data((GObject *)object, "windowid", GUINT_TO_POINTER((guint)value)); +} + +static void save_webview_to_content_manager(void *contentManager, void *webview) +{ + g_object_set_data(G_OBJECT((WebKitUserContentManager *)contentManager), "webview", webview); +} + +static WebKitWebView* get_webview_from_content_manager(void *contentManager) +{ + return WEBKIT_WEB_VIEW(g_object_get_data(G_OBJECT(contentManager), "webview")); +} + +static guint get_window_id(void *object) +{ + return GPOINTER_TO_UINT(g_object_get_data((GObject *)object, "windowid")); +} + +// exported below +void activateLinux(gpointer data); +extern void emit(WindowEvent* data); +extern gboolean handleConfigureEvent(GtkWidget*, GdkEventConfigure*, uintptr_t); +extern gboolean handleDeleteEvent(GtkWidget*, GdkEvent*, uintptr_t); +extern gboolean handleFocusEvent(GtkWidget*, GdkEvent*, uintptr_t); +extern void handleLoadChanged(WebKitWebView*, WebKitLoadEvent, uintptr_t); +void handleClick(void*); +extern gboolean onButtonEvent(GtkWidget *widget, GdkEventButton *event, uintptr_t user_data); +extern gboolean onMenuButtonEvent(GtkWidget *widget, GdkEventButton *event, uintptr_t user_data); +extern void onUriList(char **extracted, gint x, gint y, gpointer data); +extern void onDragEnter(gpointer data); +extern void onDragLeave(gpointer data); +extern void onDragOver(gint x, gint y, gpointer data); +extern gboolean onKeyPressEvent (GtkWidget *widget, GdkEventKey *event, uintptr_t user_data); +extern void onProcessRequest(WebKitURISchemeRequest *request, uintptr_t user_data); +extern void sendMessageToBackend(WebKitUserContentManager *contentManager, WebKitJavascriptResult *result, void *data); +// exported below (end) + +static void signal_connect(void *widget, char *event, void *cb, void* data) { + // g_signal_connect is a macro and can't be called directly + g_signal_connect(widget, event, cb, data); +} + +static WebKitWebView* webkit_web_view(GtkWidget *webview) { + return WEBKIT_WEB_VIEW(webview); +} + +static void* new_message_dialog(GtkWindow *parent, const gchar *msg, int dialogType, bool hasButtons) { + // gtk_message_dialog_new is variadic! Can't call from cgo directly + GtkWidget *dialog; + int buttonMask; + + // buttons will be added after creation + buttonMask = GTK_BUTTONS_OK; + if (hasButtons) { + buttonMask = GTK_BUTTONS_NONE; + } + + dialog = gtk_message_dialog_new( + parent, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + dialogType, + buttonMask, + "%s", + msg); + + // g_signal_connect_swapped (dialog, + // "response", + // G_CALLBACK (callback), + // dialog); + return dialog; +}; + +extern void messageDialogCB(gint button); + +static void* gtkFileChooserDialogNew(char* title, GtkWindow* window, GtkFileChooserAction action, char* cancelLabel, char* acceptLabel) { + // gtk_file_chooser_dialog_new is variadic! Can't call from cgo directly + return (GtkFileChooser*)gtk_file_chooser_dialog_new( + title, + window, + action, + cancelLabel, + GTK_RESPONSE_CANCEL, + acceptLabel, + GTK_RESPONSE_ACCEPT, + NULL); +} + +typedef struct Screen { + const char* id; + const char* name; + int p_width; + int p_height; + int width; + int height; + int x; + int y; + int w_width; + int w_height; + int w_x; + int w_y; + float scaleFactor; + double rotation; + bool isPrimary; +} Screen; + +// Signal handler fix for WebKit/GTK compatibility. +// CREDIT: https://github.com/rainycape/magick +// +// WebKit/GTK may install signal handlers without SA_ONSTACK, which causes +// Go to crash when handling signals (e.g., during panic recovery). +// This code adds SA_ONSTACK to signal handlers after WebKit initialization. +// +// Known limitation: Due to Go issue #7227 (golang/go#7227), signals may still +// be delivered on the wrong stack in some cases when C libraries are involved. +// This is a fundamental Go runtime limitation that cannot be fully resolved here. +#include +#include +#include +#include + +static void fix_signal(int signum) { + struct sigaction st; + + if (sigaction(signum, NULL, &st) < 0) { + goto fix_signal_error; + } + st.sa_flags |= SA_ONSTACK; + if (sigaction(signum, &st, NULL) < 0) { + goto fix_signal_error; + } + return; +fix_signal_error: + fprintf(stderr, "error fixing handler for signal %d, please " + "report this issue to " + "https://github.com/wailsapp/wails: %s\n", + signum, strerror(errno)); +} + +static void install_signal_handlers() { + #if defined(SIGCHLD) + fix_signal(SIGCHLD); + #endif + #if defined(SIGHUP) + fix_signal(SIGHUP); + #endif + #if defined(SIGINT) + fix_signal(SIGINT); + #endif + #if defined(SIGQUIT) + fix_signal(SIGQUIT); + #endif + #if defined(SIGABRT) + fix_signal(SIGABRT); + #endif + #if defined(SIGFPE) + fix_signal(SIGFPE); + #endif + #if defined(SIGTERM) + fix_signal(SIGTERM); + #endif + #if defined(SIGBUS) + fix_signal(SIGBUS); + #endif + #if defined(SIGSEGV) + fix_signal(SIGSEGV); + #endif + #if defined(SIGXCPU) + fix_signal(SIGXCPU); + #endif + #if defined(SIGXFSZ) + fix_signal(SIGXFSZ); + #endif +} + +static int GetNumScreens(){ + return 0; +} + +// Handle file drops from the OS - called when drag data is received +static void on_drag_data_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, + GtkSelectionData *selection_data, guint target_type, guint time, + gpointer data) +{ + // Only process target_type 2 which is our text/uri-list + // Other target types are from internal WebKit drags + if (target_type != 2) { + return; // Don't interfere with internal drags + } + + // Check if we have valid data + if (selection_data == NULL || gtk_selection_data_get_length(selection_data) <= 0) { + gtk_drag_finish(context, FALSE, FALSE, time); + return; + } + + const gchar *uri_data = (const gchar *)gtk_selection_data_get_data(selection_data); + gchar **filenames = g_uri_list_extract_uris(uri_data); + if (filenames == NULL || filenames[0] == NULL) { + if (filenames) g_strfreev(filenames); + gtk_drag_finish(context, FALSE, FALSE, time); + return; + } + + // Build file array for Go + GPtrArray *file_array = g_ptr_array_new(); + int iter = 0; + while (filenames[iter] != NULL) { + char *filename = g_filename_from_uri(filenames[iter], NULL, NULL); + if (filename != NULL) { + g_ptr_array_add(file_array, filename); + } + iter++; + } + g_strfreev(filenames); + + if (file_array->len > 0) { + // Get stored drop coordinates and data pointer + gint drop_x = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "drop-x")); + gint drop_y = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "drop-y")); + gpointer drop_data = g_object_get_data(G_OBJECT(widget), "drop-data"); + + // Add NULL terminator and call Go + g_ptr_array_add(file_array, NULL); + onUriList((gchar **)file_array->pdata, drop_x, drop_y, drop_data); + } + + // Cleanup + for (guint i = 0; i < file_array->len; i++) { + gpointer item = g_ptr_array_index(file_array, i); + if (item) g_free(item); + } + g_ptr_array_free(file_array, TRUE); + + // Finish the drag successfully to prevent WebKit from opening the file + gtk_drag_finish(context, TRUE, FALSE, time); +} + +// Track if we've notified about drag entering +static gboolean drag_entered = FALSE; + +// Track if a drag started from within the webview (internal HTML5 drag) +static gboolean internal_drag_active = FALSE; + +// Called when a drag starts FROM this widget (internal drag) +static void on_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer data) +{ + internal_drag_active = TRUE; +} + +// Called when a drag that started from this widget ends +static void on_drag_end(GtkWidget *widget, GdkDragContext *context, gpointer data) +{ + internal_drag_active = FALSE; +} + +// Check if a drag context contains file URIs (external drop) +// Returns TRUE only for external file manager drops, FALSE for internal HTML5 drags +static gboolean is_file_drag(GdkDragContext *context) +{ + GList *targets = gdk_drag_context_list_targets(context); + + // Internal HTML5 drags have WebKit-specific targets, external file drops have text/uri-list + for (GList *l = targets; l != NULL; l = l->next) { + GdkAtom atom = GDK_POINTER_TO_ATOM(l->data); + gchar *name = gdk_atom_name(atom); + if (name) { + gboolean is_uri = g_strcmp0(name, "text/uri-list") == 0; + g_free(name); + if (is_uri) { + return TRUE; + } + } + } + return FALSE; +} + +// Handle the actual drop - called when user releases mouse button +static gboolean on_drag_drop(GtkWidget *widget, GdkDragContext *context, gint x, gint y, + guint time, gpointer data) +{ + // Only handle external file drops, let WebKit handle internal HTML5 drags + if (!is_file_drag(context)) { + return FALSE; + } + + // Reset drag entered state + drag_entered = FALSE; + + // Store coordinates for use in drag-data-received + g_object_set_data(G_OBJECT(widget), "drop-x", GINT_TO_POINTER(x)); + g_object_set_data(G_OBJECT(widget), "drop-y", GINT_TO_POINTER(y)); + g_object_set_data(G_OBJECT(widget), "drop-data", data); + + // Request the file data - this triggers drag-data-received + GdkAtom target = gdk_atom_intern("text/uri-list", FALSE); + gtk_drag_get_data(widget, context, target, time); + + return TRUE; +} + +// Handle drag-motion for hover effects on external file drags +static gboolean on_drag_motion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer data) +{ + // Don't handle internal HTML5 drags + if (internal_drag_active || !is_file_drag(context)) { + return FALSE; + } + + gdk_drag_status(context, GDK_ACTION_COPY, time); + + // Notify JS once when drag enters + if (!drag_entered) { + drag_entered = TRUE; + onDragEnter(data); + } + + // Send position to JS for hover effects (Go side throttles this) + onDragOver(x, y, data); + + return TRUE; +} + +// Handle drag-leave - drag exited the window +static void on_drag_leave(GtkWidget *widget, GdkDragContext *context, guint time, gpointer data) +{ + // Don't handle internal HTML5 drags + if (internal_drag_active || !is_file_drag(context)) { + return; + } + + if (drag_entered) { + drag_entered = FALSE; + onDragLeave(data); + } +} + +// Set up drag and drop handlers for external file drops with hover effects +static void enableDND(GtkWidget *widget, gpointer data) +{ + // Core handlers for file drop + g_signal_connect(G_OBJECT(widget), "drag-data-received", G_CALLBACK(on_drag_data_received), data); + g_signal_connect(G_OBJECT(widget), "drag-drop", G_CALLBACK(on_drag_drop), data); + + // Hover effect handlers - return FALSE for internal drags to let WebKit handle them + g_signal_connect(G_OBJECT(widget), "drag-motion", G_CALLBACK(on_drag_motion), data); + g_signal_connect(G_OBJECT(widget), "drag-leave", G_CALLBACK(on_drag_leave), data); +} + +// Block external file drops - consume the events to prevent WebKit from navigating to files +// Returns TRUE for file drags to consume them, FALSE for internal HTML5 drags to let WebKit handle +static gboolean on_drag_drop_blocked(GtkWidget *widget, GdkDragContext *context, gint x, gint y, + guint time, gpointer data) +{ + if (!is_file_drag(context)) { + return FALSE; // Let WebKit handle internal HTML5 drags + } + // Block external file drops by finishing with failure + gtk_drag_finish(context, FALSE, FALSE, time); + return TRUE; +} + +static gboolean on_drag_motion_blocked(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer data) +{ + if (internal_drag_active || !is_file_drag(context)) { + return FALSE; // Let WebKit handle internal HTML5 drags + } + // Show "no drop" cursor for external file drags + gdk_drag_status(context, 0, time); + return TRUE; +} + +// Set up handlers that block external file drops while allowing internal HTML5 drag-and-drop +static void disableDND(GtkWidget *widget, gpointer data) +{ + g_signal_connect(G_OBJECT(widget), "drag-drop", G_CALLBACK(on_drag_drop_blocked), data); + g_signal_connect(G_OBJECT(widget), "drag-motion", G_CALLBACK(on_drag_motion_blocked), data); +} +*/ +import "C" + +// Calloc handles alloc/dealloc of C data +type Calloc struct { + pool []unsafe.Pointer +} + +// NewCalloc creates a new allocator +func NewCalloc() Calloc { + return Calloc{} +} + +// String creates a new C string and retains a reference to it +func (c Calloc) String(in string) *C.char { + result := C.CString(in) + c.pool = append(c.pool, unsafe.Pointer(result)) + return result +} + +// Free frees all allocated C memory +func (c Calloc) Free() { + for _, str := range c.pool { + C.free(str) + } + c.pool = []unsafe.Pointer{} +} + +type windowPointer *C.GtkWindow +type identifier C.uint +type pointer unsafe.Pointer +type GSList C.GSList +type GSListPointer *GSList + +// getLinuxWebviewWindow safely extracts a linuxWebviewWindow from a Window interface +// Returns nil if the window is not a WebviewWindow or not a Linux implementation +func getLinuxWebviewWindow(window Window) *linuxWebviewWindow { + if window == nil { + return nil + } + + webviewWindow, ok := window.(*WebviewWindow) + if !ok { + return nil + } + + lw, ok := webviewWindow.impl.(*linuxWebviewWindow) + if !ok { + return nil + } + + return lw +} + +var ( + nilPointer pointer = nil + nilRadioGroup GSListPointer = nil +) + +var ( + gtkSignalToMenuItem map[uint]*MenuItem + mainThreadId *C.GThread +) + +var registerURIScheme sync.Once +var fixSignalHandlers sync.Once + +func init() { + gtkSignalToMenuItem = map[uint]*MenuItem{} + + mainThreadId = C.g_thread_self() +} + +// mainthread stuff +func dispatchOnMainThread(id uint) { + C.dispatchOnMainThread(C.uint(id)) +} + +//export dispatchOnMainThreadCallback +func dispatchOnMainThreadCallback(callbackID C.uint) { + executeOnMainThread(uint(callbackID)) +} + +//export activateLinux +func activateLinux(data pointer) { + processApplicationEvent(C.uint(events.Linux.ApplicationStartup), data) +} + +//export processApplicationEvent +func processApplicationEvent(eventID C.uint, data pointer) { + event := newApplicationEvent(events.ApplicationEventType(eventID)) + + //if data != nil { + // dataCStrJSON := C.serializationNSDictionary(data) + // if dataCStrJSON != nil { + // defer C.free(unsafe.Pointer(dataCStrJSON)) + // + // dataJSON := C.GoString(dataCStrJSON) + // var result map[string]any + // err := json.Unmarshal([]byte(dataJSON), &result) + // + // if err != nil { + // panic(err) + // } + // + // event.Context().setData(result) + // } + //} + + switch event.Id { + case uint(events.Linux.SystemThemeChanged): + isDark := globalApplication.Env.IsDarkMode() + event.Context().setIsDarkMode(isDark) + } + applicationEvents <- event +} + +func isOnMainThread() bool { + threadId := C.g_thread_self() + return threadId == mainThreadId +} + +// implementation below +func appName() string { + name := C.g_get_application_name() + defer C.free(unsafe.Pointer(name)) + return C.GoString(name) +} + +func appNew(name string) pointer { + // Name is already sanitized by sanitizeAppName() in application_linux.go + appId := fmt.Sprintf("org.wails.%s", name) + nameC := C.CString(appId) + defer C.free(unsafe.Pointer(nameC)) + return pointer(C.gtk_application_new(nameC, C.APPLICATION_DEFAULT_FLAGS)) +} + +func setProgramName(prgName string) { + cPrgName := C.CString(prgName) + defer C.free(unsafe.Pointer(cPrgName)) + C.g_set_prgname(cPrgName) +} + +func appRun(app pointer) error { + application := (*C.GApplication)(app) + //TODO: Only set this if we configure it to do so + C.g_application_hold(application) // allows it to run without a window + + signal := C.CString("activate") + defer C.free(unsafe.Pointer(signal)) + C.signal_connect(unsafe.Pointer(application), signal, C.activateLinux, nil) + status := C.g_application_run(application, 0, nil) + C.g_application_release(application) + C.g_object_unref(C.gpointer(app)) + + var err error + if status != 0 { + err = fmt.Errorf("exit code: %d", status) + } + return err +} + +func appDestroy(application pointer) { + C.g_application_quit((*C.GApplication)(application)) +} + +func (w *linuxWebviewWindow) contextMenuSignals(menu pointer) { + c := NewCalloc() + defer c.Free() + winID := unsafe.Pointer(uintptr(C.uint(w.parent.ID()))) + C.signal_connect(unsafe.Pointer(menu), c.String("button-release-event"), C.onMenuButtonEvent, winID) +} + +func (w *linuxWebviewWindow) contextMenuShow(menu pointer, data *ContextMenuData) { + geometry := C.GdkRectangle{ + x: C.int(data.X), + y: C.int(data.Y), + } + event := C.GdkEvent{} + gdkWindow := C.gtk_widget_get_window(w.gtkWidget()) + C.gtk_menu_popup_at_rect( + (*C.GtkMenu)(menu), + gdkWindow, + (*C.GdkRectangle)(&geometry), + C.GDK_GRAVITY_NORTH_WEST, + C.GDK_GRAVITY_NORTH_WEST, + (*C.GdkEvent)(&event), + ) + w.ctxMenuOpened = true +} + +func (a *linuxApp) getCurrentWindowID() uint { + // TODO: Add extra metadata to window and use it! + window := (*C.GtkWindow)(C.gtk_application_get_active_window((*C.GtkApplication)(a.application))) + if window == nil { + return uint(1) + } + identifier, ok := a.windowMap[window] + if ok { + return identifier + } + // FIXME: Should we panic here if not found? + return uint(1) +} + +func (a *linuxApp) getWindows() []pointer { + result := []pointer{} + windows := C.gtk_application_get_windows((*C.GtkApplication)(a.application)) + for { + result = append(result, pointer(windows.data)) + windows = windows.next + if windows == nil { + return result + } + } +} + +func (a *linuxApp) hideAllWindows() { + for _, window := range a.getWindows() { + C.gtk_widget_hide((*C.GtkWidget)(window)) + } +} + +func (a *linuxApp) showAllWindows() { + for _, window := range a.getWindows() { + C.gtk_window_present((*C.GtkWindow)(window)) + } +} + +func (a *linuxApp) setIcon(icon []byte) { + if len(icon) == 0 { + return + } + // Use g_bytes_new instead of g_bytes_new_static because Go memory can be + // moved or freed by the GC. g_bytes_new copies the data to C-owned memory. + gbytes := C.g_bytes_new(C.gconstpointer(unsafe.Pointer(&icon[0])), C.ulong(len(icon))) + defer C.g_bytes_unref(gbytes) + stream := C.g_memory_input_stream_new_from_bytes(gbytes) + defer C.g_object_unref(C.gpointer(stream)) + var gerror *C.GError + pixbuf := C.gdk_pixbuf_new_from_stream(stream, nil, &gerror) + if gerror != nil { + a.parent.error("failed to load application icon: %s", C.GoString(gerror.message)) + C.g_error_free(gerror) + return + } + + a.icon = pointer(pixbuf) +} + +// Clipboard +func clipboardGet() string { + clip := C.gtk_clipboard_get(C.GDK_SELECTION_CLIPBOARD) + text := C.gtk_clipboard_wait_for_text(clip) + return C.GoString(text) +} + +func clipboardSet(text string) { + cText := C.CString(text) + clip := C.gtk_clipboard_get(C.GDK_SELECTION_CLIPBOARD) + C.gtk_clipboard_set_text(clip, cText, -1) + + clip = C.gtk_clipboard_get(C.GDK_SELECTION_PRIMARY) + C.gtk_clipboard_set_text(clip, cText, -1) + C.free(unsafe.Pointer(cText)) +} + +// Menu +func menuAddSeparator(menu *Menu) { + C.gtk_menu_shell_append( + (*C.GtkMenuShell)((menu.impl).(*linuxMenu).native), + C.gtk_separator_menu_item_new()) +} + +func menuAppend(parent *Menu, menu *MenuItem) { + C.gtk_menu_shell_append( + (*C.GtkMenuShell)((parent.impl).(*linuxMenu).native), + (*C.GtkWidget)((menu.impl).(*linuxMenuItem).native), + ) + /* gtk4 + C.gtk_menu_item_set_submenu( + (*C.struct__GtkMenuItem)((menu.impl).(*linuxMenuItem).native), + (*C.struct__GtkWidget)((parent.impl).(*linuxMenu).native), + ) + */ +} + +func menuBarNew() pointer { + return pointer(C.gtk_menu_bar_new()) +} + +func menuNew() pointer { + return pointer(C.gtk_menu_new()) +} + +func menuSetSubmenu(item *MenuItem, menu *Menu) { + C.gtk_menu_item_set_submenu( + (*C.GtkMenuItem)((item.impl).(*linuxMenuItem).native), + (*C.GtkWidget)((menu.impl).(*linuxMenu).native)) +} + +func menuGetRadioGroup(item *linuxMenuItem) *GSList { + return (*GSList)(C.gtk_radio_menu_item_get_group((*C.GtkRadioMenuItem)(item.native))) +} + +func menuClear(menu *Menu) { + menuShell := (*C.GtkMenuShell)((menu.impl).(*linuxMenu).native) + children := C.gtk_container_get_children((*C.GtkContainer)(unsafe.Pointer(menuShell))) + if children != nil { + // Save the original pointer to free later + originalList := children + // Iterate through all children and remove them + for children != nil { + child := (*C.GtkWidget)(children.data) + if child != nil { + C.gtk_container_remove((*C.GtkContainer)(unsafe.Pointer(menuShell)), child) + } + children = children.next + } + C.g_list_free(originalList) + } +} + +//export handleClick +func handleClick(idPtr unsafe.Pointer) { + ident := C.CString("id") + defer C.free(unsafe.Pointer(ident)) + value := C.g_object_get_data((*C.GObject)(idPtr), ident) + id := uint(*(*C.uint)(value)) + item, ok := gtkSignalToMenuItem[id] + if !ok { + return + } + switch item.itemType { + case text, checkbox: + menuItemClicked <- item.id + case radio: + menuItem := (item.impl).(*linuxMenuItem) + if menuItem.isChecked() { + menuItemClicked <- item.id + } + } +} + +func attachMenuHandler(item *MenuItem) uint { + signal := C.CString("activate") + defer C.free(unsafe.Pointer(signal)) + impl := (item.impl).(*linuxMenuItem) + widget := impl.native + flags := C.GConnectFlags(0) + handlerId := C.g_signal_connect_object( + C.gpointer(widget), + signal, + C.GCallback(C.handleClick), + C.gpointer(widget), + flags) + + id := C.uint(item.id) + ident := C.CString("id") + defer C.free(unsafe.Pointer(ident)) + C.g_object_set_data( + (*C.GObject)(widget), + ident, + C.gpointer(&id), + ) + + gtkSignalToMenuItem[item.id] = item + return uint(handlerId) +} + +// menuItem +func menuItemChecked(widget pointer) bool { + if C.gtk_check_menu_item_get_active((*C.GtkCheckMenuItem)(widget)) == C.int(1) { + return true + } + return false +} + +func menuItemNew(label string, bitmap []byte) pointer { + return menuItemAddProperties(C.gtk_menu_item_new(), label, bitmap) +} + +func menuItemDestroy(widget pointer) { + C.gtk_widget_destroy((*C.GtkWidget)(widget)) +} + +func menuItemAddProperties(menuItem *C.GtkWidget, label string, bitmap []byte) pointer { + /* + // FIXME: Support accelerator configuration + activate := C.CString("activate") + defer C.free(unsafe.Pointer(activate)) + accelGroup := C.gtk_accel_group_new() + C.gtk_widget_add_accelerator(menuItem, activate, accelGroup, + C.GDK_KEY_m, C.GDK_CONTROL_MASK, C.GTK_ACCEL_VISIBLE) + */ + cLabel := C.CString(label) + defer C.free(unsafe.Pointer(cLabel)) + lbl := unsafe.Pointer(C.gtk_accel_label_new(cLabel)) + C.gtk_label_set_use_underline((*C.GtkLabel)(lbl), 1) + C.gtk_label_set_xalign((*C.GtkLabel)(lbl), 0.0) + C.gtk_accel_label_set_accel_widget( + (*C.GtkAccelLabel)(lbl), + (*C.GtkWidget)(unsafe.Pointer(menuItem))) + + box := C.gtk_box_new(C.GTK_ORIENTATION_HORIZONTAL, 6) + if img, err := pngToImage(bitmap); err == nil && len(img.Pix) > 0 { + // Use g_bytes_new instead of g_bytes_new_static because Go memory can be + // moved or freed by the GC. g_bytes_new copies the data to C-owned memory. + gbytes := C.g_bytes_new(C.gconstpointer(unsafe.Pointer(&img.Pix[0])), + C.ulong(len(img.Pix))) + defer C.g_bytes_unref(gbytes) + pixBuf := C.gdk_pixbuf_new_from_bytes( + gbytes, + C.GDK_COLORSPACE_RGB, + 1, // has_alpha + 8, + C.int(img.Bounds().Dx()), + C.int(img.Bounds().Dy()), + C.int(img.Stride), + ) + image := C.gtk_image_new_from_pixbuf(pixBuf) + C.gtk_widget_set_visible((*C.GtkWidget)(image), C.gboolean(1)) + C.gtk_container_add( + (*C.GtkContainer)(unsafe.Pointer(box)), + (*C.GtkWidget)(unsafe.Pointer(image))) + } + + C.gtk_box_pack_end( + (*C.GtkBox)(unsafe.Pointer(box)), + (*C.GtkWidget)(lbl), 1, 1, 0) + C.gtk_container_add( + (*C.GtkContainer)(unsafe.Pointer(menuItem)), + (*C.GtkWidget)(unsafe.Pointer(box))) + C.gtk_widget_show_all(menuItem) + return pointer(menuItem) +} + +func menuCheckItemNew(label string, bitmap []byte) pointer { + return menuItemAddProperties(C.gtk_check_menu_item_new(), label, bitmap) +} + +func menuItemSetChecked(widget pointer, checked bool) { + value := C.int(0) + if checked { + value = C.int(1) + } + C.gtk_check_menu_item_set_active( + (*C.GtkCheckMenuItem)(widget), + value) +} + +func menuItemSetDisabled(widget pointer, disabled bool) { + value := C.int(1) + if disabled { + value = C.int(0) + } + C.gtk_widget_set_sensitive( + (*C.GtkWidget)(widget), + value) +} + +func menuItemSetLabel(widget pointer, label string) { + value := C.CString(label) + C.gtk_menu_item_set_label( + (*C.GtkMenuItem)(widget), + value) + C.free(unsafe.Pointer(value)) +} + +func menuItemRemoveBitmap(widget pointer) { + box := C.gtk_bin_get_child((*C.GtkBin)(widget)) + if box == nil { + return + } + + children := C.gtk_container_get_children((*C.GtkContainer)(unsafe.Pointer(box))) + defer C.g_list_free(children) + count := int(C.g_list_length(children)) + if count == 2 { + C.gtk_container_remove((*C.GtkContainer)(unsafe.Pointer(box)), + (*C.GtkWidget)(children.data)) + } +} + +func menuItemSetBitmap(widget pointer, bitmap []byte) { + menuItemRemoveBitmap(widget) + box := C.gtk_bin_get_child((*C.GtkBin)(widget)) + if img, err := pngToImage(bitmap); err == nil && len(img.Pix) > 0 { + // Use g_bytes_new instead of g_bytes_new_static because Go memory can be + // moved or freed by the GC. g_bytes_new copies the data to C-owned memory. + gbytes := C.g_bytes_new(C.gconstpointer(unsafe.Pointer(&img.Pix[0])), + C.ulong(len(img.Pix))) + defer C.g_bytes_unref(gbytes) + pixBuf := C.gdk_pixbuf_new_from_bytes( + gbytes, + C.GDK_COLORSPACE_RGB, + 1, // has_alpha + 8, + C.int(img.Bounds().Dx()), + C.int(img.Bounds().Dy()), + C.int(img.Stride), + ) + image := C.gtk_image_new_from_pixbuf(pixBuf) + C.gtk_widget_set_visible((*C.GtkWidget)(image), C.gboolean(1)) + C.gtk_container_add( + (*C.GtkContainer)(unsafe.Pointer(box)), + (*C.GtkWidget)(unsafe.Pointer(image))) + } + +} + +func menuItemSetToolTip(widget pointer, tooltip string) { + value := C.CString(tooltip) + C.gtk_widget_set_tooltip_text( + (*C.GtkWidget)(widget), + value) + C.free(unsafe.Pointer(value)) +} + +func menuItemSignalBlock(widget pointer, handlerId uint, block bool) { + if block { + C.g_signal_handler_block(C.gpointer(widget), C.ulong(handlerId)) + } else { + C.g_signal_handler_unblock(C.gpointer(widget), C.ulong(handlerId)) + } +} + +func menuRadioItemNew(group *GSList, label string) pointer { + cLabel := C.CString(label) + defer C.free(unsafe.Pointer(cLabel)) + return pointer(C.gtk_radio_menu_item_new_with_label((*C.GSList)(group), cLabel)) +} + +// screen related + +func getScreenByIndex(display *C.struct__GdkDisplay, index int) *Screen { + monitor := C.gdk_display_get_monitor(display, C.int(index)) + // TODO: Do we need to update Screen to contain current info? + // currentMonitor := C.gdk_display_get_monitor_at_window(display, window) + + var geometry C.GdkRectangle + C.gdk_monitor_get_geometry(monitor, &geometry) + primary := false + if C.gdk_monitor_is_primary(monitor) == 1 { + primary = true + } + name := C.gdk_monitor_get_model(monitor) + return &Screen{ + ID: fmt.Sprintf("%d", index), + Name: C.GoString(name), + IsPrimary: primary, + ScaleFactor: float32(C.gdk_monitor_get_scale_factor(monitor)), + X: int(geometry.x), + Y: int(geometry.y), + Size: Size{ + Height: int(geometry.height), + Width: int(geometry.width), + }, + Bounds: Rect{ + X: int(geometry.x), + Y: int(geometry.y), + Height: int(geometry.height), + Width: int(geometry.width), + }, + PhysicalBounds: Rect{ + X: int(geometry.x), + Y: int(geometry.y), + Height: int(geometry.height), + Width: int(geometry.width), + }, + WorkArea: Rect{ + X: int(geometry.x), + Y: int(geometry.y), + Height: int(geometry.height), + Width: int(geometry.width), + }, + PhysicalWorkArea: Rect{ + X: int(geometry.x), + Y: int(geometry.y), + Height: int(geometry.height), + Width: int(geometry.width), + }, + Rotation: 0.0, + } +} + +func getScreens(app pointer) ([]*Screen, error) { + var screens []*Screen + window := C.gtk_application_get_active_window((*C.GtkApplication)(app)) + gdkWindow := C.gtk_widget_get_window((*C.GtkWidget)(unsafe.Pointer(window))) + display := C.gdk_window_get_display(gdkWindow) + count := C.gdk_display_get_n_monitors(display) + for i := 0; i < int(count); i++ { + screens = append(screens, getScreenByIndex(display, i)) + } + return screens, nil +} + +// widgets + +func (w *linuxWebviewWindow) setEnabled(enabled bool) { + var value C.int + if enabled { + value = C.int(1) + } + + C.gtk_widget_set_sensitive(w.gtkWidget(), value) +} + +func widgetSetVisible(widget pointer, hidden bool) { + if hidden { + C.gtk_widget_hide((*C.GtkWidget)(widget)) + } else { + C.gtk_widget_show((*C.GtkWidget)(widget)) + } +} + +func (w *linuxWebviewWindow) close() { + C.gtk_widget_destroy(w.gtkWidget()) + getNativeApplication().unregisterWindow(windowPointer(w.window)) +} + +func (w *linuxWebviewWindow) enableDND() { + // Pass window ID as pointer value (not pointer to ID) - same pattern as other signal handlers + winID := unsafe.Pointer(uintptr(w.parent.id)) + C.enableDND((*C.GtkWidget)(w.webview), C.gpointer(winID)) +} + +func (w *linuxWebviewWindow) disableDND() { + // Block external file drops while allowing internal HTML5 drag-and-drop + winID := unsafe.Pointer(uintptr(w.parent.id)) + C.disableDND((*C.GtkWidget)(w.webview), C.gpointer(winID)) +} + +func (w *linuxWebviewWindow) execJS(js string) { + InvokeAsync(func() { + value := C.CString(js) + C.webkit_web_view_evaluate_javascript(w.webKitWebView(), + value, + C.long(len(js)), + nil, + C.CString(""), + nil, + nil, + nil) + C.free(unsafe.Pointer(value)) + }) +} + +// Preallocated buffer for drag-over JS calls to avoid allocations +// "window._wails.handleDragOver(XXXXX,YYYYY)" is max ~45 chars +var dragOverJSBuffer = C.CString(strings.Repeat(" ", 64)) +var emptyWorldName = C.CString("") + +// execJSDragOver executes JS for drag-over events with zero Go allocations. +// It directly writes to a preallocated C buffer. Must be called from main thread. +func (w *linuxWebviewWindow) execJSDragOver(x, y int) { + // Format: "window._wails.handleDragOver(X,Y)" + // Write directly to C buffer + buf := (*[64]byte)(unsafe.Pointer(dragOverJSBuffer)) + n := copy(buf[:], "window._wails.handleDragOver(") + n += writeInt(buf[n:], x) + buf[n] = ',' + n++ + n += writeInt(buf[n:], y) + buf[n] = ')' + n++ + buf[n] = 0 // null terminate + + C.webkit_web_view_evaluate_javascript(w.webKitWebView(), + dragOverJSBuffer, + C.long(n), + nil, + emptyWorldName, + nil, + nil, + nil) +} + +// writeInt writes an integer to a byte slice and returns the number of bytes written +func writeInt(buf []byte, n int) int { + if n < 0 { + buf[0] = '-' + return 1 + writeInt(buf[1:], -n) + } + if n == 0 { + buf[0] = '0' + return 1 + } + // Count digits + tmp := n + digits := 0 + for tmp > 0 { + digits++ + tmp /= 10 + } + // Write digits in reverse + for i := digits - 1; i >= 0; i-- { + buf[i] = byte('0' + n%10) + n /= 10 + } + return digits +} + +func getMousePosition() (int, int, *Screen) { + var x, y C.gint + var screen *C.GdkScreen + defaultDisplay := C.gdk_display_get_default() + device := C.gdk_seat_get_pointer(C.gdk_display_get_default_seat(defaultDisplay)) + C.gdk_device_get_position(device, &screen, &x, &y) + // Get Monitor for screen + monitor := C.gdk_display_get_monitor_at_point(defaultDisplay, x, y) + geometry := C.GdkRectangle{} + C.gdk_monitor_get_geometry(monitor, &geometry) + scaleFactor := int(C.gdk_monitor_get_scale_factor(monitor)) + return int(x), int(y), &Screen{ + ID: fmt.Sprintf("%d", 0), // A unique identifier for the display + Name: C.GoString(C.gdk_monitor_get_model(monitor)), // The name of the display + ScaleFactor: float32(scaleFactor), // The scale factor of the display + X: int(geometry.x), // The x-coordinate of the top-left corner of the rectangle + Y: int(geometry.y), // The y-coordinate of the top-left corner of the rectangle + Size: Size{ + Height: int(geometry.height), + Width: int(geometry.width), + }, + Bounds: Rect{ + X: int(geometry.x), + Y: int(geometry.y), + Height: int(geometry.height), + Width: int(geometry.width), + }, + WorkArea: Rect{ + X: int(geometry.x), + Y: int(geometry.y), + Height: int(geometry.height), + Width: int(geometry.width), + }, + IsPrimary: false, + Rotation: 0.0, + } +} + +func (w *linuxWebviewWindow) destroy() { + w.parent.markAsDestroyed() + // Free menu + if w.gtkmenu != nil { + C.gtk_widget_destroy((*C.GtkWidget)(w.gtkmenu)) + w.gtkmenu = nil + } + // Free window + C.gtk_widget_destroy(w.gtkWidget()) +} + +func (w *linuxWebviewWindow) fullscreen() { + w.maximise() + //w.lastWidth, w.lastHeight = w.size() + x, y, width, height, scaleFactor := w.getCurrentMonitorGeometry() + if x == -1 && y == -1 && width == -1 && height == -1 { + return + } + w.setMinMaxSize(0, 0, width*scaleFactor, height*scaleFactor) + w.setSize(width*scaleFactor, height*scaleFactor) + C.gtk_window_fullscreen(w.gtkWindow()) + w.setRelativePosition(0, 0) +} + +func (w *linuxWebviewWindow) getCurrentMonitor() *C.GdkMonitor { + display := C.gtk_widget_get_display(w.gtkWidget()) + gdkWindow := C.gtk_widget_get_window(w.gtkWidget()) + if gdkWindow != nil { + monitor := C.gdk_display_get_monitor_at_window(display, gdkWindow) + if monitor != nil { + return monitor + } + } + + // Wayland fallback: find monitor containing the current window + n_monitors := C.gdk_display_get_n_monitors(display) + window_x, window_y := w.position() + for i := 0; i < int(n_monitors); i++ { + test_monitor := C.gdk_display_get_monitor(display, C.int(i)) + if test_monitor != nil { + var rect C.GdkRectangle + C.gdk_monitor_get_geometry(test_monitor, &rect) + + // Check if window is within this monitor's bounds + if window_x >= int(rect.x) && window_x < int(rect.x+rect.width) && + window_y >= int(rect.y) && window_y < int(rect.y+rect.height) { + return test_monitor + } + } + } + + return nil +} + +func (w *linuxWebviewWindow) getScreen() (*Screen, error) { + // Get the current screen for the window + monitor := w.getCurrentMonitor() + name := C.gdk_monitor_get_model(monitor) + mx, my, width, height, scaleFactor := w.getCurrentMonitorGeometry() + return &Screen{ + ID: fmt.Sprintf("%d", w.id), // A unique identifier for the display + Name: C.GoString(name), // The name of the display + ScaleFactor: float32(scaleFactor), // The scale factor of the display + X: mx, // The x-coordinate of the top-left corner of the rectangle + Y: my, // The y-coordinate of the top-left corner of the rectangle + Size: Size{ + Height: int(height), + Width: int(width), + }, + Bounds: Rect{ + X: int(mx), + Y: int(my), + Height: int(height), + Width: int(width), + }, + WorkArea: Rect{ + X: int(mx), + Y: int(my), + Height: int(height), + Width: int(width), + }, + PhysicalBounds: Rect{ + X: int(mx), + Y: int(my), + Height: int(height), + Width: int(width), + }, + PhysicalWorkArea: Rect{ + X: int(mx), + Y: int(my), + Height: int(height), + Width: int(width), + }, + IsPrimary: false, + Rotation: 0.0, + }, nil +} + +func (w *linuxWebviewWindow) getCurrentMonitorGeometry() (x int, y int, width int, height int, scaleFactor int) { + monitor := w.getCurrentMonitor() + if monitor == nil { + // Best effort to find screen resolution of default monitor + display := C.gdk_display_get_default() + monitor = C.gdk_display_get_primary_monitor(display) + if monitor == nil { + return -1, -1, -1, -1, 1 + } + } + var result C.GdkRectangle + C.gdk_monitor_get_geometry(monitor, &result) + scaleFactor = int(C.gdk_monitor_get_scale_factor(monitor)) + return int(result.x), int(result.y), int(result.width), int(result.height), scaleFactor +} + +func (w *linuxWebviewWindow) size() (int, int) { + var windowWidth C.int + var windowHeight C.int + C.gtk_window_get_size(w.gtkWindow(), &windowWidth, &windowHeight) + return int(windowWidth), int(windowHeight) +} + +func (w *linuxWebviewWindow) relativePosition() (int, int) { + x, y := w.position() + // The position must be relative to the screen it is on + // We need to get the screen it is on + monitor := w.getCurrentMonitor() + geometry := C.GdkRectangle{} + C.gdk_monitor_get_geometry(monitor, &geometry) + x = x - int(geometry.x) + y = y - int(geometry.y) + + // TODO: Scale based on DPI + + return x, y +} + +func (w *linuxWebviewWindow) gtkWidget() *C.GtkWidget { + return (*C.GtkWidget)(w.window) +} + +func (w *linuxWebviewWindow) windowHide() { + C.gtk_widget_hide(w.gtkWidget()) +} + +func (w *linuxWebviewWindow) isFullscreen() bool { + gdkWindow := C.gtk_widget_get_window(w.gtkWidget()) + state := C.gdk_window_get_state(gdkWindow) + return state&C.GDK_WINDOW_STATE_FULLSCREEN > 0 +} + +func (w *linuxWebviewWindow) isFocused() bool { + // returns true if window is focused + return C.gtk_window_has_toplevel_focus(w.gtkWindow()) == 1 +} + +func (w *linuxWebviewWindow) isMaximised() bool { + gdkwindow := C.gtk_widget_get_window(w.gtkWidget()) + state := C.gdk_window_get_state(gdkwindow) + return state&C.GDK_WINDOW_STATE_MAXIMIZED > 0 && state&C.GDK_WINDOW_STATE_FULLSCREEN == 0 +} + +func (w *linuxWebviewWindow) isMinimised() bool { + gdkwindow := C.gtk_widget_get_window(w.gtkWidget()) + state := C.gdk_window_get_state(gdkwindow) + return state&C.GDK_WINDOW_STATE_ICONIFIED > 0 +} + +func (w *linuxWebviewWindow) isVisible() bool { + if C.gtk_widget_is_visible(w.gtkWidget()) == 1 { + return true + } + return false +} + +func (w *linuxWebviewWindow) maximise() { + C.gtk_window_maximize(w.gtkWindow()) +} + +func (w *linuxWebviewWindow) minimise() { + C.gtk_window_iconify(w.gtkWindow()) +} + +func windowNew(application pointer, menu pointer, windowId uint, gpuPolicy WebviewGpuPolicy) (window, webview, vbox pointer) { + window = pointer(C.gtk_application_window_new((*C.GtkApplication)(application))) + C.g_object_ref_sink(C.gpointer(window)) + webview = windowNewWebview(windowId, gpuPolicy) + vbox = pointer(C.gtk_box_new(C.GTK_ORIENTATION_VERTICAL, 0)) + name := C.CString("webview-box") + defer C.free(unsafe.Pointer(name)) + C.gtk_widget_set_name((*C.GtkWidget)(vbox), name) + + C.gtk_container_add((*C.GtkContainer)(window), (*C.GtkWidget)(vbox)) + if menu != nil { + C.gtk_box_pack_start((*C.GtkBox)(vbox), (*C.GtkWidget)(menu), 0, 0, 0) + } + C.gtk_box_pack_start((*C.GtkBox)(unsafe.Pointer(vbox)), (*C.GtkWidget)(webview), 1, 1, 0) + return +} + +func windowNewWebview(parentId uint, gpuPolicy WebviewGpuPolicy) pointer { + c := NewCalloc() + defer c.Free() + manager := C.webkit_user_content_manager_new() + C.webkit_user_content_manager_register_script_message_handler(manager, c.String("external")) + webView := C.webkit_web_view_new_with_user_content_manager(manager) + + fixSignalHandlers.Do(func() { + C.install_signal_handlers() + }) + + C.save_webview_to_content_manager(unsafe.Pointer(manager), unsafe.Pointer(webView)) + + // attach window id to both the webview and contentmanager + C.save_window_id(unsafe.Pointer(webView), C.uint(parentId)) + C.save_window_id(unsafe.Pointer(manager), C.uint(parentId)) + + registerURIScheme.Do(func() { + context := C.webkit_web_view_get_context(C.webkit_web_view(webView)) + C.webkit_web_context_register_uri_scheme( + context, + c.String("wails"), + C.WebKitURISchemeRequestCallback(C.onProcessRequest), + nil, + nil) + }) + settings := C.webkit_web_view_get_settings((*C.WebKitWebView)(unsafe.Pointer(webView))) + C.webkit_settings_set_user_agent_with_application_details(settings, c.String("wails.io"), c.String("")) + + switch gpuPolicy { + case WebviewGpuPolicyAlways: + C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS) + break + case WebviewGpuPolicyOnDemand: + C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND) + break + case WebviewGpuPolicyNever: + C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER) + break + default: + C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND) + } + return pointer(webView) +} + +func (w *linuxWebviewWindow) present() { + C.gtk_window_present(w.gtkWindow()) + // gtk_window_unminimize (w.gtkWindow()) /// gtk4 +} + +func (w *linuxWebviewWindow) setSize(width, height int) { + C.gtk_window_resize( + w.gtkWindow(), + C.gint(width), + C.gint(height)) +} + +func (w *linuxWebviewWindow) windowShow() { + if w.gtkWidget() == nil { + return + } + // Realize the window first to ensure it has a valid GdkWindow. + // This prevents crashes on Wayland when appmenu-gtk-module tries to + // set DBus properties for global menu integration before the window + // is fully realized. See: https://github.com/wailsapp/wails/issues/4769 + C.gtk_widget_realize(w.gtkWidget()) + C.gtk_widget_show_all(w.gtkWidget()) +} + +func windowIgnoreMouseEvents(window pointer, webview pointer, ignore bool) { + var enable C.int + if ignore { + enable = 1 + } + gdkWindow := (*C.GdkWindow)(window) + C.gdk_window_set_pass_through(gdkWindow, enable) + C.webkit_web_view_set_editable((*C.WebKitWebView)(webview), C.gboolean(enable)) +} + +func (w *linuxWebviewWindow) webKitWebView() *C.WebKitWebView { + return (*C.WebKitWebView)(w.webview) +} + +func (w *linuxWebviewWindow) setBorderless(borderless bool) { + C.gtk_window_set_decorated(w.gtkWindow(), gtkBool(!borderless)) +} + +func (w *linuxWebviewWindow) setResizable(resizable bool) { + C.gtk_window_set_resizable(w.gtkWindow(), gtkBool(resizable)) +} + +func (w *linuxWebviewWindow) setDefaultSize(width int, height int) { + C.gtk_window_set_default_size(w.gtkWindow(), C.gint(width), C.gint(height)) +} + +func (w *linuxWebviewWindow) setBackgroundColour(colour RGBA) { + rgba := C.GdkRGBA{C.double(colour.Red) / 255.0, C.double(colour.Green) / 255.0, C.double(colour.Blue) / 255.0, C.double(colour.Alpha) / 255.0} + C.webkit_web_view_set_background_color((*C.WebKitWebView)(w.webview), &rgba) + + cssStr := C.CString(fmt.Sprintf("#webview-box {background-color: rgba(%d, %d, %d, %1.1f);}", colour.Red, colour.Green, colour.Blue, float32(colour.Alpha)/255.0)) + provider := C.gtk_css_provider_new() + C.gtk_style_context_add_provider( + C.gtk_widget_get_style_context((*C.GtkWidget)(w.vbox)), + (*C.GtkStyleProvider)(unsafe.Pointer(provider)), + C.GTK_STYLE_PROVIDER_PRIORITY_USER) + C.g_object_unref(C.gpointer(provider)) + C.gtk_css_provider_load_from_data(provider, cssStr, -1, nil) + C.free(unsafe.Pointer(cssStr)) +} + +func getPrimaryScreen() (*Screen, error) { + display := C.gdk_display_get_default() + monitor := C.gdk_display_get_primary_monitor(display) + geometry := C.GdkRectangle{} + C.gdk_monitor_get_geometry(monitor, &geometry) + scaleFactor := int(C.gdk_monitor_get_scale_factor(monitor)) + // get the name for the screen + name := C.gdk_monitor_get_model(monitor) + return &Screen{ + ID: "0", + Name: C.GoString(name), + IsPrimary: true, + X: int(geometry.x), + Y: int(geometry.y), + Size: Size{ + Height: int(geometry.height), + Width: int(geometry.width), + }, + Bounds: Rect{ + X: int(geometry.x), + Y: int(geometry.y), + Height: int(geometry.height), + Width: int(geometry.width), + }, + ScaleFactor: float32(scaleFactor), + }, nil +} + +func windowSetGeometryHints(window pointer, minWidth, minHeight, maxWidth, maxHeight int) { + size := C.GdkGeometry{ + min_width: C.int(minWidth), + min_height: C.int(minHeight), + max_width: C.int(maxWidth), + max_height: C.int(maxHeight), + } + C.gtk_window_set_geometry_hints((*C.GtkWindow)(window), nil, &size, C.GDK_HINT_MAX_SIZE|C.GDK_HINT_MIN_SIZE) +} + +func (w *linuxWebviewWindow) setFrameless(frameless bool) { + C.gtk_window_set_decorated(w.gtkWindow(), gtkBool(!frameless)) + // TODO: Deal with transparency for the titlebar if possible when !frameless + // Perhaps we just make it undecorated and add a menu bar inside? +} + +// TODO: confirm this is working properly +func (w *linuxWebviewWindow) setHTML(html string) { + cHTML := C.CString(html) + uri := C.CString("wails://") + empty := C.CString("") + defer C.free(unsafe.Pointer(cHTML)) + defer C.free(unsafe.Pointer(uri)) + defer C.free(unsafe.Pointer(empty)) + C.webkit_web_view_load_alternate_html( + w.webKitWebView(), + cHTML, + uri, + empty) +} + +func (w *linuxWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) { + C.gtk_window_set_keep_above(w.gtkWindow(), gtkBool(alwaysOnTop)) +} + +func (w *linuxWebviewWindow) flash(_ bool) { + // Not supported on Linux +} + +func (w *linuxWebviewWindow) setTitle(title string) { + if !w.parent.options.Frameless { + cTitle := C.CString(title) + C.gtk_window_set_title(w.gtkWindow(), cTitle) + C.free(unsafe.Pointer(cTitle)) + } +} + +func (w *linuxWebviewWindow) setIcon(icon pointer) { + if icon != nil { + C.gtk_window_set_icon(w.gtkWindow(), (*C.GdkPixbuf)(icon)) + } +} + +func (w *linuxWebviewWindow) gtkWindow() *C.GtkWindow { + return (*C.GtkWindow)(w.window) +} + +func (w *linuxWebviewWindow) setTransparent() { + screen := C.gtk_widget_get_screen(w.gtkWidget()) + visual := C.gdk_screen_get_rgba_visual(screen) + + if visual != nil && C.gdk_screen_is_composited(screen) == C.int(1) { + C.gtk_widget_set_app_paintable(w.gtkWidget(), C.gboolean(1)) + C.gtk_widget_set_visual(w.gtkWidget(), visual) + } +} + +func (w *linuxWebviewWindow) setURL(uri string) { + target := C.CString(uri) + C.webkit_web_view_load_uri(w.webKitWebView(), target) + C.free(unsafe.Pointer(target)) +} + +//export emit +func emit(we *C.WindowEvent) { + window, _ := globalApplication.Window.GetByID(uint(we.id)) + if window != nil { + windowEvents <- &windowEvent{ + WindowID: window.ID(), + EventID: uint(events.WindowEventType(we.event)), + } + } +} + +//export handleConfigureEvent +func handleConfigureEvent(widget *C.GtkWidget, event *C.GdkEventConfigure, data C.uintptr_t) C.gboolean { + window, _ := globalApplication.Window.GetByID(uint(data)) + if window != nil { + lw := getLinuxWebviewWindow(window) + if lw == nil { + return C.gboolean(1) + } + if lw.lastX != int(event.x) || lw.lastY != int(event.y) { + lw.moveDebouncer(func() { + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowDidMove)) + }) + } + + if lw.lastWidth != int(event.width) || lw.lastHeight != int(event.height) { + lw.resizeDebouncer(func() { + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowDidResize)) + }) + } + + lw.lastX = int(event.x) + lw.lastY = int(event.y) + lw.lastWidth = int(event.width) + lw.lastHeight = int(event.height) + } + + return C.gboolean(0) +} + +//export handleDeleteEvent +func handleDeleteEvent(widget *C.GtkWidget, event *C.GdkEvent, data C.uintptr_t) C.gboolean { + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowDeleteEvent)) + return C.gboolean(1) +} + +//export handleFocusEvent +func handleFocusEvent(widget *C.GtkWidget, event *C.GdkEvent, data C.uintptr_t) C.gboolean { + focusEvent := (*C.GdkEventFocus)(unsafe.Pointer(event)) + if focusEvent._type == C.GDK_FOCUS_CHANGE { + if focusEvent.in == C.TRUE { + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowFocusIn)) + } else { + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowFocusOut)) + } + } + return C.gboolean(0) +} + +//export handleLoadChanged +func handleLoadChanged(webview *C.WebKitWebView, event C.WebKitLoadEvent, data C.uintptr_t) { + switch event { + case C.WEBKIT_LOAD_STARTED: + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowLoadStarted)) + case C.WEBKIT_LOAD_REDIRECTED: + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowLoadRedirected)) + case C.WEBKIT_LOAD_COMMITTED: + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowLoadCommitted)) + case C.WEBKIT_LOAD_FINISHED: + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowLoadFinished)) + } +} + +func (w *linuxWebviewWindow) setupSignalHandlers(emit func(e events.WindowEventType)) { + + c := NewCalloc() + defer c.Free() + + winID := unsafe.Pointer(uintptr(C.uint(w.parent.ID()))) + + // Set up the window close event + wv := unsafe.Pointer(w.webview) + C.signal_connect(unsafe.Pointer(w.window), c.String("delete-event"), C.handleDeleteEvent, winID) + C.signal_connect(unsafe.Pointer(w.window), c.String("focus-out-event"), C.handleFocusEvent, winID) + C.signal_connect(wv, c.String("load-changed"), C.handleLoadChanged, winID) + C.signal_connect(unsafe.Pointer(w.window), c.String("configure-event"), C.handleConfigureEvent, winID) + + contentManager := C.webkit_web_view_get_user_content_manager(w.webKitWebView()) + C.signal_connect(unsafe.Pointer(contentManager), c.String("script-message-received::external"), C.sendMessageToBackend, nil) + C.signal_connect(wv, c.String("button-press-event"), C.onButtonEvent, winID) + C.signal_connect(wv, c.String("button-release-event"), C.onButtonEvent, winID) + C.signal_connect(wv, c.String("key-press-event"), C.onKeyPressEvent, winID) +} + +func getMouseButtons() (bool, bool, bool) { + var pointer *C.GdkDevice + var state C.GdkModifierType + pointer = C.gdk_seat_get_pointer(C.gdk_display_get_default_seat(C.gdk_display_get_default())) + C.gdk_device_get_state(pointer, nil, nil, &state) + return state&C.GDK_BUTTON1_MASK > 0, state&C.GDK_BUTTON2_MASK > 0, state&C.GDK_BUTTON3_MASK > 0 +} + +func openDevTools(webview pointer) { + inspector := C.webkit_web_view_get_inspector((*C.WebKitWebView)(webview)) + C.webkit_web_inspector_show(inspector) +} + +func (w *linuxWebviewWindow) startDrag() error { + C.gtk_window_begin_move_drag( + (*C.GtkWindow)(w.window), + C.int(w.drag.MouseButton), + C.int(w.drag.XRoot), + C.int(w.drag.YRoot), + C.uint32_t(w.drag.DragTime)) + return nil +} + +func enableDevTools(webview pointer) { + settings := C.webkit_web_view_get_settings((*C.WebKitWebView)(webview)) + enabled := C.webkit_settings_get_enable_developer_extras(settings) + switch enabled { + case C.int(0): + enabled = C.int(1) + case C.int(1): + enabled = C.int(0) + } + C.webkit_settings_set_enable_developer_extras(settings, enabled) +} + +func (w *linuxWebviewWindow) unfullscreen() { + C.gtk_window_unfullscreen((*C.GtkWindow)(w.window)) + w.unmaximise() +} + +func (w *linuxWebviewWindow) unmaximise() { + C.gtk_window_unmaximize((*C.GtkWindow)(w.window)) +} + +func (w *linuxWebviewWindow) getZoom() float64 { + return float64(C.webkit_web_view_get_zoom_level(w.webKitWebView())) +} + +func (w *linuxWebviewWindow) zoomIn() { + // FIXME: ZoomIn/Out is assumed to be incorrect! + ZoomInFactor := 1.10 + w.setZoom(w.getZoom() * ZoomInFactor) +} + +func (w *linuxWebviewWindow) zoomOut() { + ZoomInFactor := -1.10 + w.setZoom(w.getZoom() * ZoomInFactor) +} + +func (w *linuxWebviewWindow) zoomReset() { + w.setZoom(1.0) +} + +func (w *linuxWebviewWindow) reload() { + uri := C.CString("wails://") + C.webkit_web_view_load_uri(w.webKitWebView(), uri) + C.free(unsafe.Pointer(uri)) +} + +func (w *linuxWebviewWindow) setZoom(zoom float64) { + if zoom < 1 { // 1.0 is the smallest allowable + zoom = 1 + } + C.webkit_web_view_set_zoom_level(w.webKitWebView(), C.double(zoom)) +} + +func (w *linuxWebviewWindow) move(x, y int) { + // Move the window to these coordinates + C.gtk_window_move(w.gtkWindow(), C.int(x), C.int(y)) +} + +func (w *linuxWebviewWindow) position() (int, int) { + var x C.int + var y C.int + C.gtk_window_get_position((*C.GtkWindow)(w.window), &x, &y) + return int(x), int(y) +} + +func (w *linuxWebviewWindow) ignoreMouse(ignore bool) { + if ignore { + C.gtk_widget_set_events((*C.GtkWidget)(unsafe.Pointer(w.window)), C.GDK_ENTER_NOTIFY_MASK|C.GDK_LEAVE_NOTIFY_MASK) + } else { + C.gtk_widget_set_events((*C.GtkWidget)(unsafe.Pointer(w.window)), C.GDK_ALL_EVENTS_MASK) + } +} + +// FIXME Change this to reflect mouse button! +// +//export onButtonEvent +func onButtonEvent(_ *C.GtkWidget, event *C.GdkEventButton, data C.uintptr_t) C.gboolean { + // Constants (defined here to be easier to use with purego) + GdkButtonPress := C.GDK_BUTTON_PRESS // 4 + Gdk2ButtonPress := C.GDK_2BUTTON_PRESS // 5 for double-click + GdkButtonRelease := C.GDK_BUTTON_RELEASE // 7 + + windowId := uint(C.uint(data)) + window, _ := globalApplication.Window.GetByID(windowId) + if window == nil { + return C.gboolean(0) + } + lw := getLinuxWebviewWindow(window) + if lw == nil { + return C.gboolean(0) + } + + if event == nil { + return C.gboolean(0) + } + if event.button == 3 { + return C.gboolean(0) + } + + switch int(event._type) { + case GdkButtonPress: + lw.drag.MouseButton = uint(event.button) + lw.drag.XRoot = int(event.x_root) + lw.drag.YRoot = int(event.y_root) + lw.drag.DragTime = uint32(event.time) + case Gdk2ButtonPress: + // do we need something here? + case GdkButtonRelease: + lw.endDrag(uint(event.button), int(event.x_root), int(event.y_root)) + } + + return C.gboolean(0) +} + +//export onMenuButtonEvent +func onMenuButtonEvent(_ *C.GtkWidget, event *C.GdkEventButton, data C.uintptr_t) C.gboolean { + // Constants (defined here to be easier to use with purego) + GdkButtonRelease := C.GDK_BUTTON_RELEASE // 7 + + windowId := uint(C.uint(data)) + window, _ := globalApplication.Window.GetByID(windowId) + if window == nil { + return C.gboolean(0) + } + lw := getLinuxWebviewWindow(window) + if lw == nil { + return C.gboolean(0) + } + + // prevent custom context menu from closing immediately + if event.button == 3 && int(event._type) == GdkButtonRelease && lw.ctxMenuOpened { + lw.ctxMenuOpened = false + return C.gboolean(1) + } + + return C.gboolean(0) +} + +//export onDragEnter +func onDragEnter(data unsafe.Pointer) { + windowId := uint(uintptr(data)) + targetWindow, ok := globalApplication.Window.GetByID(windowId) + if !ok || targetWindow == nil { + return + } + // HandleDragEnter is Linux-specific (GTK intercepts drag events) + if w, ok := targetWindow.(*WebviewWindow); ok { + w.HandleDragEnter() + } +} + +//export onDragLeave +func onDragLeave(data unsafe.Pointer) { + windowId := uint(uintptr(data)) + targetWindow, ok := globalApplication.Window.GetByID(windowId) + if !ok || targetWindow == nil { + return + } + // HandleDragLeave is Linux-specific (GTK intercepts drag events) + if w, ok := targetWindow.(*WebviewWindow); ok { + w.HandleDragLeave() + } +} + +//export onDragOver +func onDragOver(x C.gint, y C.gint, data unsafe.Pointer) { + windowId := uint(uintptr(data)) + targetWindow, ok := globalApplication.Window.GetByID(windowId) + if !ok || targetWindow == nil { + return + } + // HandleDragOver is Linux-specific (GTK intercepts drag events) + if w, ok := targetWindow.(*WebviewWindow); ok { + w.HandleDragOver(int(x), int(y)) + } +} + +//export onUriList +func onUriList(extracted **C.char, x C.gint, y C.gint, data unsafe.Pointer) { + // Credit: https://groups.google.com/g/golang-nuts/c/bI17Bpck8K4/m/DVDa7EMtDAAJ + offset := unsafe.Sizeof(uintptr(0)) + filenames := []string{} + for *extracted != nil { + filenames = append(filenames, strings.TrimPrefix(C.GoString(*extracted), "file://")) + extracted = (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(extracted)) + offset)) + } + + // Window ID is stored as the pointer value itself (not pointing to memory) + // Same pattern as other signal handlers in this file + windowId := uint(uintptr(data)) + targetWindow, ok := globalApplication.Window.GetByID(windowId) + if !ok || targetWindow == nil { + globalApplication.error("onUriList could not find window with ID: %d", windowId) + return + } + + // Send to frontend for drop target detection and filtering + targetWindow.InitiateFrontendDropProcessing(filenames, int(x), int(y)) +} + +var debounceTimer *time.Timer +var isDebouncing bool = false + +//export onKeyPressEvent +func onKeyPressEvent(_ *C.GtkWidget, event *C.GdkEventKey, userData C.uintptr_t) C.gboolean { + // Keypress re-emits if the key is pressed over a certain threshold so we need a debounce + if isDebouncing { + debounceTimer.Reset(50 * time.Millisecond) + return C.gboolean(0) + } + + // Start the debounce + isDebouncing = true + debounceTimer = time.AfterFunc(50*time.Millisecond, func() { + isDebouncing = false + }) + + windowID := uint(C.uint(userData)) + if accelerator, ok := getKeyboardState(event); ok { + windowKeyEvents <- &windowKeyEvent{ + windowId: windowID, + acceleratorString: accelerator, + } + } + return C.gboolean(0) +} + +func getKeyboardState(event *C.GdkEventKey) (string, bool) { + modifiers := uint(event.state) & C.GDK_MODIFIER_MASK + keyCode := uint(event.keyval) + + var acc accelerator + // Check Accelerators + if modifiers&(C.GDK_SHIFT_MASK) != 0 { + acc.Modifiers = append(acc.Modifiers, ShiftKey) + } + if modifiers&(C.GDK_CONTROL_MASK) != 0 { + acc.Modifiers = append(acc.Modifiers, ControlKey) + } + if modifiers&(C.GDK_MOD1_MASK) != 0 { + acc.Modifiers = append(acc.Modifiers, OptionOrAltKey) + } + if modifiers&(C.GDK_SUPER_MASK) != 0 { + acc.Modifiers = append(acc.Modifiers, SuperKey) + } + keyString, ok := VirtualKeyCodes[keyCode] + if !ok { + return "", false + } + acc.Key = keyString + return acc.String(), true +} + +//export onProcessRequest +func onProcessRequest(request *C.WebKitURISchemeRequest, data C.uintptr_t) { + webView := C.webkit_uri_scheme_request_get_web_view(request) + windowId := uint(C.get_window_id(unsafe.Pointer(webView))) + webviewRequests <- &webViewAssetRequest{ + Request: webview.NewRequest(unsafe.Pointer(request)), + windowId: windowId, + windowName: func() string { + if window, ok := globalApplication.Window.GetByID(windowId); ok { + return window.Name() + } + return "" + }(), + } +} + +//export sendMessageToBackend +func sendMessageToBackend(contentManager *C.WebKitUserContentManager, result *C.WebKitJavascriptResult, + data unsafe.Pointer) { + + // Get the windowID from the contentManager + thisWindowID := uint(C.get_window_id(unsafe.Pointer(contentManager))) + + webView := C.get_webview_from_content_manager(unsafe.Pointer(contentManager)) + var origin string + if webView != nil { + currentUri := C.webkit_web_view_get_uri(webView) + if currentUri != nil { + uri := C.g_strdup(currentUri) + defer C.g_free(C.gpointer(uri)) + origin = C.GoString(uri) + } + } + + var msg string + value := C.webkit_javascript_result_get_js_value(result) + message := C.jsc_value_to_string(value) + msg = C.GoString(message) + defer C.g_free(C.gpointer(message)) + windowMessageBuffer <- &windowMessage{ + windowId: thisWindowID, + message: msg, + originInfo: &OriginInfo{ + Origin: origin, + }, + } +} + +func gtkBool(input bool) C.gboolean { + if input { + return C.gboolean(1) + } + return C.gboolean(0) +} + +// dialog related + +func setWindowIcon(window pointer, icon []byte) { + loader := C.gdk_pixbuf_loader_new() + if loader == nil { + return + } + written := C.gdk_pixbuf_loader_write( + loader, + (*C.uchar)(&icon[0]), + C.ulong(len(icon)), + nil) + if written == 0 { + return + } + C.gdk_pixbuf_loader_close(loader, nil) + pixbuf := C.gdk_pixbuf_loader_get_pixbuf(loader) + if pixbuf != nil { + C.gtk_window_set_icon((*C.GtkWindow)(window), pixbuf) + } + C.g_object_unref(C.gpointer(loader)) +} + +//export messageDialogCB +func messageDialogCB(button C.int) { + fmt.Println("messageDialogCB", button) +} + +func runChooserDialog(window pointer, allowMultiple, createFolders, showHidden bool, currentFolder, title string, action int, acceptLabel string, filters []FileFilter, currentName string) (chan string, error) { + titleStr := C.CString(title) + defer C.free(unsafe.Pointer(titleStr)) + cancelStr := C.CString("_Cancel") + defer C.free(unsafe.Pointer(cancelStr)) + acceptLabelStr := C.CString(acceptLabel) + defer C.free(unsafe.Pointer(acceptLabelStr)) + + fc := C.gtkFileChooserDialogNew( + titleStr, + (*C.GtkWindow)(window), + C.GtkFileChooserAction(action), + cancelStr, + acceptLabelStr) + + C.gtk_file_chooser_set_action((*C.GtkFileChooser)(fc), C.GtkFileChooserAction(action)) + + gtkFilters := []*C.GtkFileFilter{} + for _, filter := range filters { + f := C.gtk_file_filter_new() + displayStr := C.CString(filter.DisplayName) + C.gtk_file_filter_set_name(f, displayStr) + C.free(unsafe.Pointer(displayStr)) + patterns := strings.Split(filter.Pattern, ";") + for _, pattern := range patterns { + patternStr := C.CString(strings.TrimSpace(pattern)) + C.gtk_file_filter_add_pattern(f, patternStr) + C.free(unsafe.Pointer(patternStr)) + } + C.gtk_file_chooser_add_filter((*C.GtkFileChooser)(fc), f) + gtkFilters = append(gtkFilters, f) + } + C.gtk_file_chooser_set_select_multiple( + (*C.GtkFileChooser)(fc), + gtkBool(allowMultiple)) + C.gtk_file_chooser_set_create_folders( + (*C.GtkFileChooser)(fc), + gtkBool(createFolders)) + C.gtk_file_chooser_set_show_hidden( + (*C.GtkFileChooser)(fc), + gtkBool(showHidden)) + + if currentFolder != "" { + path := C.CString(currentFolder) + C.gtk_file_chooser_set_current_folder( + (*C.GtkFileChooser)(fc), + path) + C.free(unsafe.Pointer(path)) + } + + // Set the current name for save dialogs to pre-populate the filename + if currentName != "" && action == C.GTK_FILE_CHOOSER_ACTION_SAVE { + nameStr := C.CString(currentName) + C.gtk_file_chooser_set_current_name( + (*C.GtkFileChooser)(fc), + nameStr) + C.free(unsafe.Pointer(nameStr)) + } + + // FIXME: This should be consolidated - duplicate exists in linux_purego.go + buildStringAndFree := func(s C.gpointer) string { + bytes := []byte{} + p := unsafe.Pointer(s) + for { + val := *(*byte)(p) + if val == 0 { // this is the null terminator + break + } + bytes = append(bytes, val) + p = unsafe.Add(p, 1) + } + C.g_free(s) // so we don't have to iterate a second time + return string(bytes) + } + + selections := make(chan string) + // run this on the gtk thread + InvokeAsync(func() { + response := C.gtk_dialog_run((*C.GtkDialog)(fc)) + go func() { + defer handlePanic() + if response == C.GTK_RESPONSE_ACCEPT { + filenames := C.gtk_file_chooser_get_filenames((*C.GtkFileChooser)(fc)) + iter := filenames + count := 0 + for { + selections <- buildStringAndFree(C.gpointer(iter.data)) + iter = iter.next + if iter == nil || count == 1024 { + break + } + count++ + } + } + close(selections) + }() + }) + C.gtk_widget_destroy((*C.GtkWidget)(unsafe.Pointer(fc))) + return selections, nil +} + +func runOpenFileDialog(dialog *OpenFileDialogStruct) (chan string, error) { + var action int + + if dialog.canChooseDirectories { + action = C.GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + } else { + action = C.GTK_FILE_CHOOSER_ACTION_OPEN + } + + window := nilPointer + if dialog.window != nil { + nativeWindow := dialog.window.NativeWindow() + if nativeWindow != nil { + window = pointer(nativeWindow) + } + } + + buttonText := dialog.buttonText + if buttonText == "" { + buttonText = "_Open" + } + + return runChooserDialog( + window, + dialog.allowsMultipleSelection, + dialog.canCreateDirectories, + dialog.showHiddenFiles, + dialog.directory, + dialog.title, + action, + buttonText, + dialog.filters, + "") +} + +func runQuestionDialog(parent pointer, options *MessageDialog) int { + cMsg := C.CString(options.Message) + cTitle := C.CString(options.Title) + defer C.free(unsafe.Pointer(cMsg)) + defer C.free(unsafe.Pointer(cTitle)) + hasButtons := false + if len(options.Buttons) > 0 { + hasButtons = true + } + + dType, ok := map[DialogType]C.int{ + InfoDialogType: C.GTK_MESSAGE_INFO, + // ErrorDialogType: + QuestionDialogType: C.GTK_MESSAGE_QUESTION, + WarningDialogType: C.GTK_MESSAGE_WARNING, + }[options.DialogType] + if !ok { + // FIXME: Add logging here! + dType = C.GTK_MESSAGE_INFO + } + + dialog := C.new_message_dialog((*C.GtkWindow)(parent), cMsg, dType, C.bool(hasButtons)) + if options.Title != "" { + C.gtk_window_set_title( + (*C.GtkWindow)(unsafe.Pointer(dialog)), + cTitle) + } + + if img, err := pngToImage(options.Icon); err == nil && len(img.Pix) > 0 { + // Use g_bytes_new instead of g_bytes_new_static because Go memory can be + // moved or freed by the GC. g_bytes_new copies the data to C-owned memory. + gbytes := C.g_bytes_new( + C.gconstpointer(unsafe.Pointer(&img.Pix[0])), + C.ulong(len(img.Pix))) + defer C.g_bytes_unref(gbytes) + pixBuf := C.gdk_pixbuf_new_from_bytes( + gbytes, + C.GDK_COLORSPACE_RGB, + 1, // has_alpha + 8, + C.int(img.Bounds().Dx()), + C.int(img.Bounds().Dy()), + C.int(img.Stride), + ) + image := C.gtk_image_new_from_pixbuf(pixBuf) + C.gtk_widget_set_visible((*C.GtkWidget)(image), C.gboolean(1)) + contentArea := C.gtk_dialog_get_content_area((*C.GtkDialog)(dialog)) + C.gtk_container_add( + (*C.GtkContainer)(unsafe.Pointer(contentArea)), + (*C.GtkWidget)(image)) + } + for i, button := range options.Buttons { + cLabel := C.CString(button.Label) + defer C.free(unsafe.Pointer(cLabel)) + index := C.int(i) + C.gtk_dialog_add_button( + (*C.GtkDialog)(dialog), cLabel, index) + if button.IsDefault { + C.gtk_dialog_set_default_response((*C.GtkDialog)(dialog), index) + } + } + + defer C.gtk_widget_destroy((*C.GtkWidget)(dialog)) + return int(C.gtk_dialog_run((*C.GtkDialog)(unsafe.Pointer(dialog)))) +} + +func runSaveFileDialog(dialog *SaveFileDialogStruct) (chan string, error) { + window := nilPointer + buttonText := dialog.buttonText + if buttonText == "" { + buttonText = "_Save" + } + results, err := runChooserDialog( + window, + false, // multiple selection + dialog.canCreateDirectories, + dialog.showHiddenFiles, + dialog.directory, + dialog.title, + C.GTK_FILE_CHOOSER_ACTION_SAVE, + buttonText, + dialog.filters, + dialog.filename) + + return results, err +} + +func (w *linuxWebviewWindow) cut() { + //C.webkit_web_view_execute_editing_command(w.webview, C.WEBKIT_EDITING_COMMAND_CUT) +} + +func (w *linuxWebviewWindow) paste() { + //C.webkit_web_view_execute_editing_command(w.webview, C.WEBKIT_EDITING_COMMAND_PASTE) +} + +func (w *linuxWebviewWindow) copy() { + //C.webkit_web_view_execute_editing_command(w.webview, C.WEBKIT_EDITING_COMMAND_COPY) +} + +func (w *linuxWebviewWindow) selectAll() { + //C.webkit_web_view_execute_editing_command(w.webview, C.WEBKIT_EDITING_COMMAND_SELECT_ALL) +} + +func (w *linuxWebviewWindow) undo() { + //C.webkit_web_view_execute_editing_command(w.webview, C.WEBKIT_EDITING_COMMAND_UNDO) +} + +func (w *linuxWebviewWindow) redo() { +} + +func (w *linuxWebviewWindow) delete() { +} diff --git a/v3/pkg/application/linux_purego.go b/v3/pkg/application/linux_purego.go new file mode 100644 index 000000000..7b2e17235 --- /dev/null +++ b/v3/pkg/application/linux_purego.go @@ -0,0 +1,1275 @@ +//go:build linux && purego + +package application + +import ( + "fmt" + "os" + "unsafe" + + "github.com/ebitengine/purego" + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type windowPointer uintptr +type identifier uint +type pointer uintptr + +// type GSList uintptr +type GSList struct { + data pointer + next *GSList +} + +type GSListPointer *GSList + +const ( + nilPointer pointer = 0 +) + +const ( + GSourceRemove int = 0 + + // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/gdkwindow.h#L121 + GdkHintMinSize = 1 << 1 + GdkHintMaxSize = 1 << 2 + // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/gdkevents.h#L512 + GdkWindowStateIconified = 1 << 1 + GdkWindowStateMaximized = 1 << 2 + GdkWindowStateFullscreen = 1 << 4 + + // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/gtkmessagedialog.h#L87 + GtkButtonsNone int = 0 + GtkButtonsOk = 1 + GtkButtonsClose = 2 + GtkButtonsCancel = 3 + GtkButtonsYesNo = 4 + GtkButtonsOkCancel = 5 + + // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/gtkdialog.h#L36 + GtkDialogModal = 1 << 0 + GtkDialogDestroyWithParent = 1 << 1 + GtkDialogUseHeaderBar = 1 << 2 // actions in header bar instead of action area + + GtkOrientationVertical = 1 + + // enum GtkMessageType + GtkMessageInfo = 0 + GtkMessageWarning = 1 + GtkMessageQuestion = 2 + GtkMessageError = 3 +) + +type GdkGeometry struct { + minWidth int32 + minHeight int32 + maxWidth int32 + maxHeight int32 + baseWidth int32 + baseHeight int32 + widthInc int32 + heightInc int32 + padding int32 + minAspect float64 + maxAspect float64 + GdkGravity int32 +} + +var ( + nilRadioGroup GSListPointer = nil + gtkSignalHandlers map[pointer]uint = map[pointer]uint{} + gtkSignalToMenuItem map[pointer]*MenuItem = map[pointer]*MenuItem{} + mainThreadId uint64 +) + +const ( + // TODO: map distro => so filename - with fallback? + gtk3 = "libgtk-3.so.0" + gtk4 = "libgtk-4.so.1" + webkit4 = "libwebkit2gtk-4.1.so.0" +) + +var ( + gtk uintptr + gtkVersion int + webkit uintptr + + // function references + gApplicationHold func(pointer) + gApplicationQuit func(pointer) + gApplicationName func() string + gApplicationRelease func(pointer) + gApplicationRun func(pointer, int, []string) int + gBytesNew func(uintptr, int) uintptr + gBytesNewStatic func(uintptr, int) uintptr + gBytesUnref func(uintptr) + gFree func(pointer) + gIdleAdd func(uintptr) + gListFree func(*GList) + gObjectRefSink func(pointer) + gObjectUnref func(pointer) + gSignalConnectData func(pointer, string, uintptr, pointer, bool, int) int + gSignalConnectObject func(pointer, string, pointer, pointer, int) uint + gSignalHandlerBlock func(pointer, uint) + gSignalHandlerUnblock func(pointer, uint) + gThreadSelf func() uint64 + + // gdk functions + gdkDisplayGetMonitor func(pointer, int) pointer + gdkDisplayGetMonitorAtWindow func(pointer, pointer) pointer + gdkDisplayGetNMonitors func(pointer) int + gdkMonitorGetGeometry func(pointer, pointer) pointer + gdkMonitorGetScaleFactor func(pointer) int + gdkMonitorIsPrimary func(pointer) int + gdkPixbufNewFromBytes func(uintptr, int, int, int, int, int, int) pointer + gdkRgbaParse func(pointer, string) bool + gdkScreenGetRgbaVisual func(pointer) pointer + gdkScreenIsComposited func(pointer) int + gdkWindowGetState func(pointer) int + gdkWindowGetDisplay func(pointer) pointer + + // gtk functions + gtkApplicationNew func(string, uint) pointer + gtkApplicationGetActiveWindow func(pointer) pointer + gtkApplicationGetWindows func(pointer) *GList + gtkApplicationWindowNew func(pointer) pointer + gtkBoxNew func(int, int) pointer + gtkBoxPackStart func(pointer, pointer, int, int, int) + gtkCheckMenuItemGetActive func(pointer) int + gtkCheckMenuItemNewWithLabel func(string) pointer + gtkCheckMenuItemSetActive func(pointer, int) + gtkContainerAdd func(pointer, pointer) + gtkContainerGetChildren func(pointer) *GList + gtkContainerRemove func(pointer, pointer) + gtkCSSProviderLoadFromData func(pointer, string, int, pointer) + gtkCSSProviderNew func() pointer + gtkDialogAddButton func(pointer, string, int) + gtkDialogGetContentArea func(pointer) pointer + gtkDialogRun func(pointer) int + gtkDialogSetDefaultResponse func(pointer, int) + gtkDragDestSet func(pointer, uint, pointer, uint, uint) + gtkFileChooserAddFilter func(pointer, pointer) + gtkFileChooserDialogNew func(string, pointer, int, string, int, string, int, pointer) pointer + gtkFileChooserGetFilenames func(pointer) *GSList + gtkFileChooserSetAction func(pointer, int) + gtkFileChooserSetCreateFolders func(pointer, bool) + gtkFileChooserSetCurrentFolder func(pointer, string) + gtkFileChooserSetCurrentName func(pointer, string) + gtkFileChooserSetSelectMultiple func(pointer, bool) + gtkFileChooserSetShowHidden func(pointer, bool) + gtkFileFilterAddPattern func(pointer, string) + gtkFileFilterNew func() pointer + gtkFileFilterSetName func(pointer, string) + gtkImageNewFromPixbuf func(pointer) pointer + gtkMenuBarNew func() pointer + gtkMenuItemNewWithLabel func(string) pointer + gtkMenuItemSetLabel func(pointer, string) + gtkMenuItemSetSubmenu func(pointer, pointer) + gtkMenuNew func() pointer + gtkMenuShellAppend func(pointer, pointer) + gtkMessageDialogNew func(pointer, int, int, int, string) pointer + gtkRadioMenuItemGetGroup func(pointer) GSListPointer + gtkRadioMenuItemNewWithLabel func(GSListPointer, string) pointer + gtkSeparatorMenuItemNew func() pointer + gtkStyleContextAddProvider func(pointer, pointer, int) + gtkTargetEntryFree func(pointer) + gtkTargetEntryNew func(string, int, uint) pointer + gtkWidgetDestroy func(pointer) + gtkWidgetGetDisplay func(pointer) pointer + gtkWidgetGetScreen func(pointer) pointer + gtkWidgetGetStyleContext func(pointer) pointer + gtkWidgetGetWindow func(pointer) pointer + gtkWidgetHide func(pointer) + gtkWidgetIsVisible func(pointer) bool + gtkWidgetRealize func(pointer) + gtkWidgetShow func(pointer) + gtkWidgetShowAll func(pointer) + gtkWidgetSetAppPaintable func(pointer, int) + gtkWidgetSetName func(pointer, string) + gtkWidgetSetSensitive func(pointer, int) + gtkWidgetSetToolTipText func(pointer, string) + gtkWidgetSetVisual func(pointer, pointer) + gtkWindowClose func(pointer) + gtkWindowFullScreen func(pointer) + gtkWindowGetPosition func(pointer, *int, *int) bool + gtkWindowGetSize func(pointer, *int, *int) + gtkWindowHasToplevelFocus func(pointer) int + gtkWindowKeepAbove func(pointer, bool) + gtkWindowMaximize func(pointer) + gtkWindowMinimize func(pointer) + gtkWindowMove func(pointer, int, int) + gtkWindowPresent func(pointer) + gtkWindowResize func(pointer, int, int) + gtkWindowSetDecorated func(pointer, int) + gtkWindowSetGeometryHints func(pointer, pointer, pointer, int) + gtkWindowSetKeepAbove func(pointer, bool) + gtkWindowSetResizable func(pointer, bool) + gtkWindowSetTitle func(pointer, string) + gtkWindowUnfullscreen func(pointer) + gtkWindowUnmaximize func(pointer) + + // webkit + webkitNewWithUserContentManager func(pointer) pointer + webkitRegisterUriScheme func(pointer, string, pointer, int, int) + webkitSettingsGetEnableDeveloperExtras func(pointer) bool + webkitSettingsSetHardwareAccelerationPolicy func(pointer, int) + webkitSettingsSetEnableDeveloperExtras func(pointer, bool) + webkitSettingsSetUserAgentWithApplicationDetails func(pointer, string, string) + webkitUserContentManagerNew func() pointer + webkitUserContentManagerRegisterScriptMessageHandler func(pointer, string) + webkitWebContextGetDefault func() pointer + webkitWebViewEvaluateJS func(pointer, string, int, pointer, string, pointer, pointer, pointer) + webkitWebViewGetSettings func(pointer) pointer + webkitWebViewGetZoom func(pointer) float64 + webkitWebViewLoadAlternateHTML func(pointer, string, string, *string) + webkitWebViewLoadUri func(pointer, string) + webkitWebViewSetBackgroundColor func(pointer, pointer) + webkitWebViewSetSettings func(pointer, pointer) + webkitWebViewSetZoomLevel func(pointer, float64) +) + +func init() { + // needed for GTK4 to function + _ = os.Setenv("GDK_BACKEND", "x11") + var err error + + // gtk, err = purego.Dlopen(gtk4, purego.RTLD_NOW|purego.RTLD_GLOBAL) + // if err == nil { + // version = 4 + // return + // } + + // log.Println("Failed to open GTK4: Falling back to GTK3") + + gtk, err = purego.Dlopen(gtk3, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) + } + gtkVersion = 3 + + webkit, err = purego.Dlopen(webkit4, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) + } + + // Function registration + // GLib + purego.RegisterLibFunc(&gApplicationHold, gtk, "g_application_hold") + purego.RegisterLibFunc(&gApplicationName, gtk, "g_get_application_name") + purego.RegisterLibFunc(&gApplicationQuit, gtk, "g_application_quit") + purego.RegisterLibFunc(&gApplicationRelease, gtk, "g_application_release") + purego.RegisterLibFunc(&gApplicationRun, gtk, "g_application_run") + purego.RegisterLibFunc(&gBytesNew, gtk, "g_bytes_new") + purego.RegisterLibFunc(&gBytesNewStatic, gtk, "g_bytes_new_static") + purego.RegisterLibFunc(&gBytesUnref, gtk, "g_bytes_unref") + purego.RegisterLibFunc(&gFree, gtk, "g_free") + purego.RegisterLibFunc(&gIdleAdd, gtk, "g_idle_add") + purego.RegisterLibFunc(&gListFree, gtk, "g_list_free") + purego.RegisterLibFunc(&gObjectRefSink, gtk, "g_object_ref_sink") + purego.RegisterLibFunc(&gObjectUnref, gtk, "g_object_unref") + purego.RegisterLibFunc(&gSignalConnectData, gtk, "g_signal_connect_data") + purego.RegisterLibFunc(&gSignalConnectObject, gtk, "g_signal_connect_object") + purego.RegisterLibFunc(&gSignalHandlerBlock, gtk, "g_signal_handler_block") + purego.RegisterLibFunc(&gSignalHandlerUnblock, gtk, "g_signal_handler_unblock") + purego.RegisterLibFunc(&gThreadSelf, gtk, "g_thread_self") + + // GDK + purego.RegisterLibFunc(&gdkDisplayGetMonitor, gtk, "gdk_display_get_monitor") + purego.RegisterLibFunc(&gdkDisplayGetMonitorAtWindow, gtk, "gdk_display_get_monitor_at_window") + purego.RegisterLibFunc(&gdkDisplayGetNMonitors, gtk, "gdk_display_get_n_monitors") + purego.RegisterLibFunc(&gdkMonitorGetGeometry, gtk, "gdk_monitor_get_geometry") + purego.RegisterLibFunc(&gdkMonitorGetScaleFactor, gtk, "gdk_monitor_get_scale_factor") + purego.RegisterLibFunc(&gdkMonitorIsPrimary, gtk, "gdk_monitor_is_primary") + purego.RegisterLibFunc(&gdkPixbufNewFromBytes, gtk, "gdk_pixbuf_new_from_bytes") + purego.RegisterLibFunc(&gdkRgbaParse, gtk, "gdk_rgba_parse") + purego.RegisterLibFunc(&gdkScreenGetRgbaVisual, gtk, "gdk_screen_get_rgba_visual") + purego.RegisterLibFunc(&gdkScreenIsComposited, gtk, "gdk_screen_is_composited") + purego.RegisterLibFunc(&gdkWindowGetDisplay, gtk, "gdk_window_get_display") + purego.RegisterLibFunc(&gdkWindowGetState, gtk, "gdk_window_get_state") + + // GTK3 + purego.RegisterLibFunc(>kApplicationNew, gtk, "gtk_application_new") + purego.RegisterLibFunc(>kApplicationGetActiveWindow, gtk, "gtk_application_get_active_window") + purego.RegisterLibFunc(>kApplicationGetWindows, gtk, "gtk_application_get_windows") + purego.RegisterLibFunc(>kApplicationWindowNew, gtk, "gtk_application_window_new") + purego.RegisterLibFunc(>kBoxNew, gtk, "gtk_box_new") + purego.RegisterLibFunc(>kBoxPackStart, gtk, "gtk_box_pack_start") + purego.RegisterLibFunc(>kCheckMenuItemGetActive, gtk, "gtk_check_menu_item_get_active") + purego.RegisterLibFunc(>kCheckMenuItemNewWithLabel, gtk, "gtk_check_menu_item_new_with_label") + purego.RegisterLibFunc(>kCheckMenuItemSetActive, gtk, "gtk_check_menu_item_set_active") + purego.RegisterLibFunc(>kContainerAdd, gtk, "gtk_container_add") + purego.RegisterLibFunc(>kContainerGetChildren, gtk, "gtk_container_get_children") + purego.RegisterLibFunc(>kContainerRemove, gtk, "gtk_container_remove") + purego.RegisterLibFunc(>kCSSProviderLoadFromData, gtk, "gtk_css_provider_load_from_data") + purego.RegisterLibFunc(>kDialogAddButton, gtk, "gtk_dialog_add_button") + purego.RegisterLibFunc(>kDialogGetContentArea, gtk, "gtk_dialog_get_content_area") + purego.RegisterLibFunc(>kDialogRun, gtk, "gtk_dialog_run") + purego.RegisterLibFunc(>kDialogSetDefaultResponse, gtk, "gtk_dialog_set_default_response") + purego.RegisterLibFunc(>kDragDestSet, gtk, "gtk_drag_dest_set") + purego.RegisterLibFunc(>kFileChooserAddFilter, gtk, "gtk_file_chooser_add_filter") + purego.RegisterLibFunc(>kFileChooserDialogNew, gtk, "gtk_file_chooser_dialog_new") + purego.RegisterLibFunc(>kFileChooserGetFilenames, gtk, "gtk_file_chooser_get_filenames") + purego.RegisterLibFunc(>kFileChooserSetAction, gtk, "gtk_file_chooser_set_action") + purego.RegisterLibFunc(>kFileChooserSetCreateFolders, gtk, "gtk_file_chooser_set_create_folders") + purego.RegisterLibFunc(>kFileChooserSetCurrentFolder, gtk, "gtk_file_chooser_set_current_folder") + purego.RegisterLibFunc(>kFileChooserSetCurrentName, gtk, "gtk_file_chooser_set_current_name") + purego.RegisterLibFunc(>kFileChooserSetSelectMultiple, gtk, "gtk_file_chooser_set_select_multiple") + purego.RegisterLibFunc(>kFileChooserSetShowHidden, gtk, "gtk_file_chooser_set_show_hidden") + purego.RegisterLibFunc(>kFileFilterAddPattern, gtk, "gtk_file_filter_add_pattern") + purego.RegisterLibFunc(>kFileFilterNew, gtk, "gtk_file_filter_new") + purego.RegisterLibFunc(>kFileFilterSetName, gtk, "gtk_file_filter_set_name") + purego.RegisterLibFunc(>kImageNewFromPixbuf, gtk, "gtk_image_new_from_pixbuf") + purego.RegisterLibFunc(>kMenuItemSetLabel, gtk, "gtk_menu_item_set_label") + purego.RegisterLibFunc(>kMenuBarNew, gtk, "gtk_menu_bar_new") + purego.RegisterLibFunc(>kMenuItemNewWithLabel, gtk, "gtk_menu_item_new_with_label") + purego.RegisterLibFunc(>kMenuItemSetSubmenu, gtk, "gtk_menu_item_set_submenu") + purego.RegisterLibFunc(>kMenuNew, gtk, "gtk_menu_new") + purego.RegisterLibFunc(>kMenuShellAppend, gtk, "gtk_menu_shell_append") + purego.RegisterLibFunc(>kMessageDialogNew, gtk, "gtk_message_dialog_new") + purego.RegisterLibFunc(>kRadioMenuItemGetGroup, gtk, "gtk_radio_menu_item_get_group") + purego.RegisterLibFunc(>kRadioMenuItemNewWithLabel, gtk, "gtk_radio_menu_item_new_with_label") + purego.RegisterLibFunc(>kSeparatorMenuItemNew, gtk, "gtk_separator_menu_item_new") + purego.RegisterLibFunc(>kStyleContextAddProvider, gtk, "gtk_style_context_add_provider") + purego.RegisterLibFunc(>kTargetEntryFree, gtk, "gtk_target_entry_free") + purego.RegisterLibFunc(>kTargetEntryNew, gtk, "gtk_target_entry_new") + purego.RegisterLibFunc(>kWidgetDestroy, gtk, "gtk_widget_destroy") + purego.RegisterLibFunc(>kWidgetGetDisplay, gtk, "gtk_widget_get_display") + purego.RegisterLibFunc(>kWidgetGetScreen, gtk, "gtk_widget_get_screen") + purego.RegisterLibFunc(>kWidgetGetStyleContext, gtk, "gtk_widget_get_style_context") + purego.RegisterLibFunc(>kWidgetGetWindow, gtk, "gtk_widget_get_window") + purego.RegisterLibFunc(>kWidgetHide, gtk, "gtk_widget_hide") + purego.RegisterLibFunc(>kWidgetIsVisible, gtk, "gtk_widget_is_visible") + purego.RegisterLibFunc(>kWidgetRealize, gtk, "gtk_widget_realize") + purego.RegisterLibFunc(>kWidgetSetAppPaintable, gtk, "gtk_widget_set_app_paintable") + purego.RegisterLibFunc(>kWidgetSetName, gtk, "gtk_widget_set_name") + purego.RegisterLibFunc(>kWidgetSetSensitive, gtk, "gtk_widget_set_sensitive") + purego.RegisterLibFunc(>kWidgetSetToolTipText, gtk, "gtk_widget_set_tooltip_text") + purego.RegisterLibFunc(>kWidgetSetVisual, gtk, "gtk_widget_set_visual") + purego.RegisterLibFunc(>kWidgetShow, gtk, "gtk_widget_show") + purego.RegisterLibFunc(>kWidgetShowAll, gtk, "gtk_widget_show_all") + purego.RegisterLibFunc(>kWindowFullScreen, gtk, "gtk_window_fullscreen") + purego.RegisterLibFunc(>kWindowClose, gtk, "gtk_window_close") + purego.RegisterLibFunc(>kWindowGetPosition, gtk, "gtk_window_get_position") + purego.RegisterLibFunc(>kWindowGetSize, gtk, "gtk_window_get_size") + purego.RegisterLibFunc(>kWindowMaximize, gtk, "gtk_window_maximize") + purego.RegisterLibFunc(>kWindowMove, gtk, "gtk_window_move") + purego.RegisterLibFunc(>kWindowPresent, gtk, "gtk_window_present") + //purego.RegisterLibFunc(>kWindowPresent, gtk, "gtk_window_unminimize") // gtk4 + purego.RegisterLibFunc(>kWindowHasToplevelFocus, gtk, "gtk_window_has_toplevel_focus") + purego.RegisterLibFunc(>kWindowMinimize, gtk, "gtk_window_iconify") // gtk3 + // purego.RegisterLibFunc(>kWindowMinimize, gtk, "gtk_window_minimize") // gtk4 + purego.RegisterLibFunc(>kWindowResize, gtk, "gtk_window_resize") + purego.RegisterLibFunc(>kWindowSetGeometryHints, gtk, "gtk_window_set_geometry_hints") + purego.RegisterLibFunc(>kWindowSetDecorated, gtk, "gtk_window_set_decorated") + purego.RegisterLibFunc(>kWindowKeepAbove, gtk, "gtk_window_set_keep_above") + purego.RegisterLibFunc(>kWindowSetResizable, gtk, "gtk_window_set_resizable") + purego.RegisterLibFunc(>kWindowSetTitle, gtk, "gtk_window_set_title") + purego.RegisterLibFunc(>kWindowUnfullscreen, gtk, "gtk_window_unfullscreen") + purego.RegisterLibFunc(>kWindowUnmaximize, gtk, "gtk_window_unmaximize") + + // webkit + purego.RegisterLibFunc(&webkitNewWithUserContentManager, webkit, "webkit_web_view_new_with_user_content_manager") + purego.RegisterLibFunc(&webkitRegisterUriScheme, webkit, "webkit_web_context_register_uri_scheme") + purego.RegisterLibFunc(&webkitSettingsGetEnableDeveloperExtras, webkit, "webkit_settings_get_enable_developer_extras") + purego.RegisterLibFunc(&webkitSettingsSetEnableDeveloperExtras, webkit, "webkit_settings_set_enable_developer_extras") + purego.RegisterLibFunc(&webkitSettingsSetHardwareAccelerationPolicy, webkit, "webkit_settings_set_hardware_acceleration_policy") + purego.RegisterLibFunc(&webkitSettingsSetUserAgentWithApplicationDetails, webkit, "webkit_settings_set_user_agent_with_application_details") + purego.RegisterLibFunc(&webkitUserContentManagerNew, webkit, "webkit_user_content_manager_new") + purego.RegisterLibFunc(&webkitUserContentManagerRegisterScriptMessageHandler, webkit, "webkit_user_content_manager_register_script_message_handler") + purego.RegisterLibFunc(&webkitWebContextGetDefault, webkit, "webkit_web_context_get_default") + purego.RegisterLibFunc(&webkitWebViewEvaluateJS, webkit, "webkit_web_view_evaluate_javascript") + purego.RegisterLibFunc(&webkitWebViewGetSettings, webkit, "webkit_web_view_get_settings") + purego.RegisterLibFunc(&webkitWebViewGetZoom, webkit, "webkit_web_view_get_zoom_level") + purego.RegisterLibFunc(&webkitWebViewLoadAlternateHTML, webkit, "webkit_web_view_load_alternate_html") + purego.RegisterLibFunc(&webkitWebViewLoadUri, webkit, "webkit_web_view_load_uri") + purego.RegisterLibFunc(&webkitWebViewSetBackgroundColor, webkit, "webkit_web_view_set_background_color") + purego.RegisterLibFunc(&webkitWebViewSetSettings, webkit, "webkit_web_view_set_settings") + purego.RegisterLibFunc(&webkitWebViewSetZoomLevel, webkit, "webkit_web_view_set_zoom_level") +} + +// mainthread stuff +func dispatchOnMainThread(id uint) { + gIdleAdd(purego.NewCallback(func(pointer) int { + executeOnMainThread(id) + return GSourceRemove + })) +} + +// implementation below +func appName() string { + return gApplicationName() +} + +func appNew(name string) pointer { + // Use NON_UNIQUE to allow multiple instances of the application to run + // This matches the behavior of gtk_init/gtk_main used in v2 + // G_APPLICATION_NON_UNIQUE = (1 << 5) = 32 + GApplicationNonUnique := uint(32) + + // Name is already sanitized by sanitizeAppName() in application_linux.go + identifier := fmt.Sprintf("org.wails.%s", name) + + return pointer(gtkApplicationNew(identifier, GApplicationNonUnique)) +} + +func appRun(application pointer) error { + mainThreadId = gThreadSelf() + fmt.Println("linux_purego: appRun threadID", mainThreadId) + + app := pointer(application) + activate := func() { + // TODO: Do we care? + fmt.Println("linux.activated!") + gApplicationHold(app) // allow running without a window + } + gSignalConnectData( + application, + "activate", + purego.NewCallback(activate), + app, + false, + 0) + + status := gApplicationRun(app, 0, nil) + gApplicationRelease(app) + gObjectUnref(app) + + var err error + if status != 0 { + err = fmt.Errorf("exit code: %d", status) + } + return err +} + +func appDestroy(application pointer) { + gApplicationQuit(pointer(application)) +} + +func getCurrentWindowID(application pointer, windows map[windowPointer]uint) uint { + // TODO: Add extra metadata to window and use it! + window := gtkApplicationGetActiveWindow(pointer(application)) + identifier, ok := windows[windowPointer(window)] + if ok { + return identifier + } + // FIXME: Should we panic here if not found? + return uint(1) +} + +type GList struct { + data pointer + next *GList + prev *GList +} + +func getWindows(application pointer) []pointer { + result := []pointer{} + windows := gtkApplicationGetWindows(pointer(application)) + // FIXME: Need to make a struct here to deal with response data + for { + result = append(result, pointer(windows.data)) + windows = windows.next + if windows == nil { + return result + } + } +} + +func hideAllWindows(application pointer) { + for _, window := range getWindows(application) { + gtkWidgetHide(window) + } +} + +func showAllWindows(application pointer) { + for _, window := range getWindows(application) { + gtkWidgetShowAll(window) + } +} + +// Menu +func menuAddSeparator(menu *Menu) { + gtkMenuShellAppend( + pointer((menu.impl).(*linuxMenu).native), + gtkSeparatorMenuItemNew()) +} + +func menuAppend(parent *Menu, menu *MenuItem) { + // TODO: override this with the GTK4 version if needed - possibly rename to imply it's an alias + gtkMenuShellAppend( + pointer((parent.impl).(*linuxMenu).native), + pointer((menu.impl).(*linuxMenuItem).native)) + + /* gtk4 + C.gtk_menu_item_set_submenu( + (*C.struct__GtkMenuItem)((menu.impl).(*linuxMenuItem).native), + (*C.struct__GtkWidget)((parent.impl).(*linuxMenu).native), + ) + */ +} + +func menuBarNew() pointer { + return pointer(gtkMenuBarNew()) +} + +func menuNew() pointer { + return pointer(gtkMenuNew()) +} + +func menuSetSubmenu(item *MenuItem, menu *Menu) { + // FIXME: How is this different than `menuAppend` above? + gtkMenuItemSetSubmenu( + pointer((item.impl).(*linuxMenuItem).native), + pointer((menu.impl).(*linuxMenu).native)) +} + +func menuGetRadioGroup(item *linuxMenuItem) *GSList { + return (*GSList)(gtkRadioMenuItemGetGroup(pointer(item.native))) +} + +func menuClear(menu *Menu) { + menuShell := pointer((menu.impl).(*linuxMenu).native) + children := gtkContainerGetChildren(menuShell) + if children != nil { + // Save the original pointer to free later + originalList := children + // Iterate through all children and remove them + for children != nil { + child := children.data + if child != nilPointer { + gtkContainerRemove(menuShell, child) + } + children = children.next + } + gListFree(originalList) + } +} + +func attachMenuHandler(item *MenuItem) { + handleClick := func() { + item := item + switch item.itemType { + case text, checkbox: + menuItemClicked <- item.id + case radio: + menuItem := (item.impl).(*linuxMenuItem) + if menuItem.isChecked() { + menuItemClicked <- item.id + } + } + } + + impl := (item.impl).(*linuxMenuItem) + widget := impl.native + flags := 0 + handlerId := gSignalConnectObject( + pointer(widget), + "activate", + pointer(purego.NewCallback(handleClick)), + pointer(widget), + flags) + impl.handlerId = uint(handlerId) +} + +// menuItem +func menuItemChecked(widget pointer) bool { + if gtkCheckMenuItemGetActive(widget) == 1 { + return true + } + return false +} + +func menuItemNew(label string) pointer { + return pointer(gtkMenuItemNewWithLabel(label)) +} + +func menuCheckItemNew(label string) pointer { + return pointer(gtkCheckMenuItemNewWithLabel(label)) +} + +func menuItemSetChecked(widget pointer, checked bool) { + value := 0 + if checked { + value = 1 + } + gtkCheckMenuItemSetActive(pointer(widget), value) +} + +func menuItemSetDisabled(widget pointer, disabled bool) { + value := 1 + if disabled { + value = 0 + } + gtkWidgetSetSensitive(widget, value) +} + +func menuItemSetLabel(widget pointer, label string) { + gtkMenuItemSetLabel( + pointer(widget), + label) +} + +func menuItemSetToolTip(widget pointer, tooltip string) { + gtkWidgetSetToolTipText( + pointer(widget), + tooltip) +} + +func menuItemSignalBlock(widget pointer, handlerId uint, block bool) { + if block { + gSignalHandlerBlock(widget, handlerId) + } else { + gSignalHandlerUnblock(widget, handlerId) + } +} + +func menuRadioItemNew(group GSListPointer, label string) pointer { + return pointer(gtkRadioMenuItemNewWithLabel(group, label)) +} + +// screen related + +func getScreenByIndex(display pointer, index int) *Screen { + monitor := gdkDisplayGetMonitor(display, index) + // TODO: Do we need to update Screen to contain current info? + // currentMonitor := C.gdk_display_get_monitor_at_window(display, window) + + geometry := struct { + x int32 + y int32 + width int32 + height int32 + }{} + result := pointer(unsafe.Pointer(&geometry)) + gdkMonitorGetGeometry(monitor, result) + + primary := false + if gdkMonitorIsPrimary(monitor) == 1 { + primary = true + } + + return &Screen{ + IsPrimary: primary, + ScaleFactor: 1.0, + X: int(geometry.x), + Y: int(geometry.y), + Size: Size{ + Height: int(geometry.height), + Width: int(geometry.width), + }, + } +} + +func getScreens(app pointer) ([]*Screen, error) { + var screens []*Screen + window := gtkApplicationGetActiveWindow(app) + display := gdkWindowGetDisplay(window) + count := gdkDisplayGetNMonitors(display) + for i := 0; i < int(count); i++ { + screens = append(screens, getScreenByIndex(display, i)) + } + return screens, nil +} + +// widgets +func widgetSetSensitive(widget pointer, enabled bool) { + value := 0 + if enabled { + value = 1 + } + gtkWidgetSetSensitive(widget, value) +} + +func widgetSetVisible(widget pointer, hidden bool) { + if hidden { + gtkWidgetHide(widget) + } else { + gtkWidgetShow(widget) + } +} + +// window related functions +func windowClose(window pointer) { + gtkWindowClose(window) +} + +func windowEnableDND(id uint, webview pointer) { + targetentry := gtkTargetEntryNew("text/uri-list", 0, id) + defer gtkTargetEntryFree(targetentry) + + GtkDestDefaultDrop := uint(0) + GdkActionCopy := uint(0) //? + gtkDragDestSet(webview, GtkDestDefaultDrop, targetentry, 1, GdkActionCopy) + + // FIXME: enable and process + /* gSignalConnectData(webview, + "drag-data-received", + purego.NewCallback(onDragNDrop), + 0, + false, + 0)*/ +} + +func windowExecJS(webview pointer, js string) { + webkitWebViewEvaluateJS( + webview, + js, + len(js), + 0, + "", + 0, + 0, + 0) +} + +func windowDestroy(window pointer) { + // Should this truly 'destroy' ? + gtkWindowClose(window) +} + +func windowFullscreen(window pointer) { + gtkWindowFullScreen(window) +} + +func windowGetPosition(window pointer) (int, int) { + var x, y int + gtkWindowGetPosition(window, &x, &y) + return x, y +} + +func windowGetCurrentMonitor(window pointer) pointer { + // Get the monitor that the window is currently on + display := gtkWidgetGetDisplay(window) + window = gtkWidgetGetWindow(window) + if window == 0 { + return 0 + } + return gdkDisplayGetMonitorAtWindow(display, window) +} + +func windowGetCurrentMonitorGeometry(window pointer) (x int, y int, width int, height int, scaleFactor int) { + monitor := windowGetCurrentMonitor(window) + if monitor == 0 { + return -1, -1, -1, -1, 1 + } + + result := struct { + x int32 + y int32 + width int32 + height int32 + }{} + gdkMonitorGetGeometry(monitor, pointer(unsafe.Pointer(&result))) + return int(result.x), int(result.y), int(result.width), int(result.height), gdkMonitorGetScaleFactor(monitor) +} + +func windowGetRelativePosition(window pointer) (int, int) { + absX, absY := windowGetPosition(window) + x, y, _, _, _ := windowGetCurrentMonitorGeometry(window) + + relX := absX - x + relY := absY - y + + // TODO: Scale based on DPI + return relX, relY +} + +func windowGetSize(window pointer) (int, int) { + // TODO: dispatchOnMainThread? + var width, height int + gtkWindowGetSize(window, &width, &height) + return width, height +} + +func windowGetPosition(window pointer) (int, int) { + // TODO: dispatchOnMainThread? + var x, y int + gtkWindowGetPosition(window, &x, &y) + return x, y +} + +func windowHide(window pointer) { + gtkWidgetHide(window) +} + +func windowIsFocused(window pointer) bool { + return gtkWindowHasToplevelFocus(window) == 1 +} + +func windowIsFullscreen(window pointer) bool { + gdkwindow := gtkWidgetGetWindow(window) + state := gdkWindowGetState(gdkwindow) + return state&GdkWindowStateFullscreen > 0 +} + +func windowIsMaximized(window pointer) bool { + gdkwindow := gtkWidgetGetWindow(window) + state := gdkWindowGetState(gdkwindow) + return state&GdkWindowStateMaximized > 0 && state&GdkWindowStateFullscreen == 0 +} + +func windowIsMinimized(window pointer) bool { + gdkwindow := gtkWidgetGetWindow(window) + state := gdkWindowGetState(gdkwindow) + return state&GdkWindowStateIconified > 0 +} + +func windowIsVisible(window pointer) bool { + // TODO: validate this works.. (used a `bool` in the registration) + return gtkWidgetIsVisible(window) +} + +func windowMaximize(window pointer) { + gtkWindowMaximize(window) +} + +func windowMinimize(window pointer) { + gtkWindowMinimize(window) +} + +func windowNew(application pointer, menu pointer, windowId uint, gpuPolicy int) (pointer, pointer, pointer) { + window := gtkApplicationWindowNew(application) + gObjectRefSink(window) + webview := windowNewWebview(windowId, gpuPolicy) + vbox := gtkBoxNew(GtkOrientationVertical, 0) + gtkContainerAdd(window, vbox) + gtkWidgetSetName(vbox, "webview-box") + + if menu != 0 { + gtkBoxPackStart(vbox, menu, 0, 0, 0) + } + gtkBoxPackStart(vbox, webview, 1, 1, 0) + return pointer(window), pointer(webview), pointer(vbox) +} + +func windowNewWebview(parentId uint, gpuPolicy int) pointer { + manager := webkitUserContentManagerNew() + webkitUserContentManagerRegisterScriptMessageHandler(manager, "external") + wv := webkitNewWithUserContentManager(manager) + if !registered { + webkitRegisterUriScheme( + webkitWebContextGetDefault(), + "wails", + pointer(purego.NewCallback(func(request uintptr) { + webviewRequests <- &webViewAssetRequest{ + Request: webview.NewRequest(request), + windowId: parentId, + windowName: func() string { + if window, ok := globalApplication.Window.GetByID(parentId); ok { + return window.Name() + } + return "" + }(), + } + })), + 0, + 0, + ) + registered = true + } + + settings := webkitWebViewGetSettings(wv) + webkitSettingsSetUserAgentWithApplicationDetails( + settings, + "wails.io", + "") + webkitSettingsSetHardwareAccelerationPolicy(settings, gpuPolicy) + webkitWebViewSetSettings(wv, settings) + return wv +} + +func windowPresent(window pointer) { + gtkWindowPresent(pointer(window)) +} + +func windowReload(webview pointer, address string) { + webkitWebViewLoadUri(pointer(webview), address) +} + +func windowResize(window pointer, width, height int) { + gtkWindowResize(window, width, height) +} + +func windowShow(window pointer) { + // Realize the window first to ensure it has a valid GdkWindow. + // This prevents crashes on Wayland when appmenu-gtk-module tries to + // set DBus properties for global menu integration before the window + // is fully realized. See: https://github.com/wailsapp/wails/issues/4769 + gtkWidgetRealize(pointer(window)) + gtkWidgetShowAll(pointer(window)) +} + +func windowSetBackgroundColour(vbox, webview pointer, colour RGBA) { + const GtkStyleProviderPriorityUser = 800 + + // FIXME: Use a struct! + rgba := make([]byte, 4*8) // C.sizeof_GdkRGBA == 32 + rgbaPointer := pointer(unsafe.Pointer(&rgba[0])) + if !gdkRgbaParse( + rgbaPointer, + fmt.Sprintf("rgba(%v,%v,%v,%v)", + colour.Red, + colour.Green, + colour.Blue, + float32(colour.Alpha)/255.0, + )) { + return + } + webkitWebViewSetBackgroundColor(pointer(webview), rgbaPointer) + + colour.Alpha = 255 + css := fmt.Sprintf("#webview-box {background-color: rgba(%d, %d, %d, %1.1f);}", colour.Red, colour.Green, colour.Blue, float32(colour.Alpha)/255.0) + provider := gtkCSSProviderNew() + defer gObjectUnref(provider) + gtkStyleContextAddProvider( + gtkWidgetGetStyleContext(vbox), + provider, + GtkStyleProviderPriorityUser, + ) + gtkCSSProviderLoadFromData(provider, css, -1, 0) +} + +func windowSetGeometryHints(window pointer, minWidth, minHeight, maxWidth, maxHeight int) { + size := GdkGeometry{ + minWidth: int32(minWidth), + minHeight: int32(minHeight), + maxWidth: int32(maxWidth), + maxHeight: int32(maxHeight), + } + gtkWindowSetGeometryHints( + pointer(window), + pointer(0), + pointer(unsafe.Pointer(&size)), + GdkHintMinSize|GdkHintMaxSize) +} + +func windowSetFrameless(window pointer, frameless bool) { + decorated := 1 + if frameless { + decorated = 0 + } + gtkWindowSetDecorated(pointer(window), decorated) + + // TODO: Deal with transparency for the titlebar if possible when !frameless + // Perhaps we just make it undecorated and add a menu bar inside? +} + +// TODO: confirm this is working properly +func windowSetHTML(webview pointer, html string) { + webkitWebViewLoadAlternateHTML(webview, html, "wails://", nil) +} + +func windowSetKeepAbove(window pointer, alwaysOnTop bool) { + gtkWindowKeepAbove(window, alwaysOnTop) +} + +func windowSetResizable(window pointer, resizable bool) { + // FIXME: Does this work? + gtkWindowSetResizable( + pointer(window), + resizable, + ) +} + +func windowSetTitle(window pointer, title string) { + gtkWindowSetTitle(pointer(window), title) +} + +func windowSetTransparent(window pointer) { + screen := gtkWidgetGetScreen(pointer(window)) + visual := gdkScreenGetRgbaVisual(screen) + if visual == 0 { + return + } + if gdkScreenIsComposited(screen) == 1 { + gtkWidgetSetAppPaintable(pointer(window), 1) + gtkWidgetSetVisual(pointer(window), visual) + } +} + +func windowSetURL(webview pointer, uri string) { + webkitWebViewLoadUri(webview, uri) +} + +func windowSetupSignalHandlers(windowId uint, window, webview pointer, emit func(e events.WindowEventType)) { + handleDelete := purego.NewCallback(func(pointer) { + emit(events.Common.WindowClosing) + }) + gSignalConnectData(window, "delete-event", handleDelete, 0, false, 0) + + /* + event = C.CString("load-changed") + defer C.free(unsafe.Pointer(event)) + C.signal_connect(webview, event, C.webviewLoadChanged, unsafe.Pointer(&w.parent.id)) + */ + + // TODO: Handle mouse button / drag events + /* id := C.uint(windowId) + event = C.CString("button-press-event") + C.signal_connect((*C.GtkWidget)(unsafe.Pointer(webview)), event, C.onButtonEvent, unsafe.Pointer(&id)) + C.free(unsafe.Pointer(event)) + event = C.CString("button-release-event") + defer C.free(unsafe.Pointer(event)) + C.signal_connect((*C.GtkWidget)(unsafe.Pointer(webview)), event, onButtonEvent, unsafe.Pointer(&id)) + */ +} + +func windowOpenDevTools(webview pointer) { + settings := webkitWebViewGetSettings(pointer(webview)) + webkitSettingsSetEnableDeveloperExtras( + settings, + !webkitSettingsGetEnableDeveloperExtras(settings)) +} + +func windowUnfullscreen(window pointer) { + gtkWindowUnfullscreen(window) +} + +func windowUnmaximize(window pointer) { + gtkWindowUnmaximize(window) +} + +func windowZoom(webview pointer) float64 { + return webkitWebViewGetZoom(webview) +} + +func windowZoomIn(webview pointer) { + ZoomInFactor := 1.10 + windowZoomSet(webview, windowZoom(webview)*ZoomInFactor) +} +func windowZoomOut(webview pointer) { + ZoomOutFactor := -1.10 + windowZoomSet(webview, windowZoom(webview)*ZoomOutFactor) +} + +func windowZoomSet(webview pointer, zoom float64) { + if zoom < 1.0 { // 1.0 is the smallest allowable + zoom = 1.0 + } + webkitWebViewSetZoomLevel(webview, zoom) +} + +func windowMove(window pointer, x, y int) { + gtkWindowMove(window, x, y) +} + +func runChooserDialog(window pointer, allowMultiple, createFolders, showHidden bool, currentFolder, title string, action int, acceptLabel string, filters []FileFilter, currentName string) ([]string, error) { + GtkResponseCancel := 0 + GtkResponseAccept := 1 + + fc := gtkFileChooserDialogNew( + title, + window, + action, + "_Cancel", + GtkResponseCancel, + acceptLabel, + GtkResponseAccept, + 0) + + gtkFileChooserSetAction(fc, action) + + gtkFilters := []pointer{} + for _, filter := range filters { + f := gtkFileFilterNew() + gtkFileFilterSetName(f, filter.DisplayName) + gtkFileFilterAddPattern(f, filter.Pattern) + gtkFileChooserAddFilter(fc, f) + gtkFilters = append(gtkFilters, f) + } + gtkFileChooserSetSelectMultiple(fc, allowMultiple) + gtkFileChooserSetCreateFolders(fc, createFolders) + gtkFileChooserSetShowHidden(fc, showHidden) + + if currentFolder != "" { + gtkFileChooserSetCurrentFolder(fc, currentFolder) + } + + // Set the current name for save dialogs to pre-populate the filename + const GtkFileChooserActionSave = 1 + if currentName != "" && action == GtkFileChooserActionSave { + gtkFileChooserSetCurrentName(fc, currentName) + } + + buildStringAndFree := func(s pointer) string { + bytes := []byte{} + p := unsafe.Pointer(s) + for { + val := *(*byte)(p) + if val == 0 { // this is the null terminator + break + } + bytes = append(bytes, val) + p = unsafe.Add(p, 1) + } + gFree(s) // so we don't have to iterate a second time + return string(bytes) + } + + response := gtkDialogRun(fc) + selections := []string{} + if response == GtkResponseAccept { + filenames := gtkFileChooserGetFilenames(fc) + iter := filenames + count := 0 + for { + selections = append(selections, buildStringAndFree(iter.data)) + iter = iter.next + if iter == nil || count == 1024 { + break + } + count++ + } + } + defer gtkWidgetDestroy(fc) + return selections, nil +} + +// dialog related +func runOpenFileDialog(dialog *OpenFileDialogStruct) ([]string, error) { + const GtkFileChooserActionOpen = 0 + const GtkFileChooserActionSelectFolder = 2 + + var action int + + if dialog.canChooseDirectories { + action = GtkFileChooserActionSelectFolder + } else { + action = GtkFileChooserActionOpen + } + + window := pointer(0) + if dialog.window != nil { + nativeWindow := dialog.window.NativeWindow() + if nativeWindow != nil { + window = pointer(uintptr(nativeWindow)) + } + } + + buttonText := dialog.buttonText + if buttonText == "" { + buttonText = "_Open" + } + + return runChooserDialog( + window, + dialog.allowsMultipleSelection, + dialog.canCreateDirectories, + dialog.showHiddenFiles, + dialog.directory, + dialog.title, + GtkFileChooserActionOpen, + buttonText, + dialog.filters, + "") +} + +func runQuestionDialog(parent pointer, options *MessageDialog) int { + dType, ok := map[DialogType]int{ + InfoDialogType: GtkMessageInfo, + WarningDialogType: GtkMessageWarning, + QuestionDialogType: GtkMessageQuestion, + }[options.DialogType] + if !ok { + // FIXME: Add logging here! + dType = GtkMessageInfo + } + buttonMask := GtkButtonsOk + if len(options.Buttons) > 0 { + buttonMask = GtkButtonsNone + } + + dialog := gtkMessageDialogNew( + pointer(parent), + GtkDialogModal|GtkDialogDestroyWithParent, + dType, + buttonMask, + options.Message) + + if options.Title != "" { + gtkWindowSetTitle(dialog, options.Title) + } + + GdkColorspaceRGB := 0 + + if img, err := pngToImage(options.Icon); err == nil && len(img.Pix) > 0 { + // Use gBytesNew instead of gBytesNewStatic because Go memory can be + // moved or freed by the GC. gBytesNew copies the data to C-owned memory. + gbytes := gBytesNew(uintptr(unsafe.Pointer(&img.Pix[0])), len(img.Pix)) + + defer gBytesUnref(gbytes) + pixBuf := gdkPixbufNewFromBytes( + gbytes, + GdkColorspaceRGB, + 1, // has_alpha + 8, + img.Bounds().Dx(), + img.Bounds().Dy(), + img.Stride, + ) + image := gtkImageNewFromPixbuf(pixBuf) + widgetSetVisible(image, false) + contentArea := gtkDialogGetContentArea(dialog) + gtkContainerAdd(contentArea, image) + } + + for i, button := range options.Buttons { + gtkDialogAddButton( + dialog, + button.Label, + i, + ) + if button.IsDefault { + gtkDialogSetDefaultResponse(dialog, i) + } + } + defer gtkWidgetDestroy(dialog) + return gtkDialogRun(dialog) +} + +func runSaveFileDialog(dialog *SaveFileDialogStruct) (string, error) { + const GtkFileChooserActionSave = 1 + + window := pointer(0) + buttonText := dialog.buttonText + if buttonText == "" { + buttonText = "_Save" + } + results, err := runChooserDialog( + window, + false, // multiple selection + dialog.canCreateDirectories, + dialog.showHiddenFiles, + dialog.directory, + dialog.title, + GtkFileChooserActionSave, + buttonText, + dialog.filters, + dialog.filename) + + if err != nil || len(results) == 0 { + return "", err + } + + return results[0], nil +} + +func isOnMainThread() bool { + return mainThreadId == gThreadSelf() +} + +// linuxWebviewWindow show/hide methods for purego implementation +func (w *linuxWebviewWindow) windowShow() { + if w.window == 0 { + return + } + windowShow(w.window) +} + +func (w *linuxWebviewWindow) windowHide() { + if w.window == 0 { + return + } + windowHide(w.window) +} diff --git a/v3/pkg/application/logger_dev.go b/v3/pkg/application/logger_dev.go new file mode 100644 index 000000000..d69ec631c --- /dev/null +++ b/v3/pkg/application/logger_dev.go @@ -0,0 +1,20 @@ +//go:build !windows && !production && !ios + +package application + +import ( + "log/slog" + "os" + "time" + + "github.com/lmittmann/tint" + "github.com/mattn/go-isatty" +) + +func DefaultLogger(level slog.Leveler) *slog.Logger { + return slog.New(tint.NewHandler(os.Stderr, &tint.Options{ + TimeFormat: time.Kitchen, + NoColor: !isatty.IsTerminal(os.Stderr.Fd()), + Level: level, + })) +} diff --git a/v3/pkg/application/logger_dev_windows.go b/v3/pkg/application/logger_dev_windows.go new file mode 100644 index 000000000..20d20c376 --- /dev/null +++ b/v3/pkg/application/logger_dev_windows.go @@ -0,0 +1,21 @@ +//go:build windows && !production + +package application + +import ( + "log/slog" + "os" + "time" + + "github.com/lmittmann/tint" + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" +) + +func DefaultLogger(level slog.Leveler) *slog.Logger { + return slog.New(tint.NewHandler(colorable.NewColorable(os.Stderr), &tint.Options{ + TimeFormat: time.StampMilli, + NoColor: !isatty.IsTerminal(os.Stderr.Fd()), + Level: level, + })) +} diff --git a/v3/pkg/application/logger_ios.go b/v3/pkg/application/logger_ios.go new file mode 100644 index 000000000..584e96609 --- /dev/null +++ b/v3/pkg/application/logger_ios.go @@ -0,0 +1,128 @@ +//go:build ios + +package application + +/* +#cgo CFLAGS: -x objective-c -fobjc-arc +#cgo LDFLAGS: -framework Foundation -framework UIKit -framework WebKit +#include "webview_window_ios.h" +*/ +import "C" + +import ( + "bytes" + "context" + "log/slog" + "strings" + "time" + "unsafe" + + "encoding/json" +) + +// iosConsoleHandler implements slog.Handler and forwards records to the WKWebView console. +type iosConsoleHandler struct { + level slog.Leveler + attrs []slog.Attr + group string +} + +func (h *iosConsoleHandler) Enabled(_ context.Context, lvl slog.Level) bool { + if h.level == nil { + return true + } + return lvl >= h.level.Level() +} + +func (h *iosConsoleHandler) Handle(_ context.Context, r slog.Record) error { + // Build a compact log line + var b bytes.Buffer + b.WriteString(r.Time.Format(time.Kitchen)) + b.WriteString(" ") + if h.group != "" { + b.WriteString("[") + b.WriteString(h.group) + b.WriteString("] ") + } + b.WriteString(r.Message) + + writeAttr := func(a slog.Attr) { + // Resolve attr values + a.Value = a.Value.Resolve() + b.WriteString(" ") + b.WriteString(a.Key) + b.WriteString("=") + // Use JSON for complex values + switch a.Value.Kind() { + case slog.KindString: + b.WriteString(strconvQuote(a.Value.String())) + default: + js, _ := json.Marshal(a.Value.Any()) + b.Write(js) + } + } + + for _, a := range h.attrs { + writeAttr(a) + } + r.Attrs(func(a slog.Attr) bool { writeAttr(a); return true }) + + lvl := levelToConsole(r.Level) + + msg := b.String() + cmsg := C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + clvl := C.CString(lvl) + defer C.free(unsafe.Pointer(clvl)) + C.ios_console_log(clvl, cmsg) + return nil +} + +func (h *iosConsoleHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + clone := *h + clone.attrs = append(append([]slog.Attr{}, h.attrs...), attrs...) + return &clone +} + +func (h *iosConsoleHandler) WithGroup(name string) slog.Handler { + clone := *h + if clone.group == "" { + clone.group = name + } else if name != "" { + clone.group = clone.group + "." + name + } + return &clone +} + +func levelToConsole(l slog.Level) string { + switch { + case l <= slog.LevelDebug: + return "debug" + case l <= slog.LevelInfo: + return "info" + case l <= slog.LevelWarn: + return "warn" + default: + return "error" + } +} + +// strconvQuote quotes a string for compact key=value output (no surrounding quotes if no spaces). +func strconvQuote(s string) string { + if strings.IndexFunc(s, func(r rune) bool { return r == ' ' || r == '\n' || r == '\t' }) >= 0 { + bs, _ := json.Marshal(s) + return string(bs) + } + return s +} + +// DefaultLogger for iOS forwards all logs to the browser console. +func DefaultLogger(level slog.Leveler) *slog.Logger { + // Ensure there's always a leveler + if level == nil { + var lv slog.LevelVar + lv.Set(slog.LevelInfo) + level = &lv + } + return slog.New(&iosConsoleHandler{level: level}) +} diff --git a/v3/pkg/application/logger_prod.go b/v3/pkg/application/logger_prod.go new file mode 100644 index 000000000..eed0fb154 --- /dev/null +++ b/v3/pkg/application/logger_prod.go @@ -0,0 +1,12 @@ +//go:build production && !ios + +package application + +import ( + "io" + "log/slog" +) + +func DefaultLogger(level slog.Leveler) *slog.Logger { + return slog.New(slog.NewTextHandler(io.Discard, nil)) +} diff --git a/v3/pkg/application/mainthread.go b/v3/pkg/application/mainthread.go new file mode 100644 index 000000000..6eb40ba9d --- /dev/null +++ b/v3/pkg/application/mainthread.go @@ -0,0 +1,87 @@ +package application + +import ( + "sync" +) + +var mainThreadFunctionStore = make(map[uint]func()) +var mainThreadFunctionStoreLock sync.RWMutex + +func generateFunctionStoreID() uint { + startID := 0 + for { + if _, ok := mainThreadFunctionStore[uint(startID)]; !ok { + return uint(startID) + } + startID++ + if startID == 0 { + Fatal("Too many functions have been dispatched to the main thread") + } + } +} + +func InvokeSync(fn func()) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer handlePanic() + fn() + wg.Done() + }) + wg.Wait() +} + +func InvokeSyncWithResult[T any](fn func() T) (res T) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer handlePanic() + res = fn() + wg.Done() + }) + wg.Wait() + return res +} + +func InvokeSyncWithError(fn func() error) (err error) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer handlePanic() + err = fn() + wg.Done() + }) + wg.Wait() + return +} + +func InvokeSyncWithResultAndError[T any](fn func() (T, error)) (res T, err error) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer handlePanic() + res, err = fn() + wg.Done() + }) + wg.Wait() + return res, err +} + +func InvokeSyncWithResultAndOther[T any, U any](fn func() (T, U)) (res T, other U) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer handlePanic() + res, other = fn() + wg.Done() + }) + wg.Wait() + return res, other +} + +func InvokeAsync(fn func()) { + globalApplication.dispatchOnMainThread(func() { + defer handlePanic() + fn() + }) +} diff --git a/v3/pkg/application/mainthread_android.go b/v3/pkg/application/mainthread_android.go new file mode 100644 index 000000000..396bffba7 --- /dev/null +++ b/v3/pkg/application/mainthread_android.go @@ -0,0 +1,25 @@ +//go:build android + +package application + +// isOnMainThread returns whether the current goroutine is on the main thread +func (a *androidApp) isOnMainThread() bool { + // On Android, Go runs in its own thread separate from the UI thread + // UI operations need to be dispatched via JNI to the main thread + return false +} + +// dispatchOnMainThread executes a function on the Android main/UI thread +func (a *androidApp) dispatchOnMainThread(id uint) { + // TODO: Implement via JNI callback to Activity.runOnUiThread() + // For now, execute the callback directly + mainThreadFunctionStoreLock.RLock() + fn := mainThreadFunctionStore[id] + if fn == nil { + mainThreadFunctionStoreLock.RUnlock() + return + } + delete(mainThreadFunctionStore, id) + mainThreadFunctionStoreLock.RUnlock() + fn() +} diff --git a/v3/pkg/application/mainthread_darwin.go b/v3/pkg/application/mainthread_darwin.go new file mode 100644 index 000000000..5e7e32c67 --- /dev/null +++ b/v3/pkg/application/mainthread_darwin.go @@ -0,0 +1,45 @@ +//go:build darwin && !ios + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa + +#include "Cocoa/Cocoa.h" + +extern void dispatchOnMainThreadCallback(unsigned int); + +static void dispatchOnMainThread(unsigned int id) { + dispatch_async(dispatch_get_main_queue(), ^{ + dispatchOnMainThreadCallback(id); + }); +} + +static bool onMainThread() { + return [NSThread isMainThread]; +} + +*/ +import "C" + +func (m *macosApp) isOnMainThread() bool { + return bool(C.onMainThread()) +} + +func (m *macosApp) dispatchOnMainThread(id uint) { + C.dispatchOnMainThread(C.uint(id)) +} + +//export dispatchOnMainThreadCallback +func dispatchOnMainThreadCallback(callbackID C.uint) { + mainThreadFunctionStoreLock.RLock() + id := uint(callbackID) + fn := mainThreadFunctionStore[id] + if fn == nil { + Fatal("dispatchCallback called with invalid id: %v", id) + } + delete(mainThreadFunctionStore, id) + mainThreadFunctionStoreLock.RUnlock() + fn() +} diff --git a/v3/pkg/application/mainthread_ios.go b/v3/pkg/application/mainthread_ios.go new file mode 100644 index 000000000..dd13808de --- /dev/null +++ b/v3/pkg/application/mainthread_ios.go @@ -0,0 +1,44 @@ +//go:build ios + +package application + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework UIKit +#import +#import + +extern void dispatchOnMainThreadCallback(unsigned int); + +static void dispatchOnMainThread(unsigned int id) { + dispatch_async(dispatch_get_main_queue(), ^{ + dispatchOnMainThreadCallback(id); + }); +} + +static bool onMainThread() { + return [NSThread isMainThread]; +} +*/ +import "C" + +func (a *iosApp) isOnMainThread() bool { + return bool(C.onMainThread()) +} + +func (a *iosApp) dispatchOnMainThread(id uint) { + C.dispatchOnMainThread(C.uint(id)) +} + +//export dispatchOnMainThreadCallback +func dispatchOnMainThreadCallback(callbackID C.uint) { + mainThreadFunctionStoreLock.RLock() + id := uint(callbackID) + fn := mainThreadFunctionStore[id] + if fn == nil { + Fatal("dispatchCallback called with invalid id: %v", id) + } + delete(mainThreadFunctionStore, id) + mainThreadFunctionStoreLock.RUnlock() + fn() +} diff --git a/v3/pkg/application/mainthread_linux.go b/v3/pkg/application/mainthread_linux.go new file mode 100644 index 000000000..5154109d3 --- /dev/null +++ b/v3/pkg/application/mainthread_linux.go @@ -0,0 +1,18 @@ +//go:build linux && !android && !server + +package application + +func (a *linuxApp) dispatchOnMainThread(id uint) { + dispatchOnMainThread(id) +} + +func executeOnMainThread(callbackID uint) { + mainThreadFunctionStoreLock.RLock() + fn := mainThreadFunctionStore[callbackID] + if fn == nil { + Fatal("dispatchCallback called with invalid id: %v", callbackID) + } + delete(mainThreadFunctionStore, callbackID) + mainThreadFunctionStoreLock.RUnlock() + fn() +} diff --git a/v3/pkg/application/mainthread_windows.go b/v3/pkg/application/mainthread_windows.go new file mode 100644 index 000000000..ddee96339 --- /dev/null +++ b/v3/pkg/application/mainthread_windows.go @@ -0,0 +1,127 @@ +//go:build windows + +package application + +import ( + "runtime" + "sort" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/w32" +) + +var ( + wmInvokeCallback uint32 +) + +func init() { + wmInvokeCallback = w32.RegisterWindowMessage(w32.MustStringToUTF16Ptr("WailsV0.InvokeCallback")) +} + +// initMainLoop must be called with the same OSThread that is used to call runMainLoop() later. +func (m *windowsApp) initMainLoop() { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if m.mainThreadWindowHWND != 0 { + panic("initMainLoop was already called") + } + + // We need a hidden window so we can PostMessage to it, if we don't use PostMessage for dispatching to a HWND + // messages might get lost if a modal inner loop is being run. + // We had this once in V2: https://github.com/wailsapp/wails/issues/969 + // See: https://devblogs.microsoft.com/oldnewthing/20050426-18/?p=35783 + // See also: https://learn.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues#creating-a-message-loop + // > Because the system directs messages to individual windows in an application, a thread must create at least one window before starting its message loop. + m.mainThreadWindowHWND = w32.CreateWindowEx( + 0, + w32.MustStringToUTF16Ptr(m.parent.options.Windows.WndClass), + w32.MustStringToUTF16Ptr("__wails_hidden_mainthread"), + w32.WS_DISABLED, + w32.CW_USEDEFAULT, + w32.CW_USEDEFAULT, + 0, + 0, + 0, + 0, + w32.GetModuleHandle(""), + nil) + + m.mainThreadID, _ = w32.GetWindowThreadProcessId(m.mainThreadWindowHWND) +} + +func (m *windowsApp) runMainLoop() int { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if m.invokeRequired() { + panic("invokeRequired for runMainLoop, the mainloop must be running on the same OSThread as the mainThreadWindow has been created on") + } + + msg := (*w32.MSG)(unsafe.Pointer(w32.GlobalAlloc(0, uint32(unsafe.Sizeof(w32.MSG{}))))) + defer w32.GlobalFree(w32.HGLOBAL(unsafe.Pointer(msg))) + + for w32.GetMessage(msg, 0, 0, 0) != 0 { + w32.TranslateMessage(msg) + w32.DispatchMessage(msg) + } + + return int(msg.WParam) +} + +func (m *windowsApp) dispatchOnMainThread(id uint) { + mainThreadHWND := m.mainThreadWindowHWND + if mainThreadHWND == 0 { + panic("initMainLoop was not called") + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if m.invokeRequired() { + w32.PostMessage(mainThreadHWND, wmInvokeCallback, uintptr(id), 0) + } else { + mainThreadFunctionStoreLock.Lock() + fn := mainThreadFunctionStore[id] + delete(mainThreadFunctionStore, id) + mainThreadFunctionStoreLock.Unlock() + + if fn == nil { + Fatal("dispatchOnMainThread called with invalid id: %v", id) + } + fn() + } +} + +func (m *windowsApp) invokeRequired() bool { + mainThreadID := m.mainThreadID + if mainThreadID == 0 { + panic("initMainLoop was not called") + } + + return mainThreadID != w32.GetCurrentThreadId() +} + +func (m *windowsApp) invokeCallback(wParam, lParam uintptr) { + // TODO: Should we invoke just one or all queued? In v2 we always invoked all pendings... + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if m.invokeRequired() { + panic("invokeCallback must always be called on the MainOSThread") + } + + mainThreadFunctionStoreLock.Lock() + fnIDs := make([]uint, 0, len(mainThreadFunctionStore)) + for id := range mainThreadFunctionStore { + fnIDs = append(fnIDs, id) + } + sort.Slice(fnIDs, func(i, j int) bool { return fnIDs[i] < fnIDs[j] }) + + fns := make([]func(), len(fnIDs)) + for i, id := range fnIDs { + fns[i] = mainThreadFunctionStore[id] + delete(mainThreadFunctionStore, id) + } + mainThreadFunctionStoreLock.Unlock() + + for _, fn := range fns { + fn() + } +} diff --git a/v3/pkg/application/menu.go b/v3/pkg/application/menu.go new file mode 100644 index 000000000..96a971da1 --- /dev/null +++ b/v3/pkg/application/menu.go @@ -0,0 +1,233 @@ +package application + +type menuImpl interface { + update() +} + +type ContextMenu struct { + *Menu + name string +} + +func NewContextMenu(name string) *ContextMenu { + result := &ContextMenu{ + Menu: NewMenu(), + name: name, + } + result.Update() + return result +} + +func (m *ContextMenu) Update() { + m.Menu.Update() + globalApplication.ContextMenu.Add(m.name, m) +} + +func (m *ContextMenu) Destroy() { + globalApplication.ContextMenu.Remove(m.name) +} + +type Menu struct { + items []*MenuItem + label string + + impl menuImpl +} + +func NewMenu() *Menu { + return &Menu{} +} + +func (m *Menu) Add(label string) *MenuItem { + result := NewMenuItem(label) + m.items = append(m.items, result) + return result +} + +func (m *Menu) AddSeparator() { + result := NewMenuItemSeparator() + m.items = append(m.items, result) +} + +func (m *Menu) AddCheckbox(label string, enabled bool) *MenuItem { + result := NewMenuItemCheckbox(label, enabled) + m.items = append(m.items, result) + return result +} + +func (m *Menu) AddRadio(label string, enabled bool) *MenuItem { + result := NewMenuItemRadio(label, enabled) + m.items = append(m.items, result) + return result +} + +func (m *Menu) Update() { + m.processRadioGroups() + if m.impl == nil { + m.impl = newMenuImpl(m) + } + m.impl.update() +} + +// Clear all menu items +func (m *Menu) Clear() { + for _, item := range m.items { + removeMenuItemByID(item.id) + } + m.items = nil +} + +func (m *Menu) Destroy() { + for _, item := range m.items { + item.Destroy() + } + m.items = nil +} + +func (m *Menu) AddSubmenu(s string) *Menu { + result := NewSubMenuItem(s) + m.items = append(m.items, result) + return result.submenu +} + +func (m *Menu) AddRole(role Role) *Menu { + result := NewRole(role) + if result != nil { + m.items = append(m.items, result) + } + return m +} + +func (m *Menu) processRadioGroups() { + var radioGroup []*MenuItem + + closeOutRadioGroups := func() { + if len(radioGroup) > 0 { + for _, item := range radioGroup { + item.radioGroupMembers = radioGroup + } + radioGroup = []*MenuItem{} + } + } + + for _, item := range m.items { + if item.itemType != radio { + closeOutRadioGroups() + } + if item.itemType == submenu { + item.submenu.processRadioGroups() + continue + } + if item.itemType == radio { + radioGroup = append(radioGroup, item) + } + } + closeOutRadioGroups() +} + +func (m *Menu) SetLabel(label string) { + m.label = label +} + +func (m *Menu) setContextData(data *ContextMenuData) { + for _, item := range m.items { + item.setContextData(data) + } +} + +// FindByLabel recursively searches for a menu item with the given label +// and returns the first match, or nil if not found. +func (m *Menu) FindByLabel(label string) *MenuItem { + for _, item := range m.items { + if item.label == label { + return item + } + if item.submenu != nil { + found := item.submenu.FindByLabel(label) + if found != nil { + return found + } + } + } + return nil +} + +// FindByRole recursively searches for a menu item with the given role +// and returns the first match, or nil if not found. +func (m *Menu) FindByRole(role Role) *MenuItem { + for _, item := range m.items { + if item.role == role { + return item + } + if item.submenu != nil { + found := item.submenu.FindByRole(role) + if found != nil { + return found + } + } + } + return nil +} + +func (m *Menu) RemoveMenuItem(target *MenuItem) { + for i, item := range m.items { + if item == target { + // Remove the item from the slice + m.items = append(m.items[:i], m.items[i+1:]...) + break + } + if item.submenu != nil { + item.submenu.RemoveMenuItem(target) + } + } +} + +// ItemAt returns the menu item at the given index, or nil if the index is out of bounds. +func (m *Menu) ItemAt(index int) *MenuItem { + if index < 0 || index >= len(m.items) { + return nil + } + return m.items[index] +} + +// Clone recursively clones the menu and all its submenus. +func (m *Menu) Clone() *Menu { + result := &Menu{ + label: m.label, + } + for _, item := range m.items { + result.items = append(result.items, item.Clone()) + } + return result +} + +// Append menu to an existing menu +func (m *Menu) Append(in *Menu) { + if in == nil { + return + } + m.items = append(m.items, in.items...) +} + +// Prepend menu before an existing menu +func (m *Menu) Prepend(in *Menu) { + m.items = append(in.items, m.items...) +} + +func (a *App) NewMenu() *Menu { + return a.Menu.New() +} + +func NewMenuFromItems(item *MenuItem, items ...*MenuItem) *Menu { + result := &Menu{ + items: []*MenuItem{item}, + } + result.items = append(result.items, items...) + return result +} + +func NewSubmenu(s string, items *Menu) *MenuItem { + result := NewSubMenuItem(s) + result.submenu = items + return result +} diff --git a/v3/pkg/application/menu_android.go b/v3/pkg/application/menu_android.go new file mode 100644 index 000000000..8bb4a48d1 --- /dev/null +++ b/v3/pkg/application/menu_android.go @@ -0,0 +1,26 @@ +//go:build android + +package application + +// Android menu stubs - Android doesn't have traditional application menus + +func (m *Menu) handleStyleChange() {} + +type androidMenu struct { + menu *Menu +} + +func newMenuImpl(menu *Menu) *androidMenu { + return &androidMenu{ + menu: menu, + } +} + +func (m *androidMenu) update() { + // Android doesn't have traditional menus +} + +func defaultApplicationMenu() *Menu { + // No application menu on Android + return nil +} diff --git a/v3/pkg/application/menu_darwin.go b/v3/pkg/application/menu_darwin.go new file mode 100644 index 000000000..1a6e4a8d6 --- /dev/null +++ b/v3/pkg/application/menu_darwin.go @@ -0,0 +1,135 @@ +//go:build darwin && !ios && !server + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.10 -x objective-c +#cgo LDFLAGS: -framework Cocoa + +#include "menuitem_darwin.h" + +extern void setMenuItemChecked(void*, unsigned int, bool); +extern void setMenuItemBitmap(void*, unsigned char*, int); + +// Clear and release all menu items in the menu +void clearMenu(void* nsMenu) { + NSMenu *menu = (NSMenu *)nsMenu; + [menu removeAllItems]; +} + + +// Create a new NSMenu +void* createNSMenu(char* label) { + NSMenu *menu = [[NSMenu alloc] init]; + if( label != NULL && strlen(label) > 0 ) { + menu.title = [NSString stringWithUTF8String:label]; + free(label); + } + [menu setAutoenablesItems:NO]; + return (void*)menu; +} + +void addMenuItem(void* nsMenu, void* nsMenuItem) { + NSMenu *menu = (NSMenu *)nsMenu; + [menu addItem:nsMenuItem]; +} + +// add seperator to menu +void addMenuSeparator(void* nsMenu) { + NSMenu *menu = (NSMenu *)nsMenu; + [menu addItem:[NSMenuItem separatorItem]]; +} + +// Set the submenu of a menu item +void setMenuItemSubmenu(void* nsMenuItem, void* nsMenu) { + NSMenuItem *menuItem = (NSMenuItem *)nsMenuItem; + NSMenu *menu = (NSMenu *)nsMenu; + [menuItem setSubmenu:menu]; +} + +// Add services menu +static void addServicesMenu(void* menu) { + NSMenu *nsMenu = (__bridge NSMenu *)menu; + [NSApp setServicesMenu:nsMenu]; +} + +// Add windows menu +void addWindowsMenu(void* menu) { + NSMenu *nsMenu = (__bridge NSMenu *)menu; + [NSApp setWindowsMenu:nsMenu]; +} + + +*/ +import "C" +import "unsafe" + +type macosMenu struct { + menu *Menu + + nsMenu unsafe.Pointer +} + +func newMenuImpl(menu *Menu) *macosMenu { + result := &macosMenu{ + menu: menu, + } + return result +} + +func (m *macosMenu) update() { + InvokeSync(func() { + if m.nsMenu == nil { + m.nsMenu = C.createNSMenu(C.CString(m.menu.label)) + } else { + C.clearMenu(m.nsMenu) + } + m.processMenu(m.nsMenu, m.menu) + }) +} + +func (m *macosMenu) processMenu(parent unsafe.Pointer, menu *Menu) { + for _, item := range menu.items { + switch item.itemType { + case submenu: + submenu := item.submenu + nsSubmenu := C.createNSMenu(C.CString(item.label)) + m.processMenu(nsSubmenu, submenu) + menuItem := newMenuItemImpl(item) + item.impl = menuItem + C.addMenuItem(parent, menuItem.nsMenuItem) + C.setMenuItemSubmenu(menuItem.nsMenuItem, nsSubmenu) + if item.role == ServicesMenu { + C.addServicesMenu(nsSubmenu) + } + if item.role == WindowMenu { + C.addWindowsMenu(nsSubmenu) + } + case text, checkbox, radio: + menuItem := newMenuItemImpl(item) + item.impl = menuItem + if item.hidden { + menuItem.setHidden(true) + } + C.addMenuItem(parent, menuItem.nsMenuItem) + case separator: + C.addMenuSeparator(parent) + } + if item.bitmap != nil { + macMenuItem := item.impl.(*macosMenuItem) + C.setMenuItemBitmap(macMenuItem.nsMenuItem, (*C.uchar)(&item.bitmap[0]), C.int(len(item.bitmap))) + } + + } +} + +func DefaultApplicationMenu() *Menu { + menu := NewMenu() + menu.AddRole(AppMenu) + menu.AddRole(FileMenu) + menu.AddRole(EditMenu) + menu.AddRole(ViewMenu) + menu.AddRole(WindowMenu) + menu.AddRole(HelpMenu) + return menu +} diff --git a/v3/pkg/application/menu_internal_test.go b/v3/pkg/application/menu_internal_test.go new file mode 100644 index 000000000..488e9a520 --- /dev/null +++ b/v3/pkg/application/menu_internal_test.go @@ -0,0 +1,403 @@ +package application + +import ( + "testing" +) + +func TestNewMenu(t *testing.T) { + menu := NewMenu() + if menu == nil { + t.Fatal("NewMenu returned nil") + } + if menu.items != nil { + t.Error("items should be nil initially") + } +} + +func TestMenu_Add(t *testing.T) { + menu := NewMenu() + + item := menu.Add("Test Item") + if item == nil { + t.Fatal("Add returned nil") + } + if item.label != "Test Item" { + t.Errorf("label = %q, want %q", item.label, "Test Item") + } + if len(menu.items) != 1 { + t.Errorf("items count = %d, want 1", len(menu.items)) + } + + // Clean up + menu.Destroy() +} + +func TestMenu_AddSeparator(t *testing.T) { + menu := NewMenu() + + menu.AddSeparator() + if len(menu.items) != 1 { + t.Errorf("items count = %d, want 1", len(menu.items)) + } + if menu.items[0].itemType != separator { + t.Error("item should be a separator") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_AddCheckbox(t *testing.T) { + menu := NewMenu() + + item := menu.AddCheckbox("Check Me", true) + if item == nil { + t.Fatal("AddCheckbox returned nil") + } + if item.label != "Check Me" { + t.Errorf("label = %q, want %q", item.label, "Check Me") + } + if item.itemType != checkbox { + t.Error("item should be a checkbox") + } + if !item.checked { + t.Error("checkbox should be checked") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_AddRadio(t *testing.T) { + menu := NewMenu() + + item := menu.AddRadio("Radio Option", false) + if item == nil { + t.Fatal("AddRadio returned nil") + } + if item.label != "Radio Option" { + t.Errorf("label = %q, want %q", item.label, "Radio Option") + } + if item.itemType != radio { + t.Error("item should be a radio") + } + if item.checked { + t.Error("radio should not be checked") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_AddSubmenu(t *testing.T) { + menu := NewMenu() + + subMenu := menu.AddSubmenu("Submenu") + if subMenu == nil { + t.Fatal("AddSubmenu returned nil") + } + if len(menu.items) != 1 { + t.Errorf("items count = %d, want 1", len(menu.items)) + } + if menu.items[0].itemType != submenu { + t.Error("item should be a submenu") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_SetLabel(t *testing.T) { + menu := NewMenu() + + menu.SetLabel("My Menu") + if menu.label != "My Menu" { + t.Errorf("label = %q, want %q", menu.label, "My Menu") + } +} + +func TestMenu_ItemAt(t *testing.T) { + menu := NewMenu() + menu.Add("Item 0") + menu.Add("Item 1") + menu.Add("Item 2") + + item := menu.ItemAt(1) + if item == nil { + t.Fatal("ItemAt returned nil") + } + if item.label != "Item 1" { + t.Errorf("label = %q, want %q", item.label, "Item 1") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_ItemAt_OutOfBounds(t *testing.T) { + menu := NewMenu() + menu.Add("Item") + + if menu.ItemAt(-1) != nil { + t.Error("ItemAt(-1) should return nil") + } + if menu.ItemAt(1) != nil { + t.Error("ItemAt(1) should return nil for single item menu") + } + if menu.ItemAt(100) != nil { + t.Error("ItemAt(100) should return nil") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_FindByLabel(t *testing.T) { + menu := NewMenu() + menu.Add("First") + menu.Add("Second") + menu.Add("Third") + + found := menu.FindByLabel("Second") + if found == nil { + t.Fatal("FindByLabel returned nil") + } + if found.label != "Second" { + t.Errorf("label = %q, want %q", found.label, "Second") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_FindByLabel_NotFound(t *testing.T) { + menu := NewMenu() + menu.Add("First") + + found := menu.FindByLabel("NonExistent") + if found != nil { + t.Error("FindByLabel should return nil for non-existent label") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_FindByLabel_InSubmenu(t *testing.T) { + menu := NewMenu() + submenu := menu.AddSubmenu("Submenu") + submenu.Add("Nested Item") + + found := menu.FindByLabel("Nested Item") + if found == nil { + t.Fatal("FindByLabel should find item in submenu") + } + if found.label != "Nested Item" { + t.Errorf("label = %q, want %q", found.label, "Nested Item") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_RemoveMenuItem(t *testing.T) { + menu := NewMenu() + item1 := menu.Add("First") + item2 := menu.Add("Second") + menu.Add("Third") + + menu.RemoveMenuItem(item2) + if len(menu.items) != 2 { + t.Errorf("items count = %d, want 2", len(menu.items)) + } + if menu.FindByLabel("Second") != nil { + t.Error("Second item should be removed") + } + + // Clean up + removeMenuItemByID(item1.id) +} + +func TestMenu_Clear(t *testing.T) { + menu := NewMenu() + menu.Add("First") + menu.Add("Second") + menu.Add("Third") + + menu.Clear() + if menu.items != nil { + t.Error("items should be nil after Clear") + } +} + +func TestMenu_Append(t *testing.T) { + menu1 := NewMenu() + menu1.Add("Item 1") + + menu2 := NewMenu() + menu2.Add("Item 2") + menu2.Add("Item 3") + + menu1.Append(menu2) + if len(menu1.items) != 3 { + t.Errorf("items count = %d, want 3", len(menu1.items)) + } + + // Clean up + menu1.Destroy() +} + +func TestMenu_Append_Nil(t *testing.T) { + menu := NewMenu() + menu.Add("Item") + + menu.Append(nil) + if len(menu.items) != 1 { + t.Error("Append(nil) should not change menu") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_Prepend(t *testing.T) { + menu1 := NewMenu() + menu1.Add("Item 3") + + menu2 := NewMenu() + menu2.Add("Item 1") + menu2.Add("Item 2") + + menu1.Prepend(menu2) + if len(menu1.items) != 3 { + t.Errorf("items count = %d, want 3", len(menu1.items)) + } + if menu1.items[0].label != "Item 1" { + t.Errorf("First item should be 'Item 1', got %q", menu1.items[0].label) + } + + // Clean up + menu1.Destroy() +} + +func TestMenu_Clone(t *testing.T) { + menu := NewMenu() + menu.SetLabel("Original Menu") + menu.Add("Item 1") + menu.Add("Item 2") + + clone := menu.Clone() + if clone == menu { + t.Error("Clone should return different pointer") + } + if clone.label != menu.label { + t.Error("Clone should have same label") + } + if len(clone.items) != len(menu.items) { + t.Error("Clone should have same number of items") + } + + // Clean up + menu.Destroy() + clone.Destroy() +} + +func TestNewMenuFromItems(t *testing.T) { + item1 := NewMenuItem("Item 1") + item2 := NewMenuItem("Item 2") + item3 := NewMenuItem("Item 3") + + menu := NewMenuFromItems(item1, item2, item3) + if menu == nil { + t.Fatal("NewMenuFromItems returned nil") + } + if len(menu.items) != 3 { + t.Errorf("items count = %d, want 3", len(menu.items)) + } + + // Clean up + menu.Destroy() +} + +func TestNewSubmenu(t *testing.T) { + items := NewMenu() + items.Add("Sub Item 1") + items.Add("Sub Item 2") + + item := NewSubmenu("My Submenu", items) + if item == nil { + t.Fatal("NewSubmenu returned nil") + } + if item.label != "My Submenu" { + t.Errorf("label = %q, want %q", item.label, "My Submenu") + } + if item.submenu != items { + t.Error("submenu should be the provided menu") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenu_ProcessRadioGroups(t *testing.T) { + menu := NewMenu() + + // Add some non-radio items + menu.Add("Regular Item") + + // Add a group of radio items + radio1 := menu.AddRadio("Radio 1", true) + radio2 := menu.AddRadio("Radio 2", false) + radio3 := menu.AddRadio("Radio 3", false) + + // Add separator to end the group + menu.AddSeparator() + + // Add another group + radio4 := menu.AddRadio("Radio A", true) + radio5 := menu.AddRadio("Radio B", false) + + // Process radio groups + menu.processRadioGroups() + + // First group should be linked + if len(radio1.radioGroupMembers) != 3 { + t.Errorf("First group should have 3 members, got %d", len(radio1.radioGroupMembers)) + } + if len(radio2.radioGroupMembers) != 3 { + t.Errorf("radio2 should have 3 members, got %d", len(radio2.radioGroupMembers)) + } + if len(radio3.radioGroupMembers) != 3 { + t.Errorf("radio3 should have 3 members, got %d", len(radio3.radioGroupMembers)) + } + + // Second group should be linked + if len(radio4.radioGroupMembers) != 2 { + t.Errorf("Second group should have 2 members, got %d", len(radio4.radioGroupMembers)) + } + if len(radio5.radioGroupMembers) != 2 { + t.Errorf("radio5 should have 2 members, got %d", len(radio5.radioGroupMembers)) + } + + // Clean up + menu.Destroy() +} + +func TestMenu_SetContextData(t *testing.T) { + menu := NewMenu() + menu.Add("Item 1") + menu.Add("Item 2") + + data := &ContextMenuData{Data: "test-data"} + menu.setContextData(data) + + // Verify data was set on items + for _, item := range menu.items { + if item.contextMenuData != data { + t.Error("Context data should be set on all items") + } + } + + // Clean up + menu.Destroy() +} diff --git a/v3/pkg/application/menu_ios.go b/v3/pkg/application/menu_ios.go new file mode 100644 index 000000000..f5f7dcbb0 --- /dev/null +++ b/v3/pkg/application/menu_ios.go @@ -0,0 +1,24 @@ +//go:build ios + +package application + +// iOS menu stubs - iOS doesn't have traditional menus + +// iOS doesn't have traditional menus like desktop platforms +// These are placeholder implementations + +func (m *Menu) handleStyleChange() {} + +type iosMenu struct { + menu *Menu +} + +func newMenuImpl(menu *Menu) *iosMenu { + return &iosMenu{ + menu: menu, + } +} + +func (m *iosMenu) update() { + // iOS doesn't have traditional menus +} \ No newline at end of file diff --git a/v3/pkg/application/menu_linux.go b/v3/pkg/application/menu_linux.go new file mode 100644 index 000000000..1d8d681af --- /dev/null +++ b/v3/pkg/application/menu_linux.go @@ -0,0 +1,121 @@ +//go:build linux && !android && !server + +package application + +type linuxMenu struct { + menu *Menu + native pointer +} + +func newMenuImpl(menu *Menu) *linuxMenu { + result := &linuxMenu{ + menu: menu, + native: menuBarNew(), + } + return result +} + +func (m *linuxMenu) run() { + m.update() +} + +func (m *linuxMenu) update() { + m.processMenu(m.menu) +} + +func (m *linuxMenu) processMenu(menu *Menu) { + if menu.impl == nil { + menu.impl = &linuxMenu{ + menu: menu, + native: menuNew(), + } + } else { + // Clear existing menu items before rebuilding (prevents appending on Update()) + menuClear(menu) + } + var currentRadioGroup GSListPointer + + for _, item := range menu.items { + // drop the group if we have run out of radio items + if item.itemType != radio { + currentRadioGroup = nilRadioGroup + } + + switch item.itemType { + case submenu: + menuItem := newMenuItemImpl(item) + item.impl = menuItem + m.processMenu(item.submenu) + m.addSubMenuToItem(item.submenu, item) + m.addMenuItem(menu, item) + case text, checkbox: + menuItem := newMenuItemImpl(item) + item.impl = menuItem + m.addMenuItem(menu, item) + case radio: + menuItem := newRadioItemImpl(item, currentRadioGroup) + item.impl = menuItem + m.addMenuItem(menu, item) + currentRadioGroup = menuGetRadioGroup(menuItem) + case separator: + m.addMenuSeparator(menu) + } + + } + + for _, item := range menu.items { + if item.callback != nil { + m.attachHandler(item) + } + } + +} + +func (m *linuxMenu) attachHandler(item *MenuItem) { + (item.impl).(*linuxMenuItem).handlerId = attachMenuHandler(item) +} + +func (m *linuxMenu) addSubMenuToItem(menu *Menu, item *MenuItem) { + if menu.impl == nil { + menu.impl = &linuxMenu{ + menu: menu, + native: menuNew(), + } + } + menuSetSubmenu(item, menu) +} + +func (m *linuxMenu) addMenuItem(parent *Menu, menu *MenuItem) { + menuAppend(parent, menu) +} + +func (m *linuxMenu) addMenuSeparator(menu *Menu) { + menuAddSeparator(menu) + +} + +func (m *linuxMenu) addServicesMenu(menu *Menu) { + // FIXME: Should this be required? +} + +func (l *linuxMenu) createMenu(name string, items []*MenuItem) *Menu { + impl := newMenuImpl(&Menu{label: name}) + menu := &Menu{ + label: name, + items: items, + impl: impl, + } + impl.menu = menu + return menu +} + +func DefaultApplicationMenu() *Menu { + menu := NewMenu() + menu.AddRole(AppMenu) + menu.AddRole(FileMenu) + menu.AddRole(EditMenu) + menu.AddRole(ViewMenu) + menu.AddRole(WindowMenu) + menu.AddRole(HelpMenu) + return menu +} diff --git a/v3/pkg/application/menu_manager.go b/v3/pkg/application/menu_manager.go new file mode 100644 index 000000000..4c3a36c83 --- /dev/null +++ b/v3/pkg/application/menu_manager.go @@ -0,0 +1,55 @@ +package application + +// MenuManager manages menu-related operations +type MenuManager struct { + app *App +} + +// newMenuManager creates a new MenuManager instance +func newMenuManager(app *App) *MenuManager { + return &MenuManager{ + app: app, + } +} + +// Set sets the application menu +func (mm *MenuManager) Set(menu *Menu) { + mm.SetApplicationMenu(menu) +} + +// SetApplicationMenu sets the application menu +func (mm *MenuManager) SetApplicationMenu(menu *Menu) { + mm.app.applicationMenu = menu + if mm.app.impl != nil { + mm.app.impl.setApplicationMenu(menu) + } +} + +// GetApplicationMenu returns the current application menu +func (mm *MenuManager) GetApplicationMenu() *Menu { + return mm.app.applicationMenu +} + +// New creates a new menu +func (mm *MenuManager) New() *Menu { + return &Menu{} +} + +// ShowAbout shows the about dialog +func (mm *MenuManager) ShowAbout() { + if mm.app.impl != nil { + mm.app.impl.showAboutDialog(mm.app.options.Name, mm.app.options.Description, mm.app.options.Icon) + } +} + +// handleMenuItemClicked handles menu item click events (internal use) +func (mm *MenuManager) handleMenuItemClicked(menuItemID uint) { + defer handlePanic() + + menuItem := getMenuItemByID(menuItemID) + if menuItem == nil { + mm.app.warning("MenuItem #%d not found", menuItemID) + return + } + menuItem.handleClick() +} diff --git a/v3/pkg/application/menu_test.go b/v3/pkg/application/menu_test.go new file mode 100644 index 000000000..88aeb5724 --- /dev/null +++ b/v3/pkg/application/menu_test.go @@ -0,0 +1,140 @@ +package application_test + +import ( + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func TestMenu_FindByLabel(t *testing.T) { + tests := []struct { + name string + menu *application.Menu + label string + shouldError bool + }{ + { + name: "Find top-level item", + menu: application.NewMenuFromItems( + application.NewMenuItem("Target"), + ), + label: "Target", + shouldError: false, + }, + { + name: "Find item in submenu", + menu: application.NewMenuFromItems( + application.NewMenuItem("Item 1"), + application.NewSubmenu("Submenu", application.NewMenuFromItems( + application.NewMenuItem("Subitem 1"), + application.NewMenuItem("Target"), + )), + ), + label: "Target", + shouldError: false, + }, + { + name: "Not find item", + menu: application.NewMenuFromItems( + application.NewMenuItem("Item 1"), + application.NewSubmenu("Submenu", application.NewMenuFromItems( + application.NewMenuItem("Subitem 1"), + application.NewMenuItem("Target"), + )), + ), + label: "Random", + shouldError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + found := test.menu.FindByLabel(test.label) + if test.shouldError && found != nil { + t.Errorf("Expected error, but found %v", found) + } + if !test.shouldError && found == nil { + t.Errorf("Expected item, but found none") + } + }) + } +} + +func TestMenu_ItemAt(t *testing.T) { + tests := []struct { + name string + menu *application.Menu + index int + shouldError bool + }{ + { + name: "Valid index", + menu: application.NewMenuFromItems( + application.NewMenuItem("Item 1"), + application.NewMenuItem("Item 2"), + application.NewMenuItem("Target"), + ), + index: 2, + shouldError: false, + }, + { + name: "Index out of bounds (negative)", + menu: application.NewMenuFromItems( + application.NewMenuItem("Item 1"), + application.NewMenuItem("Item 2"), + ), + index: -1, + shouldError: true, + }, + { + name: "Index out of bounds (too large)", + menu: application.NewMenuFromItems( + application.NewMenuItem("Item 1"), + application.NewMenuItem("Item 2"), + ), + index: 2, + shouldError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + item := test.menu.ItemAt(test.index) + if test.shouldError && item != nil { + t.Errorf("Expected error, but found %v", item) + } + if !test.shouldError && item == nil { + t.Errorf("Expected item, but found none") + } + }) + } +} + +func TestMenu_RemoveMenuItem(t *testing.T) { + itemToRemove := application.NewMenuItem("Target") + itemToKeep := application.NewMenuItem("Item 1") + + tests := []struct { + name string + menu *application.Menu + item *application.MenuItem + shouldFind bool + }{ + { + name: "Remove existing item", + menu: application.NewMenuFromItems(itemToKeep, itemToRemove), + item: itemToRemove, + shouldFind: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.menu.RemoveMenuItem(test.item) + found := test.menu.FindByLabel(test.item.Label()) + if !test.shouldFind && found != nil { + t.Errorf("Expected item to be removed, but found %v", found) + } + }) + } +} diff --git a/v3/pkg/application/menu_windows.go b/v3/pkg/application/menu_windows.go new file mode 100644 index 000000000..758fae985 --- /dev/null +++ b/v3/pkg/application/menu_windows.go @@ -0,0 +1,142 @@ +//go:build windows && !server + +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/w32" +) + +type windowsMenu struct { + menu *Menu + parentWindow *windowsWebviewWindow + + hWnd w32.HWND + hMenu w32.HMENU + currentMenuID int + menuMapping map[int]*MenuItem + checkboxItems []*Menu +} + +func newMenuImpl(menu *Menu) *windowsMenu { + result := &windowsMenu{ + menu: menu, + menuMapping: make(map[int]*MenuItem), + } + + return result +} + +func (w *windowsMenu) update() { + if w.hMenu != 0 { + w32.DestroyMenu(w.hMenu) + } + w.hMenu = w32.NewPopupMenu() + w.processMenu(w.hMenu, w.menu) +} + +func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) { + for _, item := range inputMenu.items { + w.currentMenuID++ + itemID := w.currentMenuID + w.menuMapping[itemID] = item + + menuItemImpl := newMenuItemImpl(item, parentMenu, itemID) + menuItemImpl.parent = inputMenu + item.impl = menuItemImpl + + if item.Hidden() { + if item.accelerator != nil && item.callback != nil { + if w.parentWindow != nil { + w.parentWindow.parent.removeMenuBinding(item.accelerator) + } else { + globalApplication.KeyBinding.Remove(item.accelerator.String()) + } + } + } + + flags := uint32(w32.MF_STRING) + if item.disabled { + flags = flags | w32.MF_GRAYED + } + if item.checked { + flags = flags | w32.MF_CHECKED + } + if item.IsSeparator() { + flags = flags | w32.MF_SEPARATOR + } + if item.itemType == radio { + flags = flags | w32.MFT_RADIOCHECK + } + + if item.submenu != nil { + flags = flags | w32.MF_POPUP + newSubmenu := w32.CreateMenu() + w.processMenu(newSubmenu, item.submenu) + itemID = int(newSubmenu) + } + + thisText := item.Label() + if item.accelerator != nil && item.callback != nil { + if w.parentWindow != nil { + w.parentWindow.parent.addMenuBinding(item.accelerator, item) + } else { + globalApplication.KeyBinding.Add(item.accelerator.String(), func(w Window) { + item.handleClick() + }) + } + thisText = thisText + "\t" + item.accelerator.String() + } + var menuText = w32.MustStringToUTF16Ptr(thisText) + + // If the item is hidden, don't append + if item.Hidden() { + continue + } + + w32.AppendMenu(parentMenu, flags, uintptr(itemID), menuText) + if item.bitmap != nil { + if err := w32.SetMenuIcons(parentMenu, itemID, item.bitmap, nil); err != nil { + globalApplication.fatal("error setting menu icons: %w", err) + } + } + } +} + +func (w *windowsMenu) ShowAtCursor() { + InvokeSync(func() { + x, y, ok := w32.GetCursorPos() + if !ok { + return + } + w.ShowAt(x, y) + }) +} + +func (w *windowsMenu) ShowAt(x int, y int) { + w.update() + w32.TrackPopupMenuEx(w.hMenu, + w32.TPM_LEFTALIGN, + int32(x), + int32(y), + w.hWnd, + nil) + w32.PostMessage(w.hWnd, w32.WM_NULL, 0, 0) +} + +func (w *windowsMenu) ProcessCommand(cmdMsgID int) { + item := w.menuMapping[cmdMsgID] + if item == nil { + return + } + item.handleClick() +} + +func DefaultApplicationMenu() *Menu { + menu := NewMenu() + menu.AddRole(FileMenu) + menu.AddRole(EditMenu) + menu.AddRole(ViewMenu) + menu.AddRole(WindowMenu) + menu.AddRole(HelpMenu) + return menu +} diff --git a/v3/pkg/application/menuitem.go b/v3/pkg/application/menuitem.go new file mode 100644 index 000000000..5d11a87c7 --- /dev/null +++ b/v3/pkg/application/menuitem.go @@ -0,0 +1,456 @@ +package application + +import ( + "sync" + "sync/atomic" +) + +type menuItemType int + +const ( + text menuItemType = iota + separator + checkbox + radio + submenu +) + +var menuItemID uintptr +var menuItemMap = make(map[uint]*MenuItem) +var menuItemMapLock sync.Mutex + +func addToMenuItemMap(menuItem *MenuItem) { + menuItemMapLock.Lock() + menuItemMap[menuItem.id] = menuItem + menuItemMapLock.Unlock() +} + +func getMenuItemByID(id uint) *MenuItem { + menuItemMapLock.Lock() + defer menuItemMapLock.Unlock() + return menuItemMap[id] +} + +func removeMenuItemByID(id uint) { + menuItemMapLock.Lock() + defer menuItemMapLock.Unlock() + delete(menuItemMap, id) +} + +type menuItemImpl interface { + setTooltip(s string) + setLabel(s string) + setDisabled(disabled bool) + setChecked(checked bool) + setAccelerator(accelerator *accelerator) + setHidden(hidden bool) + setBitmap(bitmap []byte) + destroy() +} + +type MenuItem struct { + id uint + label string + tooltip string + disabled bool + checked bool + hidden bool + bitmap []byte + submenu *Menu + callback func(*Context) + itemType menuItemType + accelerator *accelerator + role Role + contextMenuData *ContextMenuData + + impl menuItemImpl + radioGroupMembers []*MenuItem +} + +func NewMenuItem(label string) *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + label: label, + itemType: text, + } + addToMenuItemMap(result) + return result +} + +func NewMenuItemSeparator() *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + itemType: separator, + } + return result +} + +func NewMenuItemCheckbox(label string, checked bool) *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + label: label, + checked: checked, + itemType: checkbox, + } + addToMenuItemMap(result) + return result +} + +func NewMenuItemRadio(label string, checked bool) *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + label: label, + checked: checked, + itemType: radio, + } + addToMenuItemMap(result) + return result +} + +func NewSubMenuItem(label string) *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + label: label, + itemType: submenu, + submenu: &Menu{ + label: label, + }, + } + addToMenuItemMap(result) + return result +} + +func NewRole(role Role) *MenuItem { + var result *MenuItem + switch role { + case AppMenu: + result = NewAppMenu() + case EditMenu: + result = NewEditMenu() + case FileMenu: + result = NewFileMenu() + case ViewMenu: + result = NewViewMenu() + case ServicesMenu: + return NewServicesMenu() + case SpeechMenu: + result = NewSpeechMenu() + case WindowMenu: + result = NewWindowMenu() + case HelpMenu: + result = NewHelpMenu() + case Hide: + result = NewHideMenuItem() + case Front: + result = NewFrontMenuItem() + case HideOthers: + result = NewHideOthersMenuItem() + case UnHide: + result = NewUnhideMenuItem() + case Undo: + result = NewUndoMenuItem() + case Redo: + result = NewRedoMenuItem() + case Cut: + result = NewCutMenuItem() + case Copy: + result = NewCopyMenuItem() + case Paste: + result = NewPasteMenuItem() + case PasteAndMatchStyle: + result = NewPasteAndMatchStyleMenuItem() + case SelectAll: + result = NewSelectAllMenuItem() + case Delete: + result = NewDeleteMenuItem() + case Quit: + result = NewQuitMenuItem() + case CloseWindow: + result = NewCloseMenuItem() + case About: + result = NewAboutMenuItem() + case Reload: + result = NewReloadMenuItem() + case ForceReload: + result = NewForceReloadMenuItem() + case ToggleFullscreen: + result = NewToggleFullscreenMenuItem() + case OpenDevTools: + result = NewOpenDevToolsMenuItem() + case ResetZoom: + result = NewZoomResetMenuItem() + case ZoomIn: + result = NewZoomInMenuItem() + case ZoomOut: + result = NewZoomOutMenuItem() + case Minimise: + result = NewMinimiseMenuItem() + case Zoom: + result = NewZoomMenuItem() + case FullScreen: + result = NewFullScreenMenuItem() + case Print: + result = NewPrintMenuItem() + case PageLayout: + result = NewPageLayoutMenuItem() + case NoRole: + case ShowAll: + result = NewShowAllMenuItem() + case BringAllToFront: + result = NewBringAllToFrontMenuItem() + case NewFile: + result = NewNewFileMenuItem() + case Open: + result = NewOpenMenuItem() + case Save: + result = NewSaveMenuItem() + case SaveAs: + result = NewSaveAsMenuItem() + case StartSpeaking: + result = NewStartSpeakingMenuItem() + case StopSpeaking: + result = NewStopSpeakingMenuItem() + case Revert: + result = NewRevertMenuItem() + case Find: + result = NewFindMenuItem() + case FindAndReplace: + result = NewFindAndReplaceMenuItem() + case FindNext: + result = NewFindNextMenuItem() + case FindPrevious: + result = NewFindPreviousMenuItem() + case Help: + result = NewHelpMenuItem() + + default: + globalApplication.error("no support for role: %v", role) + } + + if result != nil { + result.role = role + } + + return result +} + +func NewServicesMenu() *MenuItem { + serviceMenu := NewSubMenuItem("Services") + serviceMenu.role = ServicesMenu + return serviceMenu +} + +func (m *MenuItem) handleClick() { + var ctx = newContext(). + withClickedMenuItem(m). + withContextMenuData(m.contextMenuData) + if m.itemType == checkbox { + m.checked = !m.checked + ctx.withChecked(m.checked) + if m.impl != nil { + m.impl.setChecked(m.checked) + } + } + if m.itemType == radio { + for _, member := range m.radioGroupMembers { + member.checked = false + if member.impl != nil { + member.impl.setChecked(false) + } + } + m.checked = true + ctx.withChecked(true) + if m.impl != nil { + m.impl.setChecked(true) + } + } + if m.callback != nil { + go func() { + defer handlePanic() + m.callback(ctx) + }() + } +} + +func (m *MenuItem) SetAccelerator(shortcut string) *MenuItem { + accelerator, err := parseAccelerator(shortcut) + if err != nil { + globalApplication.error("invalid accelerator: %w", err) + return m + } + m.accelerator = accelerator + if m.impl != nil { + m.impl.setAccelerator(accelerator) + } + return m +} + +func (m *MenuItem) GetAccelerator() string { + if m.accelerator == nil { + return "" + } + return m.accelerator.String() +} + +func (m *MenuItem) RemoveAccelerator() { + m.accelerator = nil +} + +func (m *MenuItem) SetTooltip(s string) *MenuItem { + m.tooltip = s + if m.impl != nil { + m.impl.setTooltip(s) + } + return m +} + +func (m *MenuItem) SetRole(role Role) *MenuItem { + m.role = role + return m +} + +func (m *MenuItem) SetLabel(s string) *MenuItem { + m.label = s + if m.impl != nil { + m.impl.setLabel(s) + } + return m +} + +func (m *MenuItem) SetEnabled(enabled bool) *MenuItem { + m.disabled = !enabled + if m.impl != nil { + m.impl.setDisabled(m.disabled) + } + return m +} + +func (m *MenuItem) SetBitmap(bitmap []byte) *MenuItem { + m.bitmap = bitmap + if m.impl != nil { + m.impl.setBitmap(bitmap) + } + return m +} + +func (m *MenuItem) SetChecked(checked bool) *MenuItem { + m.checked = checked + if m.impl != nil { + m.impl.setChecked(m.checked) + } + return m +} + +func (m *MenuItem) SetHidden(hidden bool) *MenuItem { + m.hidden = hidden + if m.impl != nil { + m.impl.setHidden(m.hidden) + } + return m +} + +// GetSubmenu returns the submenu of the MenuItem. +// If the MenuItem is not a submenu, it returns nil. +func (m *MenuItem) GetSubmenu() *Menu { + return m.submenu +} + +func (m *MenuItem) Checked() bool { + return m.checked +} + +func (m *MenuItem) IsSeparator() bool { + return m.itemType == separator +} + +func (m *MenuItem) IsSubmenu() bool { + return m.itemType == submenu +} + +func (m *MenuItem) IsCheckbox() bool { + return m.itemType == checkbox +} + +func (m *MenuItem) IsRadio() bool { + return m.itemType == radio +} + +func (m *MenuItem) Hidden() bool { + return m.hidden +} + +func (m *MenuItem) OnClick(f func(*Context)) *MenuItem { + m.callback = f + return m +} + +func (m *MenuItem) Label() string { + return m.label +} + +func (m *MenuItem) Tooltip() string { + return m.tooltip +} + +func (m *MenuItem) Enabled() bool { + return !m.disabled +} + +func (m *MenuItem) setContextData(data *ContextMenuData) { + m.contextMenuData = data + if m.submenu != nil { + m.submenu.setContextData(data) + } +} + +// Clone returns a deep copy of the MenuItem +func (m *MenuItem) Clone() *MenuItem { + result := &MenuItem{ + id: m.id, + label: m.label, + tooltip: m.tooltip, + disabled: m.disabled, + checked: m.checked, + hidden: m.hidden, + bitmap: m.bitmap, + callback: m.callback, + itemType: m.itemType, + role: m.role, + } + if m.submenu != nil { + result.submenu = m.submenu.Clone() + } + if m.accelerator != nil { + result.accelerator = m.accelerator.clone() + } + if m.contextMenuData != nil { + result.contextMenuData = m.contextMenuData.clone() + } + return result +} + +func (m *MenuItem) Destroy() { + + removeMenuItemByID(m.id) + + // Clean up resources + if m.impl != nil { + m.impl.destroy() + } + if m.submenu != nil { + m.submenu.Destroy() + m.submenu = nil + } + + if m.contextMenuData != nil { + m.contextMenuData = nil + } + + if m.accelerator != nil { + m.accelerator = nil + } + + m.callback = nil + m.radioGroupMembers = nil + +} diff --git a/v3/pkg/application/menuitem_android.go b/v3/pkg/application/menuitem_android.go new file mode 100644 index 000000000..0c78d1cf8 --- /dev/null +++ b/v3/pkg/application/menuitem_android.go @@ -0,0 +1,24 @@ +//go:build android + +package application + +import "unsafe" + +// Android doesn't have traditional menu items like desktop platforms +// These are placeholder implementations + +func (m *MenuItem) handleStyleChange() {} + +func (m *MenuItem) handleLabelChange() {} + +func (m *MenuItem) handleCheckedChange() {} + +func (m *MenuItem) handleEnabledChange() {} + +func (m *MenuItem) handleTooltipChange() {} + +func (m *MenuItem) handleSubmenuChange() {} + +func (m *MenuItem) nativeMenuItem() unsafe.Pointer { + return nil +} diff --git a/v3/pkg/application/menuitem_darwin.go b/v3/pkg/application/menuitem_darwin.go new file mode 100644 index 000000000..953a73804 --- /dev/null +++ b/v3/pkg/application/menuitem_darwin.go @@ -0,0 +1,388 @@ +//go:build darwin && !ios + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -framework WebKit + +#include "Cocoa/Cocoa.h" +#include "menuitem_darwin.h" +#include "application_darwin.h" + +#define unicode(input) [NSString stringWithFormat:@"%C", input] + +// Create menu item +void* newMenuItem(unsigned int menuItemID, char *label, bool disabled, char* tooltip, char* selector) { + MenuItem *menuItem = [MenuItem new]; + + // Label + menuItem.title = [NSString stringWithUTF8String:label]; + + // Always set the action regardless of disabled state + if (selector != NULL) { + menuItem.action = NSSelectorFromString([NSString stringWithUTF8String:selector]); + menuItem.target = disabled ? nil : nil; // Role-based actions always use responder chain + } else { + menuItem.action = @selector(handleClick); + menuItem.target = disabled ? nil : menuItem; // Custom callbacks need target=menuItem when enabled + } + menuItem.menuItemID = menuItemID; + + menuItem.enabled = !disabled; + + // Tooltip + if( tooltip != NULL ) { + menuItem.toolTip = [NSString stringWithUTF8String:tooltip]; + free(tooltip); + } + + // Set the tag + [menuItem setTag:menuItemID]; + + return (void*)menuItem; +} + +// set menu item label +void setMenuItemLabel(void* nsMenuItem, char *label) { + dispatch_async(dispatch_get_main_queue(), ^{ + MenuItem *menuItem = (MenuItem *)nsMenuItem; + menuItem.title = [NSString stringWithUTF8String:label]; + free(label); + }); +} + +// set menu item disabled +void setMenuItemDisabled(void* nsMenuItem, bool disabled) { + dispatch_async(dispatch_get_main_queue(), ^{ + MenuItem *menuItem = (MenuItem *)nsMenuItem; + [menuItem setEnabled:!disabled]; + // Handle target based on whether item uses custom selector or handleClick + if( disabled ) { + [menuItem setTarget:nil]; + } else { + // Check if this menu item uses a custom selector (role-based) + // by checking if the action is handleClick or something else + if ([menuItem action] == @selector(handleClick)) { + // This is a custom callback menu item, set target to self + [menuItem setTarget:menuItem]; + } else { + // This is a role-based menu item, target should be nil + // to allow the action to be sent up the responder chain + [menuItem setTarget:nil]; + } + } + }); +} + +// set menu item hidden +void setMenuItemHidden(void* nsMenuItem, bool hidden) { + dispatch_async(dispatch_get_main_queue(), ^{ + MenuItem *menuItem = (MenuItem *)nsMenuItem; + [menuItem setHidden:hidden]; + }); +} + +// set menu item tooltip +void setMenuItemTooltip(void* nsMenuItem, char *tooltip) { + dispatch_async(dispatch_get_main_queue(), ^{ + MenuItem *menuItem = (MenuItem *)nsMenuItem; + menuItem.toolTip = [NSString stringWithUTF8String:tooltip]; + free(tooltip); + }); +} + +// Check menu item +void setMenuItemChecked(void* nsMenuItem, bool checked) { + dispatch_async(dispatch_get_main_queue(), ^{ + MenuItem *menuItem = (MenuItem *)nsMenuItem; + menuItem.state = checked ? NSControlStateValueOn : NSControlStateValueOff; + }); +} + +NSString* translateKey(NSString* key) { + + // Guard against no accelerator key + if( key == NULL ) { + return @""; + } + + if( [key isEqualToString:@"backspace"] ) { + return unicode(0x0008); + } + if( [key isEqualToString:@"tab"] ) { + return unicode(0x0009); + } + if( [key isEqualToString:@"return"] ) { + return unicode(0x000d); + } + if( [key isEqualToString:@"enter"] ) { + return unicode(0x000d); + } + if( [key isEqualToString:@"escape"] ) { + return unicode(0x001b); + } + if( [key isEqualToString:@"left"] ) { + return unicode(0xf702); + } + if( [key isEqualToString:@"right"] ) { + return unicode(0xf703); + } + if( [key isEqualToString:@"up"] ) { + return unicode(0xf700); + } + if( [key isEqualToString:@"down"] ) { + return unicode(0xf701); + } + if( [key isEqualToString:@"space"] ) { + return unicode(0x0020); + } + if( [key isEqualToString:@"delete"] ) { + return unicode(0x007f); + } + if( [key isEqualToString:@"home"] ) { + return unicode(0x2196); + } + if( [key isEqualToString:@"end"] ) { + return unicode(0x2198); + } + if( [key isEqualToString:@"page up"] ) { + return unicode(0x21de); + } + if( [key isEqualToString:@"page down"] ) { + return unicode(0x21df); + } + if( [key isEqualToString:@"f1"] ) { + return unicode(0xf704); + } + if( [key isEqualToString:@"f2"] ) { + return unicode(0xf705); + } + if( [key isEqualToString:@"f3"] ) { + return unicode(0xf706); + } + if( [key isEqualToString:@"f4"] ) { + return unicode(0xf707); + } + if( [key isEqualToString:@"f5"] ) { + return unicode(0xf708); + } + if( [key isEqualToString:@"f6"] ) { + return unicode(0xf709); + } + if( [key isEqualToString:@"f7"] ) { + return unicode(0xf70a); + } + if( [key isEqualToString:@"f8"] ) { + return unicode(0xf70b); + } + if( [key isEqualToString:@"f9"] ) { + return unicode(0xf70c); + } + if( [key isEqualToString:@"f10"] ) { + return unicode(0xf70d); + } + if( [key isEqualToString:@"f11"] ) { + return unicode(0xf70e); + } + if( [key isEqualToString:@"f12"] ) { + return unicode(0xf70f); + } + if( [key isEqualToString:@"f13"] ) { + return unicode(0xf710); + } + if( [key isEqualToString:@"f14"] ) { + return unicode(0xf711); + } + if( [key isEqualToString:@"f15"] ) { + return unicode(0xf712); + } + if( [key isEqualToString:@"f16"] ) { + return unicode(0xf713); + } + if( [key isEqualToString:@"f17"] ) { + return unicode(0xf714); + } + if( [key isEqualToString:@"f18"] ) { + return unicode(0xf715); + } + if( [key isEqualToString:@"f19"] ) { + return unicode(0xf716); + } + if( [key isEqualToString:@"f20"] ) { + return unicode(0xf717); + } + if( [key isEqualToString:@"f21"] ) { + return unicode(0xf718); + } + if( [key isEqualToString:@"f22"] ) { + return unicode(0xf719); + } + if( [key isEqualToString:@"f23"] ) { + return unicode(0xf71a); + } + if( [key isEqualToString:@"f24"] ) { + return unicode(0xf71b); + } + if( [key isEqualToString:@"f25"] ) { + return unicode(0xf71c); + } + if( [key isEqualToString:@"f26"] ) { + return unicode(0xf71d); + } + if( [key isEqualToString:@"f27"] ) { + return unicode(0xf71e); + } + if( [key isEqualToString:@"f28"] ) { + return unicode(0xf71f); + } + if( [key isEqualToString:@"f29"] ) { + return unicode(0xf720); + } + if( [key isEqualToString:@"f30"] ) { + return unicode(0xf721); + } + if( [key isEqualToString:@"f31"] ) { + return unicode(0xf722); + } + if( [key isEqualToString:@"f32"] ) { + return unicode(0xf723); + } + if( [key isEqualToString:@"f33"] ) { + return unicode(0xf724); + } + if( [key isEqualToString:@"f34"] ) { + return unicode(0xf725); + } + if( [key isEqualToString:@"f35"] ) { + return unicode(0xf726); + } + if( [key isEqualToString:@"numLock"] ) { + return unicode(0xf739); + } + return key; +} + +// Set the menuitem key equivalent +void setMenuItemKeyEquivalent(void* nsMenuItem, char *key, int modifier) { + MenuItem *menuItem = (MenuItem *)nsMenuItem; + NSString *nskey = [NSString stringWithUTF8String:key]; + menuItem.keyEquivalent = translateKey(nskey); + menuItem.keyEquivalentModifierMask = modifier; + free(key); +} + +// Call the copy selector on the pasteboard +static void copyToPasteboard(char *text) { + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard clearContents]; + [pasteboard setString:[NSString stringWithUTF8String:text] forType:NSPasteboardTypeString]; +} + +// Call the paste selector on the pasteboard +static char *pasteFromPasteboard(void) { + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + NSString *text = [pasteboard stringForType:NSPasteboardTypeString]; + if( text == nil ) { + return NULL; + } + return strdup([text UTF8String]); +} + +void performSelectorOnMainThreadForFirstResponder(SEL selector) { + NSWindow *activeWindow = [[NSApplication sharedApplication] keyWindow]; + if (activeWindow) { + [activeWindow performSelectorOnMainThread:selector withObject:nil waitUntilDone:YES]; + } +} + +void setMenuItemBitmap(void* nsMenuItem, unsigned char *bitmap, int length) { + MenuItem *menuItem = (MenuItem *)nsMenuItem; + NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:bitmap length:length]]; + [menuItem setImage:image]; +} + +void destroyMenuItem(void* nsMenuItem) { + MenuItem *menuItem = (MenuItem *)nsMenuItem; + [menuItem release]; +} +*/ +import "C" +import ( + "unsafe" +) + +type macosMenuItem struct { + menuItem *MenuItem + + nsMenuItem unsafe.Pointer +} + +func (m macosMenuItem) setTooltip(tooltip string) { + C.setMenuItemTooltip(m.nsMenuItem, C.CString(tooltip)) +} + +func (m macosMenuItem) setLabel(s string) { + C.setMenuItemLabel(m.nsMenuItem, C.CString(s)) +} + +func (m macosMenuItem) setDisabled(disabled bool) { + C.setMenuItemDisabled(m.nsMenuItem, C.bool(disabled)) +} + +func (m macosMenuItem) setChecked(checked bool) { + C.setMenuItemChecked(m.nsMenuItem, C.bool(checked)) +} + +func (m macosMenuItem) setHidden(hidden bool) { + C.setMenuItemHidden(m.nsMenuItem, C.bool(hidden)) +} + +func (m macosMenuItem) setBitmap(bitmap []byte) { + C.setMenuItemBitmap(m.nsMenuItem, (*C.uchar)(&bitmap[0]), C.int(len(bitmap))) +} + +func (m macosMenuItem) setAccelerator(accelerator *accelerator) { + // Set the keyboard shortcut of the menu item + var modifier C.int + var key *C.char + if accelerator != nil { + modifier = C.int(toMacModifier(accelerator.Modifiers)) + key = C.CString(accelerator.Key) + } + + // Convert the key to a string + C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier) +} + +func (m macosMenuItem) destroy() { + C.destroyMenuItem(m.nsMenuItem) +} + +func newMenuItemImpl(item *MenuItem) *macosMenuItem { + result := &macosMenuItem{ + menuItem: item, + } + + selector := getSelectorForRole(item.role) + if selector != nil { + defer C.free(unsafe.Pointer(selector)) + } + result.nsMenuItem = unsafe.Pointer(C.newMenuItem( + C.uint(item.id), + C.CString(item.label), + C.bool(item.disabled), + C.CString(item.tooltip), + selector, + )) + + switch item.itemType { + case checkbox, radio: + C.setMenuItemChecked(result.nsMenuItem, C.bool(item.checked)) + } + + if item.accelerator != nil { + result.setAccelerator(item.accelerator) + } + return result +} diff --git a/v3/pkg/application/menuitem_darwin.h b/v3/pkg/application/menuitem_darwin.h new file mode 100644 index 000000000..8260d4dfd --- /dev/null +++ b/v3/pkg/application/menuitem_darwin.h @@ -0,0 +1,17 @@ +#ifndef MenuItemDelegate_h +#define MenuItemDelegate_h + +#import + +extern void processMenuItemClick(unsigned int); + +@interface MenuItem : NSMenuItem + +@property unsigned int menuItemID; + +- (void) handleClick; + +@end + + +#endif /* MenuItemDelegate_h */ diff --git a/v3/pkg/application/menuitem_darwin.m b/v3/pkg/application/menuitem_darwin.m new file mode 100644 index 000000000..5eeb9707d --- /dev/null +++ b/v3/pkg/application/menuitem_darwin.m @@ -0,0 +1,13 @@ +//go:build darwin && !ios + +#import + +#import "menuitem_darwin.h" + +@implementation MenuItem + +- (void) handleClick { + processMenuItemClick(self.menuItemID); +} + +@end diff --git a/v3/pkg/application/menuitem_dev.go b/v3/pkg/application/menuitem_dev.go new file mode 100644 index 000000000..4ce99fd66 --- /dev/null +++ b/v3/pkg/application/menuitem_dev.go @@ -0,0 +1,14 @@ +//go:build !production || devtools + +package application + +func NewOpenDevToolsMenuItem() *MenuItem { + return NewMenuItem("Open Developer Tools"). + SetAccelerator("Alt+Command+I"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.OpenDevTools() + } + }) +} diff --git a/v3/pkg/application/menuitem_internal_test.go b/v3/pkg/application/menuitem_internal_test.go new file mode 100644 index 000000000..12843d4c8 --- /dev/null +++ b/v3/pkg/application/menuitem_internal_test.go @@ -0,0 +1,413 @@ +package application + +import ( + "testing" +) + +func TestMenuItemType_Constants(t *testing.T) { + // Verify menu item type constants are distinct + if text != 0 { + t.Error("text should be 0") + } + if separator != 1 { + t.Error("separator should be 1") + } + if checkbox != 2 { + t.Error("checkbox should be 2") + } + if radio != 3 { + t.Error("radio should be 3") + } + if submenu != 4 { + t.Error("submenu should be 4") + } +} + +func TestNewMenuItem(t *testing.T) { + item := NewMenuItem("Test Label") + + if item == nil { + t.Fatal("NewMenuItem returned nil") + } + if item.label != "Test Label" { + t.Errorf("label = %q, want %q", item.label, "Test Label") + } + if item.itemType != text { + t.Error("itemType should be text") + } + if item.id == 0 { + t.Error("id should be non-zero") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestNewMenuItemSeparator(t *testing.T) { + item := NewMenuItemSeparator() + + if item == nil { + t.Fatal("NewMenuItemSeparator returned nil") + } + if item.itemType != separator { + t.Error("itemType should be separator") + } +} + +func TestNewMenuItemCheckbox(t *testing.T) { + item := NewMenuItemCheckbox("Checkbox", true) + + if item == nil { + t.Fatal("NewMenuItemCheckbox returned nil") + } + if item.label != "Checkbox" { + t.Errorf("label = %q, want %q", item.label, "Checkbox") + } + if item.itemType != checkbox { + t.Error("itemType should be checkbox") + } + if !item.checked { + t.Error("checked should be true") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestNewMenuItemCheckbox_Unchecked(t *testing.T) { + item := NewMenuItemCheckbox("Unchecked", false) + + if item.checked { + t.Error("checked should be false") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestNewMenuItemRadio(t *testing.T) { + item := NewMenuItemRadio("Radio", true) + + if item == nil { + t.Fatal("NewMenuItemRadio returned nil") + } + if item.label != "Radio" { + t.Errorf("label = %q, want %q", item.label, "Radio") + } + if item.itemType != radio { + t.Error("itemType should be radio") + } + if !item.checked { + t.Error("checked should be true") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestNewSubMenuItem(t *testing.T) { + item := NewSubMenuItem("Submenu") + + if item == nil { + t.Fatal("NewSubMenuItem returned nil") + } + if item.label != "Submenu" { + t.Errorf("label = %q, want %q", item.label, "Submenu") + } + if item.itemType != submenu { + t.Error("itemType should be submenu") + } + if item.submenu == nil { + t.Error("submenu should not be nil") + } + if item.submenu.label != "Submenu" { + t.Errorf("submenu.label = %q, want %q", item.submenu.label, "Submenu") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItemMap_AddGetRemove(t *testing.T) { + item := NewMenuItem("Test") + id := item.id + + // Item should be in map + retrieved := getMenuItemByID(id) + if retrieved != item { + t.Error("getMenuItemByID should return the same item") + } + + // Remove item + removeMenuItemByID(id) + + // Item should be gone + retrieved = getMenuItemByID(id) + if retrieved != nil { + t.Error("getMenuItemByID should return nil after removal") + } +} + +func TestGetMenuItemByID_NotFound(t *testing.T) { + result := getMenuItemByID(999999) + if result != nil { + t.Error("getMenuItemByID should return nil for non-existent ID") + } +} + +func TestMenuItem_UniqueIDs(t *testing.T) { + item1 := NewMenuItem("Item 1") + item2 := NewMenuItem("Item 2") + item3 := NewMenuItem("Item 3") + + if item1.id == item2.id || item2.id == item3.id || item1.id == item3.id { + t.Error("Menu items should have unique IDs") + } + + // Clean up + removeMenuItemByID(item1.id) + removeMenuItemByID(item2.id) + removeMenuItemByID(item3.id) +} + +func TestMenuItem_Label(t *testing.T) { + item := NewMenuItem("Original") + + if item.Label() != "Original" { + t.Errorf("Label() = %q, want %q", item.Label(), "Original") + } + + item.SetLabel("Updated") + if item.label != "Updated" { + t.Errorf("label = %q, want %q", item.label, "Updated") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_Enabled(t *testing.T) { + item := NewMenuItem("Test") + + if item.disabled { + t.Error("disabled should default to false") + } + + item.SetEnabled(false) + if !item.disabled { + t.Error("disabled should be true after SetEnabled(false)") + } + + item.SetEnabled(true) + if item.disabled { + t.Error("disabled should be false after SetEnabled(true)") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_Checked(t *testing.T) { + item := NewMenuItemCheckbox("Test", false) + + if item.checked { + t.Error("checked should be false") + } + + item.SetChecked(true) + if !item.checked { + t.Error("checked should be true after SetChecked(true)") + } + + item.SetChecked(false) + if item.checked { + t.Error("checked should be false after SetChecked(false)") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_Hidden(t *testing.T) { + item := NewMenuItem("Test") + + if item.hidden { + t.Error("hidden should default to false") + } + + item.SetHidden(true) + if !item.hidden { + t.Error("hidden should be true after SetHidden(true)") + } + + item.SetHidden(false) + if item.hidden { + t.Error("hidden should be false after SetHidden(false)") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_Tooltip(t *testing.T) { + item := NewMenuItem("Test") + + if item.tooltip != "" { + t.Error("tooltip should default to empty string") + } + + item.SetTooltip("Tooltip text") + if item.tooltip != "Tooltip text" { + t.Errorf("tooltip = %q, want %q", item.tooltip, "Tooltip text") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_Bitmap(t *testing.T) { + item := NewMenuItem("Test") + + if item.bitmap != nil { + t.Error("bitmap should default to nil") + } + + bitmap := []byte{0x89, 0x50, 0x4E, 0x47} + item.SetBitmap(bitmap) + if len(item.bitmap) != 4 { + t.Error("bitmap should be set") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_OnClick(t *testing.T) { + item := NewMenuItem("Test") + + if item.callback != nil { + t.Error("callback should default to nil") + } + + called := false + item.OnClick(func(ctx *Context) { + called = true + }) + + if item.callback == nil { + t.Error("callback should be set after OnClick") + } + + // Call the callback + item.callback(nil) + if !called { + t.Error("callback should have been called") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_SetAccelerator(t *testing.T) { + item := NewMenuItem("Test") + + if item.accelerator != nil { + t.Error("accelerator should default to nil") + } + + result := item.SetAccelerator("Ctrl+A") + if result != item { + t.Error("SetAccelerator should return the same item for chaining") + } + + if item.accelerator == nil { + t.Error("accelerator should be set after SetAccelerator") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_GetAccelerator(t *testing.T) { + item := NewMenuItem("Test") + + if item.GetAccelerator() != "" { + t.Error("GetAccelerator should return empty string when not set") + } + + item.SetAccelerator("Ctrl+B") + acc := item.GetAccelerator() + if acc == "" { + t.Error("GetAccelerator should return non-empty string when set") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_RemoveAccelerator(t *testing.T) { + item := NewMenuItem("Test") + item.SetAccelerator("Ctrl+C") + + item.RemoveAccelerator() + if item.accelerator != nil { + t.Error("accelerator should be nil after RemoveAccelerator") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_Clone(t *testing.T) { + original := NewMenuItem("Original") + original.SetTooltip("Tooltip") + original.SetChecked(true) + original.SetEnabled(false) + + clone := original.Clone() + + if clone == original { + t.Error("Clone should return a different pointer") + } + if clone.label != original.label { + t.Error("Clone should have same label") + } + if clone.tooltip != original.tooltip { + t.Error("Clone should have same tooltip") + } + if clone.checked != original.checked { + t.Error("Clone should have same checked state") + } + if clone.disabled != original.disabled { + t.Error("Clone should have same disabled state") + } + // Note: Clone preserves the ID (shallow clone behavior) + + // Clean up + removeMenuItemByID(original.id) + removeMenuItemByID(clone.id) +} + +func TestMenuItem_Chaining(t *testing.T) { + item := NewMenuItem("Test"). + SetTooltip("Tooltip"). + SetEnabled(false). + SetHidden(true). + SetAccelerator("Ctrl+X") + + if item.tooltip != "Tooltip" { + t.Error("Chaining SetTooltip failed") + } + if !item.disabled { + t.Error("Chaining SetEnabled failed") + } + if !item.hidden { + t.Error("Chaining SetHidden failed") + } + if item.accelerator == nil { + t.Error("Chaining SetAccelerator failed") + } + + // Clean up + removeMenuItemByID(item.id) +} diff --git a/v3/pkg/application/menuitem_ios.go b/v3/pkg/application/menuitem_ios.go new file mode 100644 index 000000000..31709fe96 --- /dev/null +++ b/v3/pkg/application/menuitem_ios.go @@ -0,0 +1,24 @@ +//go:build ios + +package application + +import "unsafe" + +// iOS doesn't have traditional menu items like desktop platforms +// These are placeholder implementations + +func (m *MenuItem) handleStyleChange() {} + +func (m *MenuItem) handleLabelChange() {} + +func (m *MenuItem) handleCheckedChange() {} + +func (m *MenuItem) handleEnabledChange() {} + +func (m *MenuItem) handleTooltipChange() {} + +func (m *MenuItem) handleSubmenuChange() {} + +func (m *MenuItem) nativeMenuItem() unsafe.Pointer { + return nil +} \ No newline at end of file diff --git a/v3/pkg/application/menuitem_linux.go b/v3/pkg/application/menuitem_linux.go new file mode 100644 index 000000000..9aa256c8e --- /dev/null +++ b/v3/pkg/application/menuitem_linux.go @@ -0,0 +1,436 @@ +//go:build linux && !android && !server + +package application + +import ( + "fmt" + "runtime" +) + +type linuxMenuItem struct { + menuItem *MenuItem + native pointer + handlerId uint +} + +func (l linuxMenuItem) setTooltip(tooltip string) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetToolTip(l.native, tooltip) + }) +} + +func (l linuxMenuItem) destroy() { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemDestroy(l.native) + }) +} + +func (l linuxMenuItem) blockSignal() { + if l.handlerId != 0 { + menuItemSignalBlock(l.native, l.handlerId, true) + } +} +func (l linuxMenuItem) setBitmap(data []byte) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetBitmap(l.native, data) + }) +} + +func (l linuxMenuItem) unBlockSignal() { + if l.handlerId != 0 { + menuItemSignalBlock(l.native, l.handlerId, false) + } +} + +func (l linuxMenuItem) setLabel(s string) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetLabel(l.native, s) + }) +} + +func (l linuxMenuItem) isChecked() bool { + return menuItemChecked(l.native) +} + +func (l linuxMenuItem) setDisabled(disabled bool) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetDisabled(l.native, disabled) + }) +} + +func (l linuxMenuItem) setChecked(checked bool) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetChecked(l.native, checked) + }) +} + +func (l linuxMenuItem) setHidden(hidden bool) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + widgetSetVisible(l.native, hidden) + }) +} + +func (l linuxMenuItem) setAccelerator(accelerator *accelerator) { + fmt.Println("setAccelerator", accelerator) + // Set the keyboard shortcut of the menu item + // var modifier C.int + // var key *C.char + if accelerator != nil { + // modifier = C.int(toMacModifier(accelerator.Modifiers)) + // key = C.CString(accelerator.Key) + } + + // Convert the key to a string + // C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier) +} + +func newMenuItemImpl(item *MenuItem) *linuxMenuItem { + result := &linuxMenuItem{ + menuItem: item, + } + switch item.itemType { + case text: + result.native = menuItemNew(item.label, item.bitmap) + + case checkbox: + result.native = menuCheckItemNew(item.label, item.bitmap) + result.setChecked(item.checked) + if item.accelerator != nil { + result.setAccelerator(item.accelerator) + } + case submenu: + result.native = menuItemNew(item.label, item.bitmap) + + default: + panic(fmt.Sprintf("Unknown menu type: %v", item.itemType)) + } + result.setDisabled(result.menuItem.disabled) + return result +} + +func newRadioItemImpl(item *MenuItem, group GSListPointer) *linuxMenuItem { + result := &linuxMenuItem{ + menuItem: item, + native: menuRadioItemNew(group, item.label), + } + result.setChecked(item.checked) + result.setDisabled(result.menuItem.disabled) + return result +} + +func newSpeechMenu() *MenuItem { + speechMenu := NewMenu() + speechMenu.Add("Start Speaking"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+."). + OnClick(func(ctx *Context) { + // C.startSpeaking() + }) + speechMenu.Add("Stop Speaking"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+,"). + OnClick(func(ctx *Context) { + // C.stopSpeaking() + }) + subMenu := NewSubMenuItem("Speech") + subMenu.submenu = speechMenu + return subMenu +} + +func newFrontMenuItem() *MenuItem { + panic("implement me") +} + +func newHideMenuItem() *MenuItem { + return NewMenuItem("Hide " + globalApplication.options.Name). + SetAccelerator("CmdOrCtrl+h"). + OnClick(func(ctx *Context) { + + // C.hideApplication() + }) +} + +func newHideOthersMenuItem() *MenuItem { + return NewMenuItem("Hide Others"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+h"). + OnClick(func(ctx *Context) { + // C.hideOthers() + }) +} + +func newUnhideMenuItem() *MenuItem { + return NewMenuItem("Show All"). + OnClick(func(ctx *Context) { + // C.showAll() + }) +} + +func newUndoMenuItem() *MenuItem { + return NewMenuItem("Undo"). + SetAccelerator("CmdOrCtrl+z"). + OnClick(func(ctx *Context) { + // C.undo() + }) +} + +// newRedoMenuItem creates a new menu item for redoing the last action +func newRedoMenuItem() *MenuItem { + return NewMenuItem("Redo"). + SetAccelerator("CmdOrCtrl+Shift+z"). + OnClick(func(ctx *Context) { + // C.redo() + }) +} + +func newCutMenuItem() *MenuItem { + return NewMenuItem("Cut"). + SetAccelerator("CmdOrCtrl+x"). + OnClick(func(ctx *Context) { + // C.cut() + }) +} + +func newCopyMenuItem() *MenuItem { + return NewMenuItem("Copy"). + SetAccelerator("CmdOrCtrl+c"). + OnClick(func(ctx *Context) { + // C.copy() + }) +} + +func newPasteMenuItem() *MenuItem { + return NewMenuItem("Paste"). + SetAccelerator("CmdOrCtrl+v"). + OnClick(func(ctx *Context) { + // C.paste() + }) +} + +func newPasteAndMatchStyleMenuItem() *MenuItem { + return NewMenuItem("Paste and Match Style"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+v"). + OnClick(func(ctx *Context) { + // C.pasteAndMatchStyle() + }) +} + +func newDeleteMenuItem() *MenuItem { + return NewMenuItem("Delete"). + SetAccelerator("backspace"). + OnClick(func(ctx *Context) { + // C.delete() + }) +} + +func newQuitMenuItem() *MenuItem { + return NewMenuItem("Quit " + globalApplication.options.Name). + SetAccelerator("CmdOrCtrl+q"). + OnClick(func(ctx *Context) { + globalApplication.Quit() + }) +} + +func newSelectAllMenuItem() *MenuItem { + return NewMenuItem("Select All"). + SetAccelerator("CmdOrCtrl+a"). + OnClick(func(ctx *Context) { + // C.selectAll() + }) +} + +func newAboutMenuItem() *MenuItem { + return NewMenuItem("About " + globalApplication.options.Name). + OnClick(func(ctx *Context) { + globalApplication.Menu.ShowAbout() + }) +} + +func newCloseMenuItem() *MenuItem { + return NewMenuItem("Close"). + SetAccelerator("CmdOrCtrl+w"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Close() + } + }) +} + +func newReloadMenuItem() *MenuItem { + return NewMenuItem("Reload"). + SetAccelerator("CmdOrCtrl+r"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Reload() + } + }) +} + +func newForceReloadMenuItem() *MenuItem { + return NewMenuItem("Force Reload"). + SetAccelerator("CmdOrCtrl+Shift+r"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ForceReload() + } + }) +} + +func newToggleFullscreenMenuItem() *MenuItem { + result := NewMenuItem("Toggle Full Screen"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ToggleFullscreen() + } + }) + if runtime.GOOS == "darwin" { + result.SetAccelerator("Ctrl+Command+F") + } else { + result.SetAccelerator("F11") + } + return result +} + +func newZoomResetMenuItem() *MenuItem { + // reset zoom menu item + return NewMenuItem("Actual Size"). + SetAccelerator("CmdOrCtrl+0"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ZoomReset() + } + }) +} + +func newZoomInMenuItem() *MenuItem { + return NewMenuItem("Zoom In"). + SetAccelerator("CmdOrCtrl+plus"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ZoomIn() + } + }) +} + +func newZoomOutMenuItem() *MenuItem { + return NewMenuItem("Zoom Out"). + SetAccelerator("CmdOrCtrl+-"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ZoomOut() + } + }) +} + +func newMinimizeMenuItem() *MenuItem { + return NewMenuItem("Minimize"). + SetAccelerator("CmdOrCtrl+M"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Minimise() + } + }) +} + +func newZoomMenuItem() *MenuItem { + return NewMenuItem("Zoom"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Zoom() + } + }) +} + +func newFullScreenMenuItem() *MenuItem { + return NewMenuItem("Fullscreen"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Fullscreen() + } + }) +} + +func newPrintMenuItem() *MenuItem { + panic("Implement me") +} + +func newPageLayoutMenuItem() *MenuItem { + panic("Implement me") +} + +func newShowAllMenuItem() *MenuItem { + panic("Implement me") +} + +func newBringAllToFrontMenuItem() *MenuItem { + panic("Implement me") +} + +func newNewFileMenuItem() *MenuItem { + panic("Implement me") +} + +func newOpenMenuItem() *MenuItem { + panic("Implement me") +} + +func newSaveMenuItem() *MenuItem { + panic("Implement me") +} + +func newSaveAsMenuItem() *MenuItem { + panic("Implement me") +} + +func newStartSpeakingMenuItem() *MenuItem { + panic("Implement me") +} + +func newStopSpeakingMenuItem() *MenuItem { + panic("Implement me") +} + +func newRevertMenuItem() *MenuItem { + panic("Implement me") +} + +func newFindMenuItem() *MenuItem { + panic("Implement me") +} + +func newFindAndReplaceMenuItem() *MenuItem { + panic("Implement me") +} + +func newFindNextMenuItem() *MenuItem { + panic("Implement me") +} + +func newFindPreviousMenuItem() *MenuItem { + panic("Implement me") +} + +func newHelpMenuItem() *MenuItem { + panic("Implement me") +} diff --git a/v3/pkg/application/menuitem_production.go b/v3/pkg/application/menuitem_production.go new file mode 100644 index 000000000..b5aac387f --- /dev/null +++ b/v3/pkg/application/menuitem_production.go @@ -0,0 +1,7 @@ +//go:build production && !devtools + +package application + +func NewOpenDevToolsMenuItem() *MenuItem { + return nil +} diff --git a/v3/pkg/application/menuitem_roles.go b/v3/pkg/application/menuitem_roles.go new file mode 100644 index 000000000..f36908884 --- /dev/null +++ b/v3/pkg/application/menuitem_roles.go @@ -0,0 +1,358 @@ +package application + +import "runtime" + +func NewSpeechMenu() *MenuItem { + speechMenu := NewMenu() + speechMenu.AddRole(StartSpeaking) + speechMenu.AddRole(StopSpeaking) + subMenu := NewSubMenuItem("Speech") + subMenu.submenu = speechMenu + return subMenu +} + +func NewHideMenuItem() *MenuItem { + return NewMenuItem("Hide " + globalApplication.options.Name). + SetAccelerator("CmdOrCtrl+h"). + SetRole(Hide) +} + +func NewHideOthersMenuItem() *MenuItem { + return NewMenuItem("Hide Others"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+h"). + SetRole(HideOthers) +} + +func NewFrontMenuItem() *MenuItem { + return NewMenuItem("Bring All to Front") +} + +func NewUnhideMenuItem() *MenuItem { + return NewMenuItem("Show All") +} + +func NewUndoMenuItem() *MenuItem { + result := NewMenuItem("Undo"). + SetAccelerator("CmdOrCtrl+z") + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.undo() + } + }) + } + return result +} + +// NewRedoMenuItem creates a new menu item for redoing the last action +func NewRedoMenuItem() *MenuItem { + result := NewMenuItem("Redo"). + SetAccelerator("CmdOrCtrl+Shift+z") + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.redo() + } + }) + } + return result +} + +func NewCutMenuItem() *MenuItem { + result := NewMenuItem("Cut"). + SetAccelerator("CmdOrCtrl+x") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.cut() + } + }) + } + return result +} + +func NewCopyMenuItem() *MenuItem { + result := NewMenuItem("Copy"). + SetAccelerator("CmdOrCtrl+c") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.copy() + } + }) + } + return result +} + +func NewPasteMenuItem() *MenuItem { + result := NewMenuItem("Paste"). + SetAccelerator("CmdOrCtrl+v") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.paste() + } + }) + } + return result +} + +func NewPasteAndMatchStyleMenuItem() *MenuItem { + return NewMenuItem("Paste and Match Style"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+v") +} + +func NewDeleteMenuItem() *MenuItem { + result := NewMenuItem("Delete"). + SetAccelerator("backspace") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.delete() + } + }) + } + return result +} + +func NewQuitMenuItem() *MenuItem { + label := "Quit" + if runtime.GOOS == "darwin" { + if globalApplication.options.Name != "" { + label += " " + globalApplication.options.Name + } + } + return NewMenuItem(label). + SetAccelerator("CmdOrCtrl+q"). + OnClick(func(ctx *Context) { + globalApplication.Quit() + }) +} + +func NewSelectAllMenuItem() *MenuItem { + result := NewMenuItem("Select All"). + SetAccelerator("CmdOrCtrl+a") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.selectAll() + } + }) + } + return result +} + +func NewAboutMenuItem() *MenuItem { + label := "About" + if globalApplication.options.Name != "" { + label += " " + globalApplication.options.Name + } + return NewMenuItem(label). + OnClick(func(ctx *Context) { + globalApplication.Menu.ShowAbout() + }) +} + +func NewCloseMenuItem() *MenuItem { + return NewMenuItem("Close"). + SetAccelerator("CmdOrCtrl+w"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Close() + } + }) +} + +func NewReloadMenuItem() *MenuItem { + return NewMenuItem("Reload"). + SetAccelerator("CmdOrCtrl+r"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Reload() + } + }) +} + +func NewForceReloadMenuItem() *MenuItem { + return NewMenuItem("Force Reload"). + SetAccelerator("CmdOrCtrl+Shift+r"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ForceReload() + } + }) +} + +func NewToggleFullscreenMenuItem() *MenuItem { + result := NewMenuItem("Toggle Full Screen"). + SetAccelerator("Ctrl+Command+F"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ToggleFullscreen() + } + }) + if runtime.GOOS != "darwin" { + result.SetAccelerator("F11") + } + return result +} + +func NewZoomResetMenuItem() *MenuItem { + // reset zoom menu item + return NewMenuItem("Actual Size"). + SetAccelerator("CmdOrCtrl+0"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ZoomReset() + } + }) +} + +func NewZoomInMenuItem() *MenuItem { + return NewMenuItem("Zoom In"). + SetAccelerator("CmdOrCtrl+plus"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ZoomIn() + } + }) +} + +func NewZoomOutMenuItem() *MenuItem { + return NewMenuItem("Zoom Out"). + SetAccelerator("CmdOrCtrl+-"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ZoomOut() + } + }) +} + +func NewMinimiseMenuItem() *MenuItem { + return NewMenuItem("Minimize"). + SetAccelerator("CmdOrCtrl+M"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Minimise() + } + }) +} + +func NewZoomMenuItem() *MenuItem { + return NewMenuItem("Zoom"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Zoom() + } + }) +} + +func NewFullScreenMenuItem() *MenuItem { + return NewMenuItem("Fullscreen"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Fullscreen() + } + }) +} + +func NewPrintMenuItem() *MenuItem { + return NewMenuItem("Print"). + SetAccelerator("CmdOrCtrl+p") +} + +func NewPageLayoutMenuItem() *MenuItem { + return NewMenuItem("Page Setup..."). + SetAccelerator("CmdOrCtrl+Shift+p") +} + +func NewShowAllMenuItem() *MenuItem { + return NewMenuItem("Show All") +} + +func NewBringAllToFrontMenuItem() *MenuItem { + return NewMenuItem("Bring All to Front") +} + +func NewNewFileMenuItem() *MenuItem { + return NewMenuItem("New File"). + SetAccelerator("CmdOrCtrl+n") +} + +func NewOpenMenuItem() *MenuItem { + return NewMenuItem("Open..."). + SetAccelerator("CmdOrCtrl+o"). + SetRole(Open) +} + +func NewSaveMenuItem() *MenuItem { + return NewMenuItem("Save"). + SetAccelerator("CmdOrCtrl+s") +} + +func NewSaveAsMenuItem() *MenuItem { + return NewMenuItem("Save As..."). + SetAccelerator("CmdOrCtrl+Shift+s") +} + +func NewStartSpeakingMenuItem() *MenuItem { + return NewMenuItem("Start Speaking"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+.") +} + +func NewStopSpeakingMenuItem() *MenuItem { + return NewMenuItem("Stop Speaking"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+,") +} + +func NewRevertMenuItem() *MenuItem { + return NewMenuItem("Revert"). + SetAccelerator("CmdOrCtrl+r") +} + +func NewFindMenuItem() *MenuItem { + return NewMenuItem("Find..."). + SetAccelerator("CmdOrCtrl+f") +} + +func NewFindAndReplaceMenuItem() *MenuItem { + return NewMenuItem("Find and Replace..."). + SetAccelerator("CmdOrCtrl+Shift+f") +} + +func NewFindNextMenuItem() *MenuItem { + return NewMenuItem("Find Next"). + SetAccelerator("CmdOrCtrl+g") +} + +func NewFindPreviousMenuItem() *MenuItem { + return NewMenuItem("Find Previous"). + SetAccelerator("CmdOrCtrl+Shift+g") +} + +func NewHelpMenuItem() *MenuItem { + return NewMenuItem("Help"). + SetAccelerator("CmdOrCtrl+?") +} diff --git a/v3/pkg/application/menuitem_selectors_darwin.go b/v3/pkg/application/menuitem_selectors_darwin.go new file mode 100644 index 000000000..3fd001897 --- /dev/null +++ b/v3/pkg/application/menuitem_selectors_darwin.go @@ -0,0 +1,57 @@ +// File: v3/pkg/application/menuitem_selectors_darwin.go + +//go:build darwin + +package application + +import "C" + +var roleToSelector = map[Role]string{ + //AppMenu: "", // This is a special case, handled separately + About: "orderFrontStandardAboutPanel:", + //ServicesMenu: "", // This is a submenu, no direct selector + Hide: "hide:", + HideOthers: "hideOtherApplications:", + ShowAll: "unhideAllApplications:", + Quit: "terminate:", + //WindowMenu: "", // This is a submenu, no direct selector + Minimise: "performMiniaturize:", + Zoom: "performZoom:", + BringAllToFront: "arrangeInFront:", + CloseWindow: "performClose:", + //EditMenu: "", // This is a submenu, no direct selector + Undo: "undo:", + Redo: "redo:", + Cut: "cut:", + Copy: "copy:", + Paste: "paste:", + Delete: "delete:", + SelectAll: "selectAll:", + //FindMenu: "", // This is a submenu, no direct selector + Find: "performTextFinderAction:", + FindAndReplace: "performTextFinderAction:", + FindNext: "performTextFinderAction:", + FindPrevious: "performTextFinderAction:", + //ViewMenu: "", // This is a submenu, no direct selector + ToggleFullscreen: "toggleFullScreen:", + //FileMenu: "", // This is a submenu, no direct selector + NewFile: "newDocument:", + Open: "openDocument:", + Save: "saveDocument:", + SaveAs: "saveDocumentAs:", + StartSpeaking: "startSpeaking:", + StopSpeaking: "stopSpeaking:", + Revert: "revertDocumentToSaved:", + Print: "printDocument:", + PageLayout: "runPageLayout:", + //HelpMenu: "", // This is a submenu, no direct selector + Help: "showHelp:", + //No: "", // No specific selector for this role +} + +func getSelectorForRole(role Role) *C.char { + if selector, ok := roleToSelector[role]; ok && selector != "" { + return C.CString(selector) + } + return nil +} diff --git a/v3/pkg/application/menuitem_test.go b/v3/pkg/application/menuitem_test.go new file mode 100644 index 000000000..b2178876f --- /dev/null +++ b/v3/pkg/application/menuitem_test.go @@ -0,0 +1,61 @@ +package application_test + +import ( + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func TestMenuItem_GetAccelerator(t *testing.T) { + tests := []struct { + name string + menuItem *application.MenuItem + expectedAcc string + }{ + { + name: "Get existing accelerator", + menuItem: application.NewMenuItem("Item 1").SetAccelerator("ctrl+a"), + expectedAcc: "Ctrl+A", + }, + { + name: "Get non-existing accelerator", + menuItem: application.NewMenuItem("Item 2"), + expectedAcc: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + acc := test.menuItem.GetAccelerator() + if acc != test.expectedAcc { + t.Errorf("Expected accelerator to be %v, but got %v", test.expectedAcc, acc) + } + }) + } +} + +func TestMenuItem_RemoveAccelerator(t *testing.T) { + tests := []struct { + name string + menuItem *application.MenuItem + }{ + { + name: "Remove existing accelerator", + menuItem: application.NewMenuItem("Item 1").SetAccelerator("Ctrl+A"), + }, + { + name: "Remove non-existing accelerator", + menuItem: application.NewMenuItem("Item 2"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.menuItem.RemoveAccelerator() + acc := test.menuItem.GetAccelerator() + if acc != "" { + t.Errorf("Expected accelerator to be removed, but got %v", acc) + } + }) + } +} diff --git a/v3/pkg/application/menuitem_windows.go b/v3/pkg/application/menuitem_windows.go new file mode 100644 index 000000000..941bf2388 --- /dev/null +++ b/v3/pkg/application/menuitem_windows.go @@ -0,0 +1,172 @@ +//go:build windows + +package application + +import ( + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/w32" +) + +type windowsMenuItem struct { + parent *Menu + menuItem *MenuItem + + hMenu w32.HMENU + id int + label string + disabled bool + checked bool + itemType menuItemType + hidden bool + submenu w32.HMENU +} + +func (m *windowsMenuItem) setHidden(hidden bool) { + if hidden && !m.hidden { + m.hidden = true + // Remove from parent menu + w32.RemoveMenu(m.hMenu, m.id, w32.MF_BYCOMMAND) + } else if !hidden && m.hidden { + m.hidden = false + // Reinsert into parent menu at correct visible position + var pos int + for _, item := range m.parent.items { + if item == m.menuItem { + break + } + if item.hidden == false { + pos++ + } + } + w32.InsertMenuItem(m.hMenu, uint32(pos), true, m.getMenuInfo()) + } +} + +func (m *windowsMenuItem) Checked() bool { + return m.checked +} + +func (m *windowsMenuItem) IsSeparator() bool { + return m.itemType == separator +} + +func (m *windowsMenuItem) IsCheckbox() bool { + return m.itemType == checkbox +} + +func (m *windowsMenuItem) IsRadio() bool { + return m.itemType == radio +} + +func (m *windowsMenuItem) Enabled() bool { + return !m.disabled +} + +func (m *windowsMenuItem) update() { + w32.SetMenuItemInfo(m.hMenu, uint32(m.id), false, m.getMenuInfo()) +} + +func (m *windowsMenuItem) setLabel(label string) { + m.label = label + m.update() +} + +func (m *windowsMenuItem) setDisabled(disabled bool) { + m.disabled = disabled + m.update() +} + +func (m *windowsMenuItem) setChecked(checked bool) { + m.checked = checked + m.update() +} + +func (m *windowsMenuItem) destroy() { + w32.RemoveMenu(m.hMenu, m.id, w32.MF_BYCOMMAND) +} + +func (m *windowsMenuItem) setAccelerator(accelerator *accelerator) { + //// Set the keyboard shortcut of the menu item + //var modifier C.int + //var key *C.char + //if accelerator != nil { + // modifier = C.int(toMacModifier(accelerator.Modifiers)) + // key = C.CString(accelerator.Key) + //} + // + //// Convert the key to a string + //C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier) +} + +func (m *windowsMenuItem) setBitmap(bitmap []byte) { + if m.menuItem.bitmap == nil { + return + } + + // Set the icon + err := w32.SetMenuIcons(m.hMenu, m.id, bitmap, nil) + if err != nil { + globalApplication.error("unable to set bitmap on menu item: %w", err) + return + } + m.update() +} + +func newMenuItemImpl(item *MenuItem, parentMenu w32.HMENU, ID int) *windowsMenuItem { + result := &windowsMenuItem{ + menuItem: item, + hMenu: parentMenu, + id: ID, + disabled: item.disabled, + checked: item.checked, + itemType: item.itemType, + label: item.label, + hidden: item.hidden, + } + + return result +} + +func (m *windowsMenuItem) setTooltip(_ string) { + // Unsupported +} + +func (m *windowsMenuItem) getMenuInfo() *w32.MENUITEMINFO { + var mii w32.MENUITEMINFO + mii.CbSize = uint32(unsafe.Sizeof(mii)) + mii.FMask = w32.MIIM_FTYPE | w32.MIIM_ID | w32.MIIM_STATE | w32.MIIM_STRING + if m.IsSeparator() { + mii.FType = w32.MFT_SEPARATOR + } else { + mii.FType = w32.MFT_STRING + if m.IsRadio() { + mii.FType |= w32.MFT_RADIOCHECK + } + thisText := m.label + if m.menuItem.accelerator != nil { + thisText += "\t" + m.menuItem.accelerator.String() + } + mii.DwTypeData = w32.MustStringToUTF16Ptr(thisText) + mii.Cch = uint32(len([]rune(thisText))) + } + mii.WID = uint32(m.id) + if m.Enabled() { + mii.FState &^= w32.MFS_DISABLED + } else { + mii.FState |= w32.MFS_DISABLED + } + + if m.IsCheckbox() || m.IsRadio() { + mii.FMask |= w32.MIIM_CHECKMARKS + } + if m.Checked() { + mii.FState |= w32.MFS_CHECKED + } + + if m.menuItem.submenu != nil { + mii.FMask |= w32.MIIM_SUBMENU + mii.HSubMenu = m.submenu + } + return &mii +} diff --git a/v3/pkg/application/messageprocessor.go b/v3/pkg/application/messageprocessor.go new file mode 100644 index 000000000..a8b845470 --- /dev/null +++ b/v3/pkg/application/messageprocessor.go @@ -0,0 +1,205 @@ +package application + +import ( + "context" + "log/slog" + "slices" + "strconv" + "sync" + + "github.com/wailsapp/wails/v3/pkg/errs" +) + +// TODO maybe we could use a new struct that has the targetWindow as an attribute so we could get rid of passing the targetWindow +// as parameter through every function call. + +const ( + callRequest = 0 + clipboardRequest = 1 + applicationRequest = 2 + eventsRequest = 3 + contextMenuRequest = 4 + dialogRequest = 5 + windowRequest = 6 + screensRequest = 7 + systemRequest = 8 + browserRequest = 9 + cancelCallRequest = 10 + iosRequest = 11 + androidRequest = 12 +) + +var objectNames = map[int]string{ + callRequest: "Call", + clipboardRequest: "Clipboard", + applicationRequest: "Application", + eventsRequest: "Events", + contextMenuRequest: "ContextMenu", + dialogRequest: "Dialog", + windowRequest: "Window", + screensRequest: "Screens", + systemRequest: "System", + browserRequest: "Browser", + cancelCallRequest: "CancelCall", + iosRequest: "iOS", + androidRequest: "Android", +} + +type RuntimeRequest struct { + // Object identifies which Wails subsystem to call (Call=0, Clipboard=1, etc.) + // See objectNames in runtime.ts + Object int `json:"object"` + + // Method identifies which method within the object to call + Method int `json:"method"` + + // Args contains the method arguments + Args *Args `json:"args"` + + // WebviewWindowName identifies the source window by name (optional, sent via header x-wails-window-name) + WebviewWindowName string `json:"webviewWindowName,omitempty"` + + // WebviewWindowID identifies the source window (optional, sent via header x-wails-window-id) + WebviewWindowID uint32 `json:"webviewWindowId,omitempty"` + + // ClientID identifies the frontend client (sent via header x-wails-client-id) + ClientID string `json:"clientId,omitempty"` +} + +type MessageProcessor struct { + logger *slog.Logger + + runningCalls map[string]context.CancelFunc + l sync.Mutex +} + +func NewMessageProcessor(logger *slog.Logger) *MessageProcessor { + return &MessageProcessor{ + logger: logger, + runningCalls: map[string]context.CancelFunc{}, + } +} + +func (m *MessageProcessor) HandleRuntimeCallWithIDs(ctx context.Context, req *RuntimeRequest) (resp any, err error) { + defer func() { + if handlePanic() { + // TODO: return panic error itself? + err = errs.NewInvalidRuntimeCallErrorf("runtime panic detected!") + } + }() + targetWindow, nameOrID := m.getTargetWindow(req) + + var windowNotRequiredRequests = []int{callRequest, eventsRequest, applicationRequest, systemRequest} + + // Some operations (calls, events, application) don't require a window + // This is useful for browser-based deployments with custom transports + windowRequired := !slices.Contains(windowNotRequiredRequests, req.Object) + if windowRequired && targetWindow == nil { + return nil, errs.NewInvalidRuntimeCallErrorf("window '%s' not found", nameOrID) + } + + m.logRuntimeCall(req) + + switch req.Object { + case windowRequest: + return m.processWindowMethod(req, targetWindow) + case clipboardRequest: + return m.processClipboardMethod(req) + case dialogRequest: + return m.processDialogMethod(req, targetWindow) + case eventsRequest: + return m.processEventsMethod(req, targetWindow) + case applicationRequest: + return m.processApplicationMethod(req) + case contextMenuRequest: + return m.processContextMenuMethod(req, targetWindow) + case screensRequest: + return m.processScreensMethod(req) + case callRequest: + return m.processCallMethod(ctx, req, targetWindow) + case systemRequest: + return m.processSystemMethod(req) + case browserRequest: + return m.processBrowserMethod(req) + case cancelCallRequest: + return m.processCallCancelMethod(req) + case iosRequest: + return m.processIOSMethod(req, targetWindow) + case androidRequest: + return m.processAndroidMethod(req, targetWindow) + default: + return nil, errs.NewInvalidRuntimeCallErrorf("unknown object %d", req.Object) + } +} + +func (m *MessageProcessor) getTargetWindow(req *RuntimeRequest) (Window, string) { + // Check for browser window first (server mode) + if req.ClientID != "" { + if browserWindow := GetBrowserWindow(req.ClientID); browserWindow != nil { + return browserWindow, browserWindow.Name() + } + } + if req.WebviewWindowName != "" { + window, _ := globalApplication.Window.GetByName(req.WebviewWindowName) + return window, req.WebviewWindowName + } + if req.WebviewWindowID == 0 { + // No window specified - return the first available window + // This is useful for custom transports that don't have automatic window context + windows := globalApplication.Window.GetAll() + if len(windows) > 0 { + return windows[0], "" + } + return nil, "" + } + targetWindow, _ := globalApplication.Window.GetByID(uint(req.WebviewWindowID)) + if targetWindow == nil { + m.Error("Window ID not found:", "id", req.WebviewWindowID) + return nil, strconv.Itoa(int(req.WebviewWindowID)) + } + return targetWindow, strconv.Itoa(int(req.WebviewWindowID)) +} + +func (m *MessageProcessor) Error(message string, args ...any) { + m.logger.Error(message, args...) +} + +func (m *MessageProcessor) Debug(message string, args ...any) { + m.logger.Debug(message, args...) +} + +func (m *MessageProcessor) logRuntimeCall(req *RuntimeRequest) { + objectName := objectNames[req.Object] + + methodName := "" + switch req.Object { + case callRequest: + return // logs done separately in call processor + case clipboardRequest: + methodName = clipboardMethods[req.Method] + case applicationRequest: + methodName = applicationMethodNames[req.Method] + case eventsRequest: + methodName = eventsMethodNames[req.Method] + case contextMenuRequest: + methodName = contextmenuMethodNames[req.Method] + case dialogRequest: + methodName = dialogMethodNames[req.Method] + case windowRequest: + methodName = windowMethodNames[req.Method] + case screensRequest: + methodName = screensMethodNames[req.Method] + case systemRequest: + methodName = systemMethodNames[req.Method] + case browserRequest: + methodName = browserMethodNames[req.Method] + case cancelCallRequest: + methodName = "Cancel" + case iosRequest: + methodName = iosMethodNames[req.Method] + case androidRequest: + methodName = androidMethodNames[req.Method] + } + + m.Debug("Runtime call:", "method", objectName+"."+methodName, "args", req.Args.String()) +} diff --git a/v3/pkg/application/messageprocessor_android.go b/v3/pkg/application/messageprocessor_android.go new file mode 100644 index 000000000..791cfa07e --- /dev/null +++ b/v3/pkg/application/messageprocessor_android.go @@ -0,0 +1,70 @@ +//go:build android + +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/errs" +) + +const ( + AndroidHapticsVibrate = 0 + AndroidDeviceInfo = 1 + AndroidToast = 2 +) + +var androidMethodNames = map[int]string{ + AndroidHapticsVibrate: "Haptics.Vibrate", + AndroidDeviceInfo: "Device.Info", + AndroidToast: "Toast.Show", +} + +func (m *MessageProcessor) processAndroidMethod(req *RuntimeRequest, window Window) (any, error) { + args := req.Args.AsMap() + + switch req.Method { + case AndroidHapticsVibrate: + duration := 100 // default 100ms + if d := args.Int("duration"); d != nil { + duration = *d + } + androidHapticsVibrate(duration) + return unit, nil + case AndroidDeviceInfo: + return androidDeviceInfo(), nil + case AndroidToast: + message := "" + if s := args.String("message"); s != nil { + message = *s + } + androidShowToast(message) + return unit, nil + default: + return nil, errs.NewInvalidAndroidCallErrorf("unknown method: %d", req.Method) + } +} + +// processIOSMethod is a stub on Android +func (m *MessageProcessor) processIOSMethod(req *RuntimeRequest, window Window) (any, error) { + return nil, errs.NewInvalidIOSCallErrorf("iOS methods not available on Android") +} + +// Android-specific runtime functions (stubs for now) + +func androidHapticsVibrate(durationMs int) { + // TODO: Implement via JNI to Android Vibrator service + androidLogf("debug", "Haptics vibrate: %dms", durationMs) +} + +func androidDeviceInfo() map[string]interface{} { + // TODO: Implement via JNI to get actual device info + return map[string]interface{}{ + "platform": "android", + "model": "Unknown", + "version": "Unknown", + } +} + +func androidShowToast(message string) { + // TODO: Implement via JNI to Android Toast + androidLogf("debug", "Toast: %s", message) +} diff --git a/v3/pkg/application/messageprocessor_application.go b/v3/pkg/application/messageprocessor_application.go new file mode 100644 index 000000000..18dc99850 --- /dev/null +++ b/v3/pkg/application/messageprocessor_application.go @@ -0,0 +1,35 @@ +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/errs" +) + +const ( + ApplicationHide = 0 + ApplicationShow = 1 + ApplicationQuit = 2 +) + +var applicationMethodNames = map[int]string{ + ApplicationQuit: "Quit", + ApplicationHide: "Hide", + ApplicationShow: "Show", +} + +func (m *MessageProcessor) processApplicationMethod( + req *RuntimeRequest, +) (any, error) { + switch req.Method { + case ApplicationQuit: + globalApplication.Quit() + return unit, nil + case ApplicationHide: + globalApplication.Hide() + return unit, nil + case ApplicationShow: + globalApplication.Show() + return unit, nil + default: + return nil, errs.NewInvalidApplicationCallErrorf("unknown method %d", req.Method) + } +} diff --git a/v3/pkg/application/messageprocessor_args.go b/v3/pkg/application/messageprocessor_args.go new file mode 100644 index 000000000..bb3ffc6d0 --- /dev/null +++ b/v3/pkg/application/messageprocessor_args.go @@ -0,0 +1,124 @@ +package application + +import ( + "fmt" + + "encoding/json" +) + +type Args struct { + rawData json.RawMessage +} + +func (a *Args) UnmarshalJSON(data []byte) error { + a.rawData = data + + return nil +} + +func (a *Args) String() string { return string(a.rawData) } + +func (a *Args) AsMap() *MapArgs { + m := make(map[string]interface{}) + if a.rawData == nil { + return &MapArgs{ + data: m, + } + } + err := json.Unmarshal(a.rawData, &m) + if err != nil { + return &MapArgs{ + data: m, + } + } + return &MapArgs{data: m} +} + +func (a *Args) ToStruct(str any) error { + return json.Unmarshal(a.rawData, str) +} + +type MapArgs struct { + data map[string]interface{} +} + +func (a *MapArgs) String(key string) *string { + if a == nil { + return nil + } + if val := a.data[key]; val != nil { + result := fmt.Sprintf("%v", val) + return &result + } + return nil +} + +func (a *MapArgs) Int(s string) *int { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + return convertNumber[int](val) + } + return nil +} + +func convertNumber[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](val any) *T { + if val == nil { + return nil + } + var result T + switch v := val.(type) { + case T: + result = v + case float64: + result = T(v) + default: + return nil + } + return &result +} + +func (a *MapArgs) UInt8(s string) *uint8 { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + return convertNumber[uint8](val) + } + return nil +} + +func (a *MapArgs) UInt(s string) *uint { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + return convertNumber[uint](val) + } + return nil +} + +func (a *MapArgs) Float64(s string) *float64 { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + if result, ok := val.(float64); ok { + return &result + } + } + return nil +} + +func (a *MapArgs) Bool(s string) *bool { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + if result, ok := val.(bool); ok { + return &result + } + } + return nil +} diff --git a/v3/pkg/application/messageprocessor_browser.go b/v3/pkg/application/messageprocessor_browser.go new file mode 100644 index 000000000..cd2ea7ac7 --- /dev/null +++ b/v3/pkg/application/messageprocessor_browser.go @@ -0,0 +1,39 @@ +package application + +import ( + "github.com/pkg/browser" + "github.com/wailsapp/wails/v3/pkg/errs" +) + +const ( + BrowserOpenURL = 0 +) + +var browserMethodNames = map[int]string{ + BrowserOpenURL: "OpenURL", +} + +func (m *MessageProcessor) processBrowserMethod(req *RuntimeRequest) (any, error) { + switch req.Method { + case BrowserOpenURL: + url := req.Args.AsMap().String("url") + if url == nil { + return nil, errs.NewInvalidBrowserCallErrorf("missing argument 'url'") + } + + sanitizedURL, err := ValidateAndSanitizeURL(*url) + if err != nil { + return nil, errs.WrapInvalidBrowserCallErrorf(err, "invalid URL") + } + + err = browser.OpenURL(sanitizedURL) + if err != nil { + m.Error("OpenURL: invalid URL - %s", err.Error()) + return nil, errs.WrapInvalidBrowserCallErrorf(err, "OpenURL failed") + } + + return unit, nil + default: + return nil, errs.NewInvalidBrowserCallErrorf("unknown method: %d", req.Method) + } +} diff --git a/v3/pkg/application/messageprocessor_call.go b/v3/pkg/application/messageprocessor_call.go new file mode 100644 index 000000000..71f60a00e --- /dev/null +++ b/v3/pkg/application/messageprocessor_call.go @@ -0,0 +1,137 @@ +package application + +import ( + "context" + "errors" + + "encoding/json" + + "github.com/wailsapp/wails/v3/pkg/errs" +) + +type contextKey string + +const ( + CallBinding = 0 + WindowKey contextKey = "Window" +) + +func (m *MessageProcessor) processCallCancelMethod(req *RuntimeRequest) (any, error) { + callID := req.Args.AsMap().String("call-id") + if callID == nil || *callID == "" { + return nil, errs.NewInvalidBindingCallErrorf("missing argument 'call-id'") + } + + var cancel func() + func() { + m.l.Lock() + defer m.l.Unlock() + cancel = m.runningCalls[*callID] + }() + + if cancel != nil { + cancel() + m.Debug("Binding call cancelled:", "id", *callID) + } + return unit, nil +} + +func (m *MessageProcessor) processCallMethod(ctx context.Context, req *RuntimeRequest, window Window) (any, error) { + callID := req.Args.AsMap().String("call-id") + if callID == nil || *callID == "" { + return nil, errs.NewInvalidBindingCallErrorf("missing argument 'call-id'") + } + + switch req.Method { + case CallBinding: + var options CallOptions + err := req.Args.ToStruct(&options) + if err != nil { + return nil, errs.WrapInvalidBindingCallErrorf(err, "error parsing call options") + } + + // Log call + var methodRef any = options.MethodName + if options.MethodName == "" { + methodRef = options.MethodID + } + m.Debug("Binding call started:", "id", *callID, "method", methodRef) + + ctx, cancel := context.WithCancel(context.WithoutCancel(ctx)) + + // Schedule cancel in case panics happen before starting the call. + cancelRequired := true + defer func() { + if cancelRequired { + cancel() + } + }() + + ambiguousID := false + func() { + m.l.Lock() + defer m.l.Unlock() + + if m.runningCalls[*callID] != nil { + ambiguousID = true + } else { + m.runningCalls[*callID] = cancel + } + }() + + if ambiguousID { + return nil, errs.NewInvalidBindingCallErrorf("ambiguous call id: %s", *callID) + } + + defer func() { + m.l.Lock() + defer m.l.Unlock() + delete(m.runningCalls, *callID) + }() + defer cancel() + + var boundMethod *BoundMethod + if options.MethodName != "" { + boundMethod = globalApplication.bindings.Get(&options) + if boundMethod == nil { + return nil, errs.NewBindingCallFailedErrorf("unknown bound method name '%s'", options.MethodName) + } + } else { + boundMethod = globalApplication.bindings.GetByID(options.MethodID) + if boundMethod == nil { + return nil, errs.NewBindingCallFailedErrorf("unknown bound method id %d", options.MethodID) + } + } + + jsonArgs, _ := json.Marshal(options.Args) + var result any + defer func() { + var jsonResult []byte + jsonResult, _ = json.Marshal(result) + m.Debug("Binding call complete:", "id", *callID, "method", boundMethod, "args", string(jsonArgs), "result", string(jsonResult)) + }() + + // Set the context values for the window + if window != nil { + ctx = context.WithValue(ctx, WindowKey, window) + } + + result, err = boundMethod.Call(ctx, options.Args) + if cerr := (*CallError)(nil); errors.As(err, &cerr) { + switch cerr.Kind { + case ReferenceError, TypeError: + return nil, errs.WrapBindingCallFailedErrorf(cerr, "failed to call binding") + case RuntimeError: + + return nil, errs.WrapBindingCallFailedErrorf(cerr, "Bound method returned an error") + } + } + if err != nil { + return nil, errs.WrapBindingCallFailedErrorf(err, "failed to call binding") + } + return result, nil + + default: + return nil, errs.NewInvalidBindingCallErrorf("unknown method: %d", req.Method) + } +} diff --git a/v3/pkg/application/messageprocessor_clipboard.go b/v3/pkg/application/messageprocessor_clipboard.go new file mode 100644 index 000000000..27eabe817 --- /dev/null +++ b/v3/pkg/application/messageprocessor_clipboard.go @@ -0,0 +1,37 @@ +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/errs" +) + +const ( + ClipboardSetText = 0 + ClipboardText = 1 +) + +var clipboardMethods = map[int]string{ + ClipboardSetText: "SetText", + ClipboardText: "Text", +} + +func (m *MessageProcessor) processClipboardMethod(req *RuntimeRequest) (any, error) { + args := req.Args.AsMap() + + var text string + + switch req.Method { + case ClipboardSetText: + textp := args.String("text") + if textp == nil { + return nil, errs.NewInvalidClipboardCallErrorf("missing argument 'text'") + } + text = *textp + globalApplication.Clipboard.SetText(text) + return unit, nil + case ClipboardText: + text, _ = globalApplication.Clipboard.Text() + return text, nil + default: + return nil, errs.NewInvalidClipboardCallErrorf("unknown method: %d", req.Method) + } +} diff --git a/v3/pkg/application/messageprocessor_contextmenu.go b/v3/pkg/application/messageprocessor_contextmenu.go new file mode 100644 index 000000000..9529b34e4 --- /dev/null +++ b/v3/pkg/application/messageprocessor_contextmenu.go @@ -0,0 +1,46 @@ +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/errs" +) + +type ContextMenuData struct { + Id string `json:"id"` + X int `json:"x"` + Y int `json:"y"` + Data string `json:"data"` +} + +func (d ContextMenuData) clone() *ContextMenuData { + return &ContextMenuData{ + Id: d.Id, + X: d.X, + Y: d.Y, + Data: d.Data, + } +} + +const ( + ContextMenuOpen = 0 +) + +var contextmenuMethodNames = map[int]string{ + ContextMenuOpen: "Open", +} + +func (m *MessageProcessor) processContextMenuMethod(req *RuntimeRequest, window Window) (any, error) { + switch req.Method { + case ContextMenuOpen: + var data ContextMenuData + err := req.Args.ToStruct(&data) + if err != nil { + return nil, errs.WrapInvalidContextMenuCallErrorf(err, "error parsing parameters") + } + + window.OpenContextMenu(&data) + + return unit, err + default: + return nil, errs.NewInvalidContextMenuCallErrorf("unknown method: %d", req.Method) + } +} diff --git a/v3/pkg/application/messageprocessor_dialog.go b/v3/pkg/application/messageprocessor_dialog.go new file mode 100644 index 000000000..097bd7d7f --- /dev/null +++ b/v3/pkg/application/messageprocessor_dialog.go @@ -0,0 +1,126 @@ +package application + +import ( + "runtime" + + "github.com/wailsapp/wails/v3/pkg/errs" +) + +const ( + DialogInfo = 0 + DialogWarning = 1 + DialogError = 2 + DialogQuestion = 3 + DialogOpenFile = 4 + DialogSaveFile = 5 +) + +var dialogMethodNames = map[int]string{ + DialogInfo: "Info", + DialogWarning: "Warning", + DialogError: "Error", + DialogQuestion: "Question", + DialogOpenFile: "OpenFile", + DialogSaveFile: "SaveFile", +} + +func (m *MessageProcessor) processDialogMethod(req *RuntimeRequest, window Window) (any, error) { + args := req.Args.AsMap() + + switch req.Method { + case DialogInfo, DialogWarning, DialogError, DialogQuestion: + var options MessageDialogOptions + err := req.Args.ToStruct(&options) + if err != nil { + return nil, errs.WrapInvalidDialogCallErrorf(err, "error parsing dialog options") + } + if len(options.Buttons) == 0 { + switch runtime.GOOS { + case "darwin": + options.Buttons = []*Button{{Label: "OK", IsDefault: true}} + } + } + var dialog *MessageDialog + switch req.Method { + case DialogInfo: + dialog = newMessageDialog(InfoDialogType) + case DialogWarning: + dialog = newMessageDialog(WarningDialogType) + case DialogError: + dialog = newMessageDialog(ErrorDialogType) + case DialogQuestion: + dialog = newMessageDialog(QuestionDialogType) + } + var detached = args.Bool("Detached") + if detached == nil || !*detached { + dialog.AttachToWindow(window) + } + + dialog.SetTitle(options.Title) + dialog.SetMessage(options.Message) + + resp := make(chan string, 1) + for _, button := range options.Buttons { + label := button.Label + button.OnClick(func() { + select { + case resp <- label: + default: + } + }) + } + dialog.AddButtons(options.Buttons) + dialog.Show() + + response := <-resp + return response, nil + + case DialogOpenFile: + var options OpenFileDialogOptions + err := req.Args.ToStruct(&options) + if err != nil { + return nil, errs.WrapInvalidDialogCallErrorf(err, "error parsing dialog options") + } + var detached = args.Bool("Detached") + if detached == nil || !*detached { + options.Window = window + } + dialog := globalApplication.Dialog.OpenFileWithOptions(&options) + + if options.AllowsMultipleSelection { + files, err := dialog.PromptForMultipleSelection() + if err != nil { + return nil, errs.WrapInvalidDialogCallErrorf(err, "Dialog.OpenFile failed: error getting selection") + } + + return files, nil + } else { + file, err := dialog.PromptForSingleSelection() + if err != nil { + return nil, errs.WrapInvalidDialogCallErrorf(err, "Dialog.OpenFile failed, error getting selection") + } + return file, nil + } + + case DialogSaveFile: + var options SaveFileDialogOptions + err := req.Args.ToStruct(&options) + if err != nil { + return nil, errs.WrapInvalidDialogCallErrorf(err, "error parsing dialog options") + } + var detached = args.Bool("Detached") + if detached == nil || !*detached { + options.Window = window + } + dialog := globalApplication.Dialog.SaveFileWithOptions(&options) + + file, err := dialog.PromptForSingleSelection() + if err != nil { + return nil, errs.WrapInvalidDialogCallErrorf(err, "Dialog.SaveFile failed: error getting selection") + } + return file, nil + + default: + return nil, errs.NewInvalidDialogCallErrorf("unknown method: %d", req.Method) + } +} diff --git a/v3/pkg/application/messageprocessor_events.go b/v3/pkg/application/messageprocessor_events.go new file mode 100644 index 000000000..ea4e5882e --- /dev/null +++ b/v3/pkg/application/messageprocessor_events.go @@ -0,0 +1,50 @@ +package application + +import ( + "encoding/json" + + "github.com/wailsapp/wails/v3/pkg/errs" +) + +const ( + EventsEmit = 0 +) + +var eventsMethodNames = map[int]string{ + EventsEmit: "Emit", +} + +func (m *MessageProcessor) processEventsMethod(req *RuntimeRequest, window Window) (any, error) { + switch req.Method { + case EventsEmit: + var event CustomEvent + var options struct { + Name *string `json:"name"` + Data json.RawMessage `json:"data"` + } + + err := req.Args.ToStruct(&options) + if err != nil { + return nil, errs.WrapInvalidEventsCallErrorf(err, "error parsing event") + } + if options.Name == nil { + return nil, errs.NewInvalidEventsCallErrorf("missing event name") + } + + data, err := decodeEventData(*options.Name, options.Data) + if err != nil { + return nil, errs.WrapInvalidEventsCallErrorf(err, "error parsing event data") + } + + event.Name = *options.Name + event.Data = data + if window != nil { + event.Sender = window.Name() + } + globalApplication.Event.EmitEvent(&event) + + return event.IsCancelled(), nil + default: + return nil, errs.NewInvalidEventsCallErrorf("unknown method: %d", req.Method) + } +} diff --git a/v3/pkg/application/messageprocessor_ios.go b/v3/pkg/application/messageprocessor_ios.go new file mode 100644 index 000000000..dbc4ad187 --- /dev/null +++ b/v3/pkg/application/messageprocessor_ios.go @@ -0,0 +1,105 @@ +//go:build ios + +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/errs" +) + +const ( + IOSHapticsImpact = 0 + IOSDeviceInfo = 1 + IOSScrollSetEnabled = 2 + IOSScrollSetBounceEnabled = 3 + IOSScrollSetIndicatorsEnabled = 4 + IOSNavigationSetBackForwardGestures = 5 + IOSLinksSetPreviewEnabled = 6 + IOSDebugSetInspectableEnabled = 7 + IOSUserAgentSet = 8 +) + +var iosMethodNames = map[int]string{ + IOSHapticsImpact: "Haptics.Impact", + IOSDeviceInfo: "Device.Info", + IOSScrollSetEnabled: "Scroll.SetEnabled", + IOSScrollSetBounceEnabled: "Scroll.SetBounceEnabled", + IOSScrollSetIndicatorsEnabled: "Scroll.SetIndicatorsEnabled", + IOSNavigationSetBackForwardGestures: "Navigation.SetBackForwardGesturesEnabled", + IOSLinksSetPreviewEnabled: "Links.SetPreviewEnabled", + IOSDebugSetInspectableEnabled: "Debug.SetInspectableEnabled", + IOSUserAgentSet: "UserAgent.Set", +} + +func (m *MessageProcessor) processIOSMethod(req *RuntimeRequest, window Window) (any, error) { + args := req.Args.AsMap() + + switch req.Method { + case IOSHapticsImpact: + style := "medium" + if s := args.String("style"); s != nil { + style = *s + } + iosHapticsImpact(style) + return unit, nil + case IOSDeviceInfo: + return iosDeviceInfo(), nil + case IOSScrollSetEnabled: + enabled := true + if b := args.Bool("enabled"); b != nil { + enabled = *b + } + iosSetScrollEnabled(enabled) + return unit, nil + case IOSScrollSetBounceEnabled: + enabled := true + if b := args.Bool("enabled"); b != nil { + enabled = *b + } + iosSetBounceEnabled(enabled) + return unit, nil + case IOSScrollSetIndicatorsEnabled: + enabled := true + if b := args.Bool("enabled"); b != nil { + enabled = *b + } + iosSetScrollIndicatorsEnabled(enabled) + return unit, nil + case IOSNavigationSetBackForwardGestures: + enabled := false + if b := args.Bool("enabled"); b != nil { + enabled = *b + } + iosSetBackForwardGesturesEnabled(enabled) + return unit, nil + case IOSLinksSetPreviewEnabled: + enabled := true + if b := args.Bool("enabled"); b != nil { + enabled = *b + } + iosSetLinkPreviewEnabled(enabled) + return unit, nil + case IOSDebugSetInspectableEnabled: + enabled := true + if b := args.Bool("enabled"); b != nil { + enabled = *b + } + iosSetInspectableEnabled(enabled) + return unit, nil + case IOSUserAgentSet: + ua := "" + if s := args.String("ua"); s != nil { + ua = *s + } else if s2 := args.String("userAgent"); s2 != nil { + ua = *s2 + } + iosSetCustomUserAgent(ua) + return unit, nil + default: + return nil, errs.NewInvalidIOSCallErrorf("unknown method: %d", req.Method) + } +} + +// processAndroidMethod is a stub on iOS +func (m *MessageProcessor) processAndroidMethod(req *RuntimeRequest, window Window) (any, error) { + return nil, errs.NewInvalidAndroidCallErrorf("Android methods not available on iOS") +} diff --git a/v3/pkg/application/messageprocessor_mobile_stub.go b/v3/pkg/application/messageprocessor_mobile_stub.go new file mode 100644 index 000000000..d59a26694 --- /dev/null +++ b/v3/pkg/application/messageprocessor_mobile_stub.go @@ -0,0 +1,21 @@ +//go:build !ios && !android + +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/errs" +) + +// Empty method name maps for logging on non-mobile platforms +var iosMethodNames = map[int]string{} +var androidMethodNames = map[int]string{} + +// processIOSMethod is a stub for non-mobile platforms +func (m *MessageProcessor) processIOSMethod(req *RuntimeRequest, window Window) (any, error) { + return nil, errs.NewInvalidIOSCallErrorf("iOS methods not available on this platform") +} + +// processAndroidMethod is a stub for non-mobile platforms +func (m *MessageProcessor) processAndroidMethod(req *RuntimeRequest, window Window) (any, error) { + return nil, errs.NewInvalidAndroidCallErrorf("Android methods not available on this platform") +} diff --git a/v3/pkg/application/messageprocessor_screens.go b/v3/pkg/application/messageprocessor_screens.go new file mode 100644 index 000000000..6fbadad12 --- /dev/null +++ b/v3/pkg/application/messageprocessor_screens.go @@ -0,0 +1,34 @@ +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/errs" +) + +const ( + ScreensGetAll = 0 + ScreensGetPrimary = 1 + ScreensGetCurrent = 2 +) + +var screensMethodNames = map[int]string{ + ScreensGetAll: "GetAll", + ScreensGetPrimary: "GetPrimary", + ScreensGetCurrent: "GetCurrent", +} + +func (m *MessageProcessor) processScreensMethod(req *RuntimeRequest) (any, error) { + switch req.Method { + case ScreensGetAll: + return globalApplication.Screen.GetAll(), nil + case ScreensGetPrimary: + return globalApplication.Screen.GetPrimary(), nil + case ScreensGetCurrent: + screen, err := globalApplication.Window.Current().GetScreen() + if err != nil { + return nil, errs.WrapInvalidScreensCallErrorf(err, "Window.GetScreen failed") + } + return screen, nil + default: + return nil, errs.NewInvalidScreensCallErrorf("Unknown method: %d", req.Method) + } +} diff --git a/v3/pkg/application/messageprocessor_system.go b/v3/pkg/application/messageprocessor_system.go new file mode 100644 index 000000000..5e2b004df --- /dev/null +++ b/v3/pkg/application/messageprocessor_system.go @@ -0,0 +1,35 @@ +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/errs" +) + +const ( + SystemIsDarkMode = 0 + Environment = 1 + Capabilities = 2 + Flags = 3 +) + +var systemMethodNames = map[int]string{ + SystemIsDarkMode: "IsDarkMode", + Environment: "Environment", + Capabilities: "Capabilities", + Flags: "Flags", +} + +func (m *MessageProcessor) processSystemMethod(req *RuntimeRequest) (any, error) { + switch req.Method { + case SystemIsDarkMode: + return globalApplication.Env.IsDarkMode(), nil + case Environment: + return globalApplication.Env.Info(), nil + case Capabilities: + return globalApplication.capabilities, nil + case Flags: + flags := globalApplication.impl.GetFlags(globalApplication.options) + return flags, nil + default: + return nil, errs.NewInvalidSystemCallErrorf("unknown method: %d", req.Method) + } +} diff --git a/v3/pkg/application/messageprocessor_window.go b/v3/pkg/application/messageprocessor_window.go new file mode 100644 index 000000000..1baf57606 --- /dev/null +++ b/v3/pkg/application/messageprocessor_window.go @@ -0,0 +1,415 @@ +package application + +import ( + "fmt" + + "github.com/wailsapp/wails/v3/pkg/errs" +) + +const ( + WindowPosition = 0 + WindowCenter = 1 + WindowClose = 2 + WindowDisableSizeConstraints = 3 + WindowEnableSizeConstraints = 4 + WindowFocus = 5 + WindowForceReload = 6 + WindowFullscreen = 7 + WindowGetScreen = 8 + WindowGetZoom = 9 + WindowHeight = 10 + WindowHide = 11 + WindowIsFocused = 12 + WindowIsFullscreen = 13 + WindowIsMaximised = 14 + WindowIsMinimised = 15 + WindowMaximise = 16 + WindowMinimise = 17 + WindowName = 18 + WindowOpenDevTools = 19 + WindowRelativePosition = 20 + WindowReload = 21 + WindowResizable = 22 + WindowRestore = 23 + WindowSetPosition = 24 + WindowSetAlwaysOnTop = 25 + WindowSetBackgroundColour = 26 + WindowSetFrameless = 27 + WindowSetFullscreenButtonEnabled = 28 + WindowSetMaxSize = 29 + WindowSetMinSize = 30 + WindowSetRelativePosition = 31 + WindowSetResizable = 32 + WindowSetSize = 33 + WindowSetTitle = 34 + WindowSetZoom = 35 + WindowShow = 36 + WindowSize = 37 + WindowToggleFullscreen = 38 + WindowToggleMaximise = 39 + WindowToggleFrameless = 40 + WindowUnFullscreen = 41 + WindowUnMaximise = 42 + WindowUnMinimise = 43 + WindowWidth = 44 + WindowZoom = 45 + WindowZoomIn = 46 + WindowZoomOut = 47 + WindowZoomReset = 48 + WindowSnapAssist = 49 + WindowFilesDropped = 50 + WindowPrint = 51 +) + +var windowMethodNames = map[int]string{ + WindowPosition: "Position", + WindowCenter: "Center", + WindowClose: "Close", + WindowDisableSizeConstraints: "DisableSizeConstraints", + WindowEnableSizeConstraints: "EnableSizeConstraints", + WindowFocus: "Focus", + WindowForceReload: "ForceReload", + WindowFullscreen: "Fullscreen", + WindowGetScreen: "GetScreen", + WindowGetZoom: "GetZoom", + WindowHeight: "Height", + WindowHide: "Hide", + WindowIsFocused: "IsFocused", + WindowIsFullscreen: "IsFullscreen", + WindowIsMaximised: "IsMaximised", + WindowIsMinimised: "IsMinimised", + WindowMaximise: "Maximise", + WindowMinimise: "Minimise", + WindowName: "Name", + WindowOpenDevTools: "OpenDevTools", + WindowRelativePosition: "RelativePosition", + WindowReload: "Reload", + WindowResizable: "Resizable", + WindowRestore: "Restore", + WindowSetPosition: "SetPosition", + WindowSetAlwaysOnTop: "SetAlwaysOnTop", + WindowSetBackgroundColour: "SetBackgroundColour", + WindowSetFrameless: "SetFrameless", + WindowSetFullscreenButtonEnabled: "SetFullscreenButtonEnabled", + WindowSetMaxSize: "SetMaxSize", + WindowSetMinSize: "SetMinSize", + WindowSetRelativePosition: "SetRelativePosition", + WindowSetResizable: "SetResizable", + WindowSetSize: "SetSize", + WindowSetTitle: "SetTitle", + WindowSetZoom: "SetZoom", + WindowShow: "Show", + WindowSize: "Size", + WindowToggleFullscreen: "ToggleFullscreen", + WindowToggleMaximise: "ToggleMaximise", + WindowToggleFrameless: "ToggleFrameless", + WindowUnFullscreen: "UnFullscreen", + WindowUnMaximise: "UnMaximise", + WindowUnMinimise: "UnMinimise", + WindowWidth: "Width", + WindowZoom: "Zoom", + WindowZoomIn: "ZoomIn", + WindowZoomOut: "ZoomOut", + WindowZoomReset: "ZoomReset", + WindowFilesDropped: "FilesDropped", + WindowSnapAssist: "SnapAssist", + WindowPrint: "Print", +} + +var unit = struct{}{} + +func (m *MessageProcessor) processWindowMethod( + req *RuntimeRequest, + window Window, +) (any, error) { + args := req.Args.AsMap() + + switch req.Method { + case WindowPosition: + x, y := window.Position() + return map[string]interface{}{ + "x": x, + "y": y, + }, nil + case WindowCenter: + window.Center() + return unit, nil + case WindowClose: + window.Close() + return unit, nil + case WindowDisableSizeConstraints: + window.DisableSizeConstraints() + return unit, nil + case WindowEnableSizeConstraints: + window.EnableSizeConstraints() + return unit, nil + case WindowFocus: + window.Focus() + return unit, nil + case WindowForceReload: + window.ForceReload() + return unit, nil + case WindowFullscreen: + window.Fullscreen() + return unit, nil + case WindowGetScreen: + screen, err := window.GetScreen() + if err != nil { + return nil, fmt.Errorf("Window.GetScreen failed: %w", err) + } + return screen, nil + case WindowGetZoom: + return window.GetZoom(), nil + case WindowHeight: + return window.Height(), nil + case WindowHide: + window.Hide() + return unit, nil + case WindowIsFocused: + return window.IsFocused(), nil + case WindowIsFullscreen: + return window.IsFullscreen(), nil + case WindowIsMaximised: + return window.IsMaximised(), nil + case WindowIsMinimised: + return window.IsMinimised(), nil + case WindowMaximise: + window.Maximise() + return unit, nil + case WindowMinimise: + window.Minimise() + return unit, nil + case WindowName: + return window.Name(), nil + case WindowRelativePosition: + x, y := window.RelativePosition() + return map[string]interface{}{ + "x": x, + "y": y, + }, nil + case WindowReload: + window.Reload() + return unit, nil + case WindowResizable: + return window.Resizable(), nil + case WindowRestore: + window.Restore() + return unit, nil + case WindowSetPosition: + x := args.Int("x") + if x == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'x'") + } + y := args.Int("y") + if y == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'y'") + } + window.SetPosition(*x, *y) + return unit, nil + case WindowSetAlwaysOnTop: + alwaysOnTop := args.Bool("alwaysOnTop") + if alwaysOnTop == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'alwaysOnTop'") + } + window.SetAlwaysOnTop(*alwaysOnTop) + return unit, nil + case WindowSetBackgroundColour: + r := args.UInt8("r") + if r == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'r'") + } + g := args.UInt8("g") + if g == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'g'") + } + b := args.UInt8("b") + if b == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'b'") + } + a := args.UInt8("a") + if a == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'a'") + } + window.SetBackgroundColour(RGBA{ + Red: *r, + Green: *g, + Blue: *b, + Alpha: *a, + }) + return unit, nil + case WindowSetFrameless: + frameless := args.Bool("frameless") + if frameless == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'frameless'") + } + window.SetFrameless(*frameless) + return unit, nil + case WindowSetMaxSize: + width := args.Int("width") + if width == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'width'") + } + height := args.Int("height") + if height == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'height'") + } + window.SetMaxSize(*width, *height) + return unit, nil + case WindowSetMinSize: + width := args.Int("width") + if width == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'width'") + } + height := args.Int("height") + if height == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'height'") + } + window.SetMinSize(*width, *height) + return unit, nil + case WindowSetRelativePosition: + x := args.Int("x") + if x == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'x'") + } + y := args.Int("y") + if y == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'y'") + } + window.SetRelativePosition(*x, *y) + return unit, nil + case WindowSetResizable: + resizable := args.Bool("resizable") + if resizable == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'resizable'") + } + window.SetResizable(*resizable) + return unit, nil + case WindowSetSize: + width := args.Int("width") + if width == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'width'") + } + height := args.Int("height") + if height == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'height'") + } + window.SetSize(*width, *height) + return unit, nil + case WindowSetTitle: + title := args.String("title") + if title == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'title'") + } + window.SetTitle(*title) + return unit, nil + case WindowSetZoom: + zoom := args.Float64("zoom") + if zoom == nil { + return nil, errs.NewInvalidWindowCallErrorf("missing or invalid argument 'zoom'") + } + window.SetZoom(*zoom) + return unit, nil + case WindowShow: + window.Show() + return unit, nil + case WindowSize: + width, height := window.Size() + return map[string]interface{}{ + "width": width, + "height": height, + }, nil + case WindowOpenDevTools: + window.OpenDevTools() + return unit, nil + case WindowToggleFullscreen: + window.ToggleFullscreen() + return unit, nil + case WindowToggleMaximise: + window.ToggleMaximise() + return unit, nil + case WindowToggleFrameless: + window.ToggleFrameless() + return unit, nil + case WindowUnFullscreen: + window.UnFullscreen() + return unit, nil + case WindowUnMaximise: + window.UnMaximise() + return unit, nil + case WindowUnMinimise: + window.UnMinimise() + return unit, nil + case WindowWidth: + return window.Width(), nil + case WindowZoom: + window.Zoom() + return unit, nil + case WindowZoomIn: + window.ZoomIn() + return unit, nil + case WindowZoomOut: + window.ZoomOut() + return unit, nil + case WindowZoomReset: + window.ZoomReset() + return unit, nil + case WindowFilesDropped: + var payload fileDropPayload + err := req.Args.ToStruct(&payload) + if err != nil { + return nil, errs.WrapInvalidWindowCallErrorf(err, "error decoding file drop payload") + } + m.Debug( + "[DragDropDebug] processWindowMethod: Decoded payload from 'args'", + "payload", + fmt.Sprintf("%+v", payload), + ) + + dropTarget := &DropTargetDetails{ + X: payload.X, + Y: payload.Y, + ElementID: payload.ElementDetails.ID, + ClassList: payload.ElementDetails.ClassList, + Attributes: payload.ElementDetails.Attributes, + } + + wvWindow, ok := window.(*WebviewWindow) + if !ok { + return nil, errs.NewInvalidWindowCallErrorf("target window is not a WebviewWindow") + } + + msg := &dragAndDropMessage{ + windowId: wvWindow.id, + filenames: payload.Filenames, + DropTarget: dropTarget, + } + windowDragAndDropBuffer <- msg + return unit, nil + case WindowSnapAssist: + window.SnapAssist() + return unit, nil + case WindowPrint: + err := window.Print() + if err != nil { + return nil, fmt.Errorf("Window.Print failed: %w", err) + } + return unit, nil + default: + return nil, errs.NewInvalidWindowCallErrorf("Unknown method %d", req.Method) + } +} + +// ElementDetailsPayload holds detailed information about the drop target element. +type ElementDetailsPayload struct { + ID string `json:"id"` + ClassList []string `json:"classList"` + Attributes map[string]string `json:"attributes"` +} + +// Define a struct for the JSON payload from HandlePlatformFileDrop +type fileDropPayload struct { + Filenames []string `json:"filenames"` + X int `json:"x"` + Y int `json:"y"` + ElementDetails ElementDetailsPayload `json:"elementDetails"` +} diff --git a/v3/pkg/application/panic_handler.go b/v3/pkg/application/panic_handler.go new file mode 100644 index 000000000..53f42a309 --- /dev/null +++ b/v3/pkg/application/panic_handler.go @@ -0,0 +1,104 @@ +package application + +import ( + "fmt" + "runtime" + "runtime/debug" + "strings" + "time" +) + +func getStackTrace(skipStart int, skipEnd int) string { + // Get all program counters first + pc := make([]uintptr, 32) + n := runtime.Callers(skipStart+1, pc) + if n == 0 { + return "" + } + + pc = pc[:n] + frames := runtime.CallersFrames(pc) + + // Collect all frames first + var allFrames []runtime.Frame + for { + frame, more := frames.Next() + allFrames = append(allFrames, frame) + if !more { + break + } + } + + // Remove frames from the end + if len(allFrames) > skipEnd { + allFrames = allFrames[:len(allFrames)-skipEnd] + } + + // Build the output string + var builder strings.Builder + for _, frame := range allFrames { + fmt.Fprintf(&builder, "%s\n\tat %s:%d\n", + frame.Function, frame.File, frame.Line) + } + return builder.String() +} + +type handlePanicOptions struct { + skipEnd int +} + +type PanicDetails struct { + StackTrace string + Error error + Time time.Time + FullStackTrace string +} + +func newPanicDetails(err error, trace string) *PanicDetails { + return &PanicDetails{ + Error: err, + Time: time.Now(), + StackTrace: trace, + FullStackTrace: string(debug.Stack()), + } +} + +// handlePanic handles any panics +// Returns the error if there was one +func handlePanic(options ...handlePanicOptions) bool { + // Try to recover + e := recover() + if e == nil { + return false + } + + // Get the error + err, ok := e.(error) + if !ok { + err = fmt.Errorf("%v", e) + } + + // Get the stack trace + var stackTrace string + skipEnd := 0 + if len(options) > 0 { + skipEnd = options[0].skipEnd + } + stackTrace = getStackTrace(3, skipEnd) + + processPanic(newPanicDetails(err, stackTrace)) + return false +} + +func processPanic(panicDetails *PanicDetails) { + h := globalApplication.options.PanicHandler + if h != nil { + h(panicDetails) + return + } + defaultPanicHandler(panicDetails) +} + +func defaultPanicHandler(panicDetails *PanicDetails) { + globalApplication.fatal("panic error: %w\n%s", panicDetails.Error, panicDetails.StackTrace) +} diff --git a/v3/pkg/application/parameter_test.go b/v3/pkg/application/parameter_test.go new file mode 100644 index 000000000..3a106e48e --- /dev/null +++ b/v3/pkg/application/parameter_test.go @@ -0,0 +1,118 @@ +package application + +import ( + "reflect" + "testing" +) + +func TestParameter_IsType(t *testing.T) { + param := &Parameter{ + Name: "test", + TypeName: "string", + } + + if !param.IsType("string") { + t.Error("IsType should return true for matching type") + } + + if param.IsType("int") { + t.Error("IsType should return false for non-matching type") + } +} + +func TestParameter_IsError(t *testing.T) { + errorParam := &Parameter{ + Name: "err", + TypeName: "error", + } + + if !errorParam.IsError() { + t.Error("IsError should return true for error type") + } + + stringParam := &Parameter{ + Name: "s", + TypeName: "string", + } + + if stringParam.IsError() { + t.Error("IsError should return false for non-error type") + } +} + +func TestNewParameter(t *testing.T) { + stringType := reflect.TypeOf("") + param := newParameter("myParam", stringType) + + if param.Name != "myParam" { + t.Errorf("Name = %q, want %q", param.Name, "myParam") + } + + if param.TypeName != "string" { + t.Errorf("TypeName = %q, want %q", param.TypeName, "string") + } + + if param.ReflectType != stringType { + t.Error("ReflectType not set correctly") + } +} + +func TestCallError_Error(t *testing.T) { + err := &CallError{ + Kind: ReferenceError, + Message: "test error", + } + + if err.Error() != "test error" { + t.Errorf("Error() = %q, want %q", err.Error(), "test error") + } +} + +func TestCallError_Kinds(t *testing.T) { + tests := []struct { + kind ErrorKind + expected string + }{ + {ReferenceError, "ReferenceError"}, + {TypeError, "TypeError"}, + {RuntimeError, "RuntimeError"}, + } + + for _, tt := range tests { + if string(tt.kind) != tt.expected { + t.Errorf("ErrorKind = %q, want %q", string(tt.kind), tt.expected) + } + } +} + +func TestCallError_WithCause(t *testing.T) { + cause := map[string]string{"detail": "some detail"} + err := &CallError{ + Kind: RuntimeError, + Message: "runtime error occurred", + Cause: cause, + } + + if err.Error() != "runtime error occurred" { + t.Error("Error() should return the message") + } + + if err.Cause == nil { + t.Error("Cause should be set") + } +} + +func TestCallOptions_Fields(t *testing.T) { + opts := CallOptions{ + MethodID: 12345, + MethodName: "TestService.Method", + } + + if opts.MethodID != 12345 { + t.Error("MethodID not set correctly") + } + + if opts.MethodName != "TestService.Method" { + t.Error("MethodName not set correctly") + } +} diff --git a/v3/pkg/application/path.go b/v3/pkg/application/path.go new file mode 100644 index 000000000..44066fa96 --- /dev/null +++ b/v3/pkg/application/path.go @@ -0,0 +1,133 @@ +package application + +import "github.com/adrg/xdg" + +type PathType int + +const ( + // PathHome is the user's home directory. + PathHome PathType = iota + + // PathDataHome defines the base directory relative to which user-specific + // data files should be stored. This directory is defined by the + // $XDG_DATA_HOME environment variable. If the variable is not set, + // a default equal to $HOME/.local/share should be used. + PathDataHome + + // PathConfigHome defines the base directory relative to which user-specific + // configuration files should be written. This directory is defined by + // the $XDG_CONFIG_HOME environment variable. If the variable is + // not set, a default equal to $HOME/.config should be used. + PathConfigHome + + // PathStateHome defines the base directory relative to which user-specific + // state files should be stored. This directory is defined by the + // $XDG_STATE_HOME environment variable. If the variable is not set, + // a default equal to ~/.local/state should be used. + PathStateHome + + // PathCacheHome defines the base directory relative to which user-specific + // non-essential (cached) data should be written. This directory is + // defined by the $XDG_CACHE_HOME environment variable. If the variable + // is not set, a default equal to $HOME/.cache should be used. + PathCacheHome + + // PathRuntimeDir defines the base directory relative to which user-specific + // non-essential runtime files and other file objects (such as sockets, + // named pipes, etc.) should be stored. This directory is defined by the + // $XDG_RUNTIME_DIR environment variable. If the variable is not set, + // applications should fall back to a replacement directory with similar + // capabilities. Applications should use this directory for communication + // and synchronization purposes and should not place larger files in it, + // since it might reside in runtime memory and cannot necessarily be + // swapped out to disk. + PathRuntimeDir + + // PathDesktop defines the location of the user's desktop directory. + PathDesktop + + // PathDownload defines a suitable location for user downloaded files. + PathDownload + + // PathDocuments defines a suitable location for user document files. + PathDocuments + + // PathMusic defines a suitable location for user audio files. + PathMusic + + // PathPictures defines a suitable location for user image files. + PathPictures + + // PathVideos defines a suitable location for user video files. + PathVideos + + // PathTemplates defines a suitable location for user template files. + PathTemplates + + // PathPublicShare defines a suitable location for user shared files. + PathPublicShare +) + +var paths = map[PathType]string{ + PathHome: xdg.Home, + PathDataHome: xdg.DataHome, + PathConfigHome: xdg.ConfigHome, + PathStateHome: xdg.StateHome, + PathCacheHome: xdg.CacheHome, + PathRuntimeDir: xdg.RuntimeDir, + PathDesktop: xdg.UserDirs.Desktop, + PathDownload: xdg.UserDirs.Download, + PathDocuments: xdg.UserDirs.Documents, + PathMusic: xdg.UserDirs.Music, + PathPictures: xdg.UserDirs.Pictures, + PathVideos: xdg.UserDirs.Videos, + PathTemplates: xdg.UserDirs.Templates, + PathPublicShare: xdg.UserDirs.PublicShare, +} + +type PathTypes int + +const ( + // PathsDataDirs defines the preference-ordered set of base directories to + // search for data files in addition to the DataHome base directory. + // This set of directories is defined by the $XDG_DATA_DIRS environment + // variable. If the variable is not set, the default directories + // to be used are /usr/local/share and /usr/share, in that order. The + // DataHome directory is considered more important than any of the + // directories defined by DataDirs. Therefore, user data files should be + // written relative to the DataHome directory, if possible. + PathsDataDirs PathTypes = iota + + // PathsConfigDirs defines the preference-ordered set of base directories + // search for configuration files in addition to the ConfigHome base + // directory. This set of directories is defined by the $XDG_CONFIG_DIRS + // environment variable. If the variable is not set, a default equal + // to /etc/xdg should be used. The ConfigHome directory is considered + // more important than any of the directories defined by ConfigDirs. + // Therefore, user config files should be written relative to the + // ConfigHome directory, if possible. + PathsConfigDirs + + // PathsFontDirs defines the common locations where font files are stored. + PathsFontDirs + + // PathsApplicationDirs defines the common locations of applications. + PathsApplicationDirs +) + +var pathdirs = map[PathTypes][]string{ + PathsDataDirs: xdg.DataDirs, + PathsConfigDirs: xdg.ConfigDirs, + PathsFontDirs: xdg.FontDirs, + PathsApplicationDirs: xdg.ApplicationDirs, +} + +// Path returns the path for the given selector +func Path(selector PathType) string { + return paths[selector] +} + +// Paths returns the paths for the given selector +func Paths(selector PathTypes) []string { + return pathdirs[selector] +} diff --git a/v3/pkg/application/popupmenu_windows.go b/v3/pkg/application/popupmenu_windows.go new file mode 100644 index 000000000..ed809a130 --- /dev/null +++ b/v3/pkg/application/popupmenu_windows.go @@ -0,0 +1,314 @@ +package application + +import ( + "sync/atomic" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/w32" +) + +const ( + MenuItemMsgID = w32.WM_APP + 1024 +) + +type RadioGroupMember struct { + ID int + MenuItem *MenuItem +} + +type RadioGroup []*RadioGroupMember + +func (r *RadioGroup) Add(id int, item *MenuItem) { + *r = append(*r, &RadioGroupMember{ + ID: id, + MenuItem: item, + }) +} + +func (r *RadioGroup) Bounds() (int, int) { + p := *r + return p[0].ID, p[len(p)-1].ID +} + +func (r *RadioGroup) MenuID(item *MenuItem) int { + for _, member := range *r { + if member.MenuItem == item { + return member.ID + } + } + panic("RadioGroup.MenuID: item not found:") +} + +type Win32Menu struct { + isPopup bool + menu w32.HMENU + parentWindow *windowsWebviewWindow + parent w32.HWND + menuMapping map[int]*MenuItem + checkboxItems map[*MenuItem][]int + radioGroups map[*MenuItem][]*RadioGroup + menuData *Menu + currentMenuID int + onMenuClose func() + onMenuOpen func() + isShowing atomic.Bool // guards against concurrent TrackPopupMenuEx calls +} + +func (p *Win32Menu) newMenu() w32.HMENU { + if p.isPopup { + return w32.NewPopupMenu() + } + return w32.CreateMenu() +} + +func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) { + currentRadioGroup := RadioGroup{} + for _, item := range inputMenu.items { + p.currentMenuID++ + itemID := p.currentMenuID + p.menuMapping[itemID] = item + + menuItemImpl := newMenuItemImpl(item, parentMenu, itemID) + menuItemImpl.parent = inputMenu + item.impl = menuItemImpl + + if item.Hidden() { + if item.accelerator != nil { + if p.parentWindow != nil { + // Remove the accelerator from the keybindings + p.parentWindow.parent.removeMenuBinding(item.accelerator) + } else { + // Remove the global keybindings + globalApplication.KeyBinding.Remove(item.accelerator.String()) + } + } + } + + flags := uint32(w32.MF_STRING) + if item.disabled { + flags = flags | w32.MF_GRAYED + } + if item.checked { + flags = flags | w32.MF_CHECKED + } + if item.IsSeparator() { + flags = flags | w32.MF_SEPARATOR + } + + if item.checked && item.IsRadio() { + flags = flags | w32.MFT_RADIOCHECK + } + + if item.IsCheckbox() { + p.checkboxItems[item] = append(p.checkboxItems[item], itemID) + } + if item.IsRadio() { + currentRadioGroup.Add(itemID, item) + } else { + if len(currentRadioGroup) > 0 { + for _, radioMember := range currentRadioGroup { + currentRadioGroup := currentRadioGroup + p.radioGroups[radioMember.MenuItem] = append(p.radioGroups[radioMember.MenuItem], ¤tRadioGroup) + } + currentRadioGroup = RadioGroup{} + } + } + + if item.submenu != nil { + flags = flags | w32.MF_POPUP + newSubmenu := p.newMenu() + p.buildMenu(newSubmenu, item.submenu) + itemID = int(newSubmenu) + menuItemImpl.submenu = newSubmenu + } + + var menuText = item.Label() + if item.accelerator != nil { + menuText = menuText + "\t" + item.accelerator.String() + if item.callback != nil { + if p.parentWindow != nil { + p.parentWindow.parent.addMenuBinding(item.accelerator, item) + } else { + globalApplication.KeyBinding.Add(item.accelerator.String(), func(w Window) { + item.handleClick() + }) + } + } + } + + // If the item is hidden, don't append + if item.Hidden() { + continue + } + + ok := w32.AppendMenu(parentMenu, flags, uintptr(itemID), w32.MustStringToUTF16Ptr(menuText)) + if !ok { + globalApplication.fatal("error adding menu item '%s'", menuText) + } + if item.bitmap != nil { + if err := w32.SetMenuIcons(parentMenu, itemID, item.bitmap, nil); err != nil { + globalApplication.fatal("error setting menu icons: %w", err) + } + } + } + if len(currentRadioGroup) > 0 { + for _, radioMember := range currentRadioGroup { + currentRadioGroup := currentRadioGroup + p.radioGroups[radioMember.MenuItem] = append(p.radioGroups[radioMember.MenuItem], ¤tRadioGroup) + } + currentRadioGroup = RadioGroup{} + } +} + +func (p *Win32Menu) Update() { + p.menu = p.newMenu() + p.menuMapping = make(map[int]*MenuItem) + p.currentMenuID = MenuItemMsgID + p.buildMenu(p.menu, p.menuData) + p.updateRadioGroups() +} + +func NewPopupMenu(parent w32.HWND, inputMenu *Menu) *Win32Menu { + result := &Win32Menu{ + isPopup: true, + parent: parent, + menuData: inputMenu, + checkboxItems: make(map[*MenuItem][]int), + radioGroups: make(map[*MenuItem][]*RadioGroup), + } + result.Update() + return result +} +func NewApplicationMenu(parent *windowsWebviewWindow, inputMenu *Menu) *Win32Menu { + result := &Win32Menu{ + parentWindow: parent, + parent: parent.hwnd, + menuData: inputMenu, + checkboxItems: make(map[*MenuItem][]int), + radioGroups: make(map[*MenuItem][]*RadioGroup), + } + result.Update() + return result +} + +func (p *Win32Menu) ShowAt(x int, y int) { + // Prevent concurrent menu displays - TrackPopupMenuEx is blocking and + // calling it while another popup is showing causes "TrackPopupMenu failed" + if !p.isShowing.CompareAndSwap(false, true) { + return + } + defer p.isShowing.Store(false) + + w32.SetForegroundWindow(p.parent) + + if p.onMenuOpen != nil { + p.onMenuOpen() + } + + // Get screen dimensions to determine menu positioning + monitor := w32.MonitorFromWindow(p.parent, w32.MONITOR_DEFAULTTONEAREST) + var monitorInfo w32.MONITORINFO + monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo)) + if !w32.GetMonitorInfo(monitor, &monitorInfo) { + globalApplication.fatal("GetMonitorInfo failed") + } + + // Set flags to always position the menu above the cursor + menuFlags := uint32(w32.TPM_LEFTALIGN | w32.TPM_BOTTOMALIGN) + + // Check if we're close to the right edge of the screen + // If so, right-align the menu with some padding + if x > int(monitorInfo.RcWork.Right)-200 { // Assuming 200px as a reasonable menu width + menuFlags = uint32(w32.TPM_RIGHTALIGN | w32.TPM_BOTTOMALIGN) + // Add a small padding (10px) from the right edge + x = int(monitorInfo.RcWork.Right) - 10 + } + + if !w32.TrackPopupMenuEx(p.menu, menuFlags, int32(x), int32(y), p.parent, nil) { + // TrackPopupMenuEx can fail if called during menu transitions or rapid clicks. + // This is not fatal - just skip this menu display attempt. + globalApplication.debug("TrackPopupMenu failed - menu may already be showing") + return + } + + if p.onMenuClose != nil { + p.onMenuClose() + } + + if !w32.PostMessage(p.parent, w32.WM_NULL, 0, 0) { + globalApplication.fatal("PostMessage failed") + } + +} + +func (p *Win32Menu) ShowAtCursor() { + x, y, ok := w32.GetCursorPos() + if ok == false { + globalApplication.fatal("GetCursorPos failed") + } + + p.ShowAt(x, y) +} + +func (p *Win32Menu) ProcessCommand(cmdMsgID int) bool { + item := p.menuMapping[cmdMsgID] + if item == nil { + return false + } + if item.IsRadio() { + if item.checked { + return true + } + item.checked = true + p.updateRadioGroup(item) + } + if item.callback != nil { + item.handleClick() + } + return true +} + +func (p *Win32Menu) Destroy() { + w32.DestroyMenu(p.menu) +} + +func (p *Win32Menu) UpdateMenuItem(item *MenuItem) { + if item.IsCheckbox() { + for _, itemID := range p.checkboxItems[item] { + var checkState uint = w32.MF_UNCHECKED + if item.checked { + checkState = w32.MF_CHECKED + } + w32.CheckMenuItem(p.menu, uintptr(itemID), checkState) + } + return + } + if item.IsRadio() && item.checked == true { + p.updateRadioGroup(item) + } +} + +func (p *Win32Menu) updateRadioGroups() { + for menuItem := range p.radioGroups { + if menuItem.checked { + p.updateRadioGroup(menuItem) + } + } +} + +func (p *Win32Menu) updateRadioGroup(item *MenuItem) { + for _, radioGroup := range p.radioGroups[item] { + thisMenuID := radioGroup.MenuID(item) + startID, endID := radioGroup.Bounds() + w32.CheckRadio(p.menu, startID, endID, thisMenuID) + + } +} + +func (p *Win32Menu) OnMenuOpen(fn func()) { + p.onMenuOpen = fn +} + +func (p *Win32Menu) OnMenuClose(fn func()) { + p.onMenuClose = fn +} diff --git a/v3/pkg/application/roles.go b/v3/pkg/application/roles.go new file mode 100644 index 000000000..78bbf2c38 --- /dev/null +++ b/v3/pkg/application/roles.go @@ -0,0 +1,164 @@ +package application + +import "runtime" + +// Heavily inspired by Electron (c) 2013-2020 Github Inc. +// Electron License: https://github.com/electron/electron/blob/master/LICENSE + +// Role is a type to identify menu roles +type Role uint + +// These constants need to be kept in sync with `v2/internal/frontend/desktop/darwin/Role.h` +const ( + NoRole Role = iota + AppMenu Role = iota + EditMenu Role = iota + ViewMenu Role = iota + WindowMenu Role = iota + ServicesMenu Role = iota + HelpMenu Role = iota + + Hide Role = iota + HideOthers Role = iota + ShowAll Role = iota + BringAllToFront Role = iota + UnHide Role = iota + About Role = iota + Undo Role = iota + Redo Role = iota + Cut Role = iota + Copy Role = iota + Paste Role = iota + PasteAndMatchStyle Role = iota + SelectAll Role = iota + Delete Role = iota + SpeechMenu Role = iota + Quit Role = iota + FileMenu Role = iota + CloseWindow Role = iota + Reload Role = iota + ForceReload Role = iota + OpenDevTools Role = iota + ResetZoom Role = iota + ZoomIn Role = iota + ZoomOut Role = iota + ToggleFullscreen Role = iota + + Minimise Role = iota + Zoom Role = iota + FullScreen Role = iota + + NewFile Role = iota + Open Role = iota + Save Role = iota + SaveAs Role = iota + StartSpeaking Role = iota + StopSpeaking Role = iota + Revert Role = iota + Print Role = iota + PageLayout Role = iota + Find Role = iota + FindAndReplace Role = iota + FindNext Role = iota + FindPrevious Role = iota + Front Role = iota + Help Role = iota +) + +func NewFileMenu() *MenuItem { + fileMenu := NewMenu() + if runtime.GOOS == "darwin" { + fileMenu.AddRole(CloseWindow) + } else { + fileMenu.AddRole(Quit) + } + subMenu := NewSubMenuItem("File") + subMenu.submenu = fileMenu + return subMenu +} + +func NewViewMenu() *MenuItem { + viewMenu := NewMenu() + viewMenu.AddRole(Reload) + viewMenu.AddRole(ForceReload) + addDevToolMenuItem(viewMenu) + viewMenu.AddSeparator() + viewMenu.AddRole(ResetZoom) + viewMenu.AddRole(ZoomIn) + viewMenu.AddRole(ZoomOut) + viewMenu.AddSeparator() + viewMenu.AddRole(ToggleFullscreen) + subMenu := NewSubMenuItem("View") + subMenu.submenu = viewMenu + return subMenu +} + +func NewAppMenu() *MenuItem { + if runtime.GOOS != "darwin" { + return nil + } + appMenu := NewMenu() + appMenu.AddRole(About) + appMenu.AddSeparator() + appMenu.AddRole(ServicesMenu) + appMenu.AddSeparator() + appMenu.AddRole(Hide) + appMenu.AddRole(HideOthers) + appMenu.AddRole(UnHide) + appMenu.AddSeparator() + appMenu.AddRole(Quit) + subMenu := NewSubMenuItem(globalApplication.options.Name) + subMenu.submenu = appMenu + return subMenu +} + +func NewEditMenu() *MenuItem { + editMenu := NewMenu() + editMenu.AddRole(Undo) + editMenu.AddRole(Redo) + editMenu.AddSeparator() + editMenu.AddRole(Cut) + editMenu.AddRole(Copy) + editMenu.AddRole(Paste) + if runtime.GOOS == "darwin" { + editMenu.AddRole(PasteAndMatchStyle) + editMenu.AddRole(Delete) + editMenu.AddRole(SelectAll) + editMenu.AddSeparator() + editMenu.AddRole(SpeechMenu) + } else { + editMenu.AddRole(Delete) + editMenu.AddSeparator() + editMenu.AddRole(SelectAll) + } + subMenu := NewSubMenuItem("Edit") + subMenu.submenu = editMenu + return subMenu +} + +func NewWindowMenu() *MenuItem { + menu := NewMenu() + menu.AddRole(Minimise) + menu.AddRole(Zoom) + if runtime.GOOS == "darwin" { + menu.AddSeparator() + menu.AddRole(Front) + //menu.AddSeparator() + //menu.AddRole(Window) + } else { + menu.AddRole(CloseWindow) + } + subMenu := NewSubMenuItem("Window") + subMenu.submenu = menu + return subMenu +} + +func NewHelpMenu() *MenuItem { + menu := NewMenu() + menu.Add("Learn More").OnClick(func(ctx *Context) { + globalApplication.Window.Current().SetURL("https://wails.io") + }) + subMenu := NewSubMenuItem("Help") + subMenu.submenu = menu + return subMenu +} diff --git a/v3/pkg/application/roles_dev.go b/v3/pkg/application/roles_dev.go new file mode 100644 index 000000000..1c03e398d --- /dev/null +++ b/v3/pkg/application/roles_dev.go @@ -0,0 +1,7 @@ +//go:build !production || devtools + +package application + +func addDevToolMenuItem(viewMenu *Menu) { + viewMenu.AddRole(OpenDevTools) +} diff --git a/v3/pkg/application/roles_production.go b/v3/pkg/application/roles_production.go new file mode 100644 index 000000000..d1af8dba7 --- /dev/null +++ b/v3/pkg/application/roles_production.go @@ -0,0 +1,5 @@ +//go:build production && !devtools + +package application + +func addDevToolMenuItem(viewMenu *Menu) {} diff --git a/v3/pkg/application/screen_android.go b/v3/pkg/application/screen_android.go new file mode 100644 index 000000000..03f03f280 --- /dev/null +++ b/v3/pkg/application/screen_android.go @@ -0,0 +1,32 @@ +//go:build android + +package application + +// getScreens returns the available screens for Android +func getScreens() ([]*Screen, error) { + // Android typically has one main display + // TODO: Support for multi-display via DisplayManager + return []*Screen{ + { + ID: "main", + Name: "Main Display", + IsPrimary: true, + Size: Size{ + Width: 1080, + Height: 2400, + }, + Bounds: Rect{ + X: 0, + Y: 0, + Width: 1080, + Height: 2400, + }, + WorkArea: Rect{ + X: 0, + Y: 0, + Width: 1080, + Height: 2340, // Minus navigation bar + }, + }, + }, nil +} diff --git a/v3/pkg/application/screen_darwin.go b/v3/pkg/application/screen_darwin.go new file mode 100644 index 000000000..ba9859ff0 --- /dev/null +++ b/v3/pkg/application/screen_darwin.go @@ -0,0 +1,194 @@ +//go:build darwin && !ios + +package application + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit -framework AppKit +#import +#import +#import +#import +#include + +typedef struct Screen { + const char* id; + const char* name; + int p_width; + int p_height; + int width; + int height; + int x; + int y; + int w_width; + int w_height; + int w_x; + int w_y; + float scaleFactor; + double rotation; + bool isPrimary; +} Screen; + + +int GetNumScreens(){ + return [[NSScreen screens] count]; +} + +Screen processScreen(NSScreen* screen){ + Screen returnScreen; + returnScreen.scaleFactor = screen.backingScaleFactor; + + // screen bounds + returnScreen.height = screen.frame.size.height; + returnScreen.width = screen.frame.size.width; + returnScreen.x = screen.frame.origin.x; + returnScreen.y = screen.frame.origin.y; + + // work area + NSRect workArea = [screen visibleFrame]; + returnScreen.w_height = workArea.size.height; + returnScreen.w_width = workArea.size.width; + returnScreen.w_x = workArea.origin.x; + returnScreen.w_y = workArea.origin.y; + + + // adapted from https://stackoverflow.com/a/1237490/4188138 + NSDictionary* screenDictionary = [screen deviceDescription]; + NSNumber* screenID = [screenDictionary objectForKey:@"NSScreenNumber"]; + CGDirectDisplayID displayID = [screenID unsignedIntValue]; + returnScreen.id = [[NSString stringWithFormat:@"%d", displayID] UTF8String]; + + // Get physical monitor size + NSValue *sizeValue = [screenDictionary objectForKey:@"NSDeviceSize"]; + NSSize physicalSize = sizeValue.sizeValue; + returnScreen.p_height = physicalSize.height; + returnScreen.p_width = physicalSize.width; + + // Get the rotation + double rotation = CGDisplayRotation(displayID); + returnScreen.rotation = rotation; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 + if( @available(macOS 10.15, *) ){ + returnScreen.name = [screen.localizedName UTF8String]; + } +#endif + return returnScreen; +} + +// Get primary screen +Screen GetPrimaryScreen(){ + // Get primary screen + NSScreen *mainScreen = [NSScreen mainScreen]; + return processScreen(mainScreen); +} + +Screen* getAllScreens() { + NSArray *screens = [NSScreen screens]; + Screen* returnScreens = malloc(sizeof(Screen) * screens.count); + for (int i = 0; i < screens.count; i++) { + NSScreen* screen = [screens objectAtIndex:i]; + returnScreens[i] = processScreen(screen); + } + return returnScreens; +} + +Screen getScreenForWindow(void* window){ + NSScreen* screen = ((NSWindow*)window).screen; + return processScreen(screen); +} + +// Get the screen for the system tray +Screen getScreenForSystemTray(void* nsStatusItem) { + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + NSRect frame = statusItem.button.frame; + NSArray *screens = NSScreen.screens; + NSScreen *associatedScreen = nil; + + for (NSScreen *screen in screens) { + if (NSPointInRect(frame.origin, screen.frame)) { + associatedScreen = screen; + break; + } + } + return processScreen(associatedScreen); +} + +void* getWindowForSystray(void* nsStatusItem) { + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + return statusItem.button.window; +} + + +*/ +import "C" +import "unsafe" + +func cScreenToScreen(screen C.Screen) *Screen { + + return &Screen{ + Size: Size{ + Width: int(screen.p_width), + Height: int(screen.p_height), + }, + Bounds: Rect{ + X: int(screen.x), + Y: int(screen.y), + Height: int(screen.height), + Width: int(screen.width), + }, + PhysicalBounds: Rect{ + X: int(screen.x), + Y: int(screen.y), + Height: int(screen.height), + Width: int(screen.width), + }, + WorkArea: Rect{ + X: int(screen.w_x), + Y: int(screen.w_y), + Height: int(screen.w_height), + Width: int(screen.w_width), + }, + PhysicalWorkArea: Rect{ + X: int(screen.w_x), + Y: int(screen.w_y), + Height: int(screen.w_height), + Width: int(screen.w_width), + }, + ScaleFactor: float32(screen.scaleFactor), + ID: C.GoString(screen.id), + Name: C.GoString(screen.name), + IsPrimary: bool(screen.isPrimary), + Rotation: float32(screen.rotation), + } +} + +func (m *macosApp) getPrimaryScreen() (*Screen, error) { + cScreen := C.GetPrimaryScreen() + return cScreenToScreen(cScreen), nil +} + +func (m *macosApp) getScreens() ([]*Screen, error) { + cScreens := C.getAllScreens() + defer C.free(unsafe.Pointer(cScreens)) + numScreens := int(C.GetNumScreens()) + displays := make([]*Screen, numScreens) + cScreenHeaders := (*[1 << 30]C.Screen)(unsafe.Pointer(cScreens))[:numScreens:numScreens] + for i := 0; i < numScreens; i++ { + displays[i] = cScreenToScreen(cScreenHeaders[i]) + } + return displays, nil +} + +func getScreenForWindow(window *macosWebviewWindow) (*Screen, error) { + cScreen := C.getScreenForWindow(window.nsWindow) + return cScreenToScreen(cScreen), nil +} + +func getScreenForSystray(systray *macosSystemTray) (*Screen, error) { + // Get the Window for the status item + // https://stackoverflow.com/a/5875019/4188138 + window := C.getWindowForSystray(systray.nsStatusItem) + cScreen := C.getScreenForWindow(window) + return cScreenToScreen(cScreen), nil +} diff --git a/v3/pkg/application/screen_ios.go b/v3/pkg/application/screen_ios.go new file mode 100644 index 000000000..7c3dfdbdd --- /dev/null +++ b/v3/pkg/application/screen_ios.go @@ -0,0 +1,33 @@ +//go:build ios + +package application + +// getScreens returns all screens on iOS - Screen type is defined in screenmanager.go + +// getScreens returns all screens on iOS +func getScreens() ([]*Screen, error) { + // iOS typically has one screen + // This would need proper implementation with UIScreen + mainRect := Rect{ + X: 0, + Y: 0, + Width: 1170, // iPhone 12 Pro width + Height: 2532, // iPhone 12 Pro height + } + return []*Screen{ + { + ID: "main", + Name: "Main Screen", + ScaleFactor: 3.0, // iPhone 12 Pro scale + X: 0, + Y: 0, + Size: Size{Width: 1170, Height: 2532}, + Bounds: mainRect, + PhysicalBounds: mainRect, + WorkArea: mainRect, + PhysicalWorkArea: mainRect, + IsPrimary: true, + Rotation: 0, + }, + }, nil +} \ No newline at end of file diff --git a/v3/pkg/application/screen_linux.go b/v3/pkg/application/screen_linux.go new file mode 100644 index 000000000..60507f554 --- /dev/null +++ b/v3/pkg/application/screen_linux.go @@ -0,0 +1,37 @@ +//go:build linux && !android && !server + +package application + +import ( + "sync" +) + +func (a *linuxApp) getPrimaryScreen() (*Screen, error) { + var wg sync.WaitGroup + var screen *Screen + var err error + wg.Add(1) + InvokeSync(func() { + screen, err = getPrimaryScreen() + wg.Done() + }) + wg.Wait() + return screen, err +} + +func (a *linuxApp) getScreens() ([]*Screen, error) { + var wg sync.WaitGroup + var screens []*Screen + var err error + wg.Add(1) + InvokeSync(func() { + screens, err = getScreens(a.application) + wg.Done() + }) + wg.Wait() + return screens, err +} + +func getScreenForWindow(window *linuxWebviewWindow) (*Screen, error) { + return window.getScreen() +} diff --git a/v3/pkg/application/screen_windows.go b/v3/pkg/application/screen_windows.go new file mode 100644 index 000000000..b6328f809 --- /dev/null +++ b/v3/pkg/application/screen_windows.go @@ -0,0 +1,88 @@ +//go:build windows + +package application + +import ( + "errors" + "strconv" + + "github.com/wailsapp/wails/v3/pkg/w32" + "golang.org/x/sys/windows" +) + +func (m *windowsApp) processAndCacheScreens() error { + allScreens, err := w32.GetAllScreens() + if err != nil { + return err + } + + // Convert result to []*Screen + var screens []*Screen + + for _, screen := range allScreens { + x := int(screen.MONITORINFOEX.RcMonitor.Left) + y := int(screen.MONITORINFOEX.RcMonitor.Top) + right := int(screen.MONITORINFOEX.RcMonitor.Right) + bottom := int(screen.MONITORINFOEX.RcMonitor.Bottom) + width := right - x + height := bottom - y + + workArea := Rect{ + X: int(screen.MONITORINFOEX.RcWork.Left), + Y: int(screen.MONITORINFOEX.RcWork.Top), + Width: int(screen.MONITORINFOEX.RcWork.Right - screen.MONITORINFOEX.RcWork.Left), + Height: int(screen.MONITORINFOEX.RcWork.Bottom - screen.MONITORINFOEX.RcWork.Top), + } + + screens = append(screens, &Screen{ + ID: hMonitorToScreenID(screen.HMonitor), + Name: windows.UTF16ToString(screen.MONITORINFOEX.SzDevice[:]), + X: x, + Y: y, + Size: Size{Width: width, Height: height}, + Bounds: Rect{X: x, Y: y, Width: width, Height: height}, + PhysicalBounds: Rect{X: x, Y: y, Width: width, Height: height}, + WorkArea: workArea, + PhysicalWorkArea: workArea, + IsPrimary: screen.IsPrimary, + ScaleFactor: screen.ScaleFactor, + Rotation: 0, + }) + } + + err = m.parent.Screen.LayoutScreens(screens) + if err != nil { + return err + } + + return nil +} + +// NOTE: should be moved to *App after DPI is implemented in all platforms +func (m *windowsApp) getScreens() ([]*Screen, error) { + return m.parent.Screen.screens, nil +} + +// NOTE: should be moved to *App after DPI is implemented in all platforms +func (m *windowsApp) getPrimaryScreen() (*Screen, error) { + return m.parent.Screen.primaryScreen, nil +} + +func getScreenForWindow(window *windowsWebviewWindow) (*Screen, error) { + return ScreenNearestPhysicalRect(window.physicalBounds()), nil +} + +func getScreenForWindowHwnd(hwnd w32.HWND) (*Screen, error) { + hMonitor := w32.MonitorFromWindow(hwnd, w32.MONITOR_DEFAULTTONEAREST) + screenID := hMonitorToScreenID(hMonitor) + for _, screen := range globalApplication.Screen.screens { + if screen.ID == screenID { + return screen, nil + } + } + return nil, errors.New("screen not found for window") +} + +func hMonitorToScreenID(hMonitor uintptr) string { + return strconv.Itoa(int(hMonitor)) +} diff --git a/v3/pkg/application/screenmanager.go b/v3/pkg/application/screenmanager.go new file mode 100644 index 000000000..6caa1cd33 --- /dev/null +++ b/v3/pkg/application/screenmanager.go @@ -0,0 +1,875 @@ +package application + +import ( + "errors" + "math" + "sort" +) + +// Heavily inspired by the Chromium project (Copyright 2015 The Chromium Authors) +// Chromium License: https://chromium.googlesource.com/chromium/src/+/HEAD/LICENSE + +type ScreenManager struct { + app *App + screens []*Screen + primaryScreen *Screen +} + +// newScreenManager creates a new ScreenManager instance +func newScreenManager(app *App) *ScreenManager { + return &ScreenManager{ + app: app, + } +} + +type Screen struct { + ID string // A unique identifier for the display + Name string // The name of the display + ScaleFactor float32 // The scale factor of the display (DPI/96) + X int // The x-coordinate of the top-left corner of the rectangle + Y int // The y-coordinate of the top-left corner of the rectangle + Size Size // The size of the display + Bounds Rect // The bounds of the display + PhysicalBounds Rect // The physical bounds of the display (before scaling) + WorkArea Rect // The work area of the display + PhysicalWorkArea Rect // The physical work area of the display (before scaling) + IsPrimary bool // Whether this is the primary display + Rotation float32 // The rotation of the display +} + +type Rect struct { + X int + Y int + Width int + Height int +} + +type Point struct { + X int + Y int +} +type Size struct { + Width int + Height int +} + +type Alignment int +type OffsetReference int + +const ( + TOP Alignment = iota + RIGHT + BOTTOM + LEFT +) + +const ( + BEGIN OffsetReference = iota // TOP or LEFT + END // BOTTOM or RIGHT +) + +// ScreenPlacement specifies where the screen (S) is placed relative to +// parent (P) screen. In the following example, (S) is RIGHT aligned to (P) +// with a positive offset and a BEGIN (top) offset reference. +// +// . +------------+ + +// . | | | offset +// . | P | v +// . | +--------+ +// . | | | +// . +------------+ S | +// . | | +// . +--------+ +type ScreenPlacement struct { + screen *Screen + parent *Screen + alignment Alignment + offset int + offsetReference OffsetReference +} + +func (r Rect) Origin() Point { + return Point{ + X: r.X, + Y: r.Y, + } +} + +func (s Screen) Origin() Point { + return Point{ + X: s.X, + Y: s.Y, + } +} + +func (r Rect) Corner() Point { + return Point{ + X: r.right(), + Y: r.bottom(), + } +} + +func (r Rect) InsideCorner() Point { + return Point{ + X: r.right() - 1, + Y: r.bottom() - 1, + } +} + +func (r Rect) right() int { + return r.X + r.Width +} + +func (r Rect) bottom() int { + return r.Y + r.Height +} + +func (s Screen) right() int { + return s.Bounds.right() +} + +func (s Screen) bottom() int { + return s.Bounds.bottom() +} + +func (s Screen) scale(value int, toDip bool) int { + // Round up when scaling down and round down when scaling up. + // This mix rounding strategy prevents drift over time when applying multiple scaling back and forth. + // In addition, It has been shown that using this approach minimized rounding issues and improved overall + // precision when converting between DIP and physical coordinates. + if toDip { + return int(math.Ceil(float64(value) / float64(s.ScaleFactor))) + } else { + return int(math.Floor(float64(value) * float64(s.ScaleFactor))) + } +} + +func (r Rect) Size() Size { + return Size{ + Width: r.Width, + Height: r.Height, + } +} + +func (r Rect) IsEmpty() bool { + return r.Width <= 0 || r.Height <= 0 +} + +func (r Rect) Contains(pt Point) bool { + return pt.X >= r.X && pt.X < r.X+r.Width && pt.Y >= r.Y && pt.Y < r.Y+r.Height +} + +// Get intersection with another rect +func (r Rect) Intersect(otherRect Rect) Rect { + if r.IsEmpty() || otherRect.IsEmpty() { + return Rect{} + } + + maxLeft := max(r.X, otherRect.X) + maxTop := max(r.Y, otherRect.Y) + minRight := min(r.right(), otherRect.right()) + minBottom := min(r.bottom(), otherRect.bottom()) + + if minRight > maxLeft && minBottom > maxTop { + return Rect{ + X: maxLeft, + Y: maxTop, + Width: minRight - maxLeft, + Height: minBottom - maxTop, + } + } + return Rect{} +} + +// Check if screens intersects another screen +func (s *Screen) intersects(otherScreen *Screen) bool { + maxLeft := max(s.X, otherScreen.X) + maxTop := max(s.Y, otherScreen.Y) + minRight := min(s.right(), otherScreen.right()) + minBottom := min(s.bottom(), otherScreen.bottom()) + + return minRight > maxLeft && minBottom > maxTop +} + +// Get distance from another rect (squared) +func (r Rect) distanceFromRectSquared(otherRect Rect) int { + // If they intersect, return negative area of intersection + intersection := r.Intersect(otherRect) + if !intersection.IsEmpty() { + return -(intersection.Width * intersection.Height) + } + + dX := max(0, max(r.X-otherRect.right(), otherRect.X-r.right())) + dY := max(0, max(r.Y-otherRect.bottom(), otherRect.Y-r.bottom())) + + // Distance squared + return dX*dX + dY*dY +} + +// Apply screen placement +func (p ScreenPlacement) apply() { + parentBounds := p.parent.Bounds + screenBounds := p.screen.Bounds + + newX := parentBounds.X + newY := parentBounds.Y + offset := p.offset + + if p.alignment == TOP || p.alignment == BOTTOM { + if p.offsetReference == END { + offset = parentBounds.Width - offset - screenBounds.Width + } + offset = min(offset, parentBounds.Width) + offset = max(offset, -screenBounds.Width) + newX += offset + if p.alignment == TOP { + newY -= screenBounds.Height + } else { + newY += parentBounds.Height + } + } else { + if p.offsetReference == END { + offset = parentBounds.Height - offset - screenBounds.Height + } + offset = min(offset, parentBounds.Height) + offset = max(offset, -screenBounds.Height) + newY += offset + if p.alignment == LEFT { + newX -= screenBounds.Width + } else { + newX += parentBounds.Width + } + } + + p.screen.move(newX, newY) +} + +func (s *Screen) absoluteToRelativeDipPoint(dipPoint Point) Point { + return Point{ + X: dipPoint.X - s.Bounds.X, + Y: dipPoint.Y - s.Bounds.Y, + } +} + +func (s *Screen) relativeToAbsoluteDipPoint(dipPoint Point) Point { + return Point{ + X: dipPoint.X + s.Bounds.X, + Y: dipPoint.Y + s.Bounds.Y, + } +} + +func (s *Screen) absoluteToRelativePhysicalPoint(physicalPoint Point) Point { + return Point{ + X: physicalPoint.X - s.PhysicalBounds.X, + Y: physicalPoint.Y - s.PhysicalBounds.Y, + } +} + +func (s *Screen) relativeToAbsolutePhysicalPoint(physicalPoint Point) Point { + return Point{ + X: physicalPoint.X + s.PhysicalBounds.X, + Y: physicalPoint.Y + s.PhysicalBounds.Y, + } +} + +func (s *Screen) move(newX, newY int) { + workAreaOffsetX := s.WorkArea.X - s.X + workAreaOffsetY := s.WorkArea.Y - s.Y + + s.X = newX + s.Y = newY + s.Bounds.X = newX + s.Bounds.Y = newY + s.WorkArea.X = newX + workAreaOffsetX + s.WorkArea.Y = newY + workAreaOffsetY +} + +func (s *Screen) applyDPIScaling() { + if s.ScaleFactor == 1 { + return + } + workAreaOffsetX := s.WorkArea.X - s.Bounds.X + workAreaOffsetY := s.WorkArea.Y - s.Bounds.Y + + s.WorkArea.X = s.Bounds.X + s.scale(workAreaOffsetX, true) + s.WorkArea.Y = s.Bounds.Y + s.scale(workAreaOffsetY, true) + + s.Bounds.Width = s.scale(s.PhysicalBounds.Width, true) + s.Bounds.Height = s.scale(s.PhysicalBounds.Height, true) + s.WorkArea.Width = s.scale(s.PhysicalWorkArea.Width, true) + s.WorkArea.Height = s.scale(s.PhysicalWorkArea.Height, true) + + s.Size.Width = s.Bounds.Width + s.Size.Height = s.Bounds.Height +} + +func (s *Screen) dipToPhysicalPoint(dipPoint Point, isCorner bool) Point { + relativePoint := s.absoluteToRelativeDipPoint(dipPoint) + scaledRelativePoint := Point{ + X: s.scale(relativePoint.X, false), + Y: s.scale(relativePoint.Y, false), + } + // Align edge points (fixes rounding issues) + edgeOffset := 1 + if isCorner { + edgeOffset = 0 + } + if relativePoint.X == s.Bounds.Width-edgeOffset { + scaledRelativePoint.X = s.PhysicalBounds.Width - edgeOffset + } + if relativePoint.Y == s.Bounds.Height-edgeOffset { + scaledRelativePoint.Y = s.PhysicalBounds.Height - edgeOffset + } + return s.relativeToAbsolutePhysicalPoint(scaledRelativePoint) +} + +func (s *Screen) physicalToDipPoint(physicalPoint Point, isCorner bool) Point { + relativePoint := s.absoluteToRelativePhysicalPoint(physicalPoint) + scaledRelativePoint := Point{ + X: s.scale(relativePoint.X, true), + Y: s.scale(relativePoint.Y, true), + } + // Align edge points (fixes rounding issues) + edgeOffset := 1 + if isCorner { + edgeOffset = 0 + } + if relativePoint.X == s.PhysicalBounds.Width-edgeOffset { + scaledRelativePoint.X = s.Bounds.Width - edgeOffset + } + if relativePoint.Y == s.PhysicalBounds.Height-edgeOffset { + scaledRelativePoint.Y = s.Bounds.Height - edgeOffset + } + return s.relativeToAbsoluteDipPoint(scaledRelativePoint) +} + +func (s *Screen) dipToPhysicalRect(dipRect Rect) Rect { + origin := s.dipToPhysicalPoint(dipRect.Origin(), false) + corner := s.dipToPhysicalPoint(dipRect.Corner(), true) + + return Rect{ + X: origin.X, + Y: origin.Y, + Width: corner.X - origin.X, + Height: corner.Y - origin.Y, + } +} + +func (s *Screen) physicalToDipRect(physicalRect Rect) Rect { + origin := s.physicalToDipPoint(physicalRect.Origin(), false) + corner := s.physicalToDipPoint(physicalRect.Corner(), true) + + return Rect{ + X: origin.X, + Y: origin.Y, + Width: corner.X - origin.X, + Height: corner.Y - origin.Y, + } +} + +// Layout screens in the virtual space with DIP calculations and cache the screens +// for future coordinate transformation between the physical and logical (DIP) space +func (m *ScreenManager) LayoutScreens(screens []*Screen) error { + if screens == nil || len(screens) == 0 { + return errors.New("screens parameter is nil or empty") + } + m.screens = screens + + err := m.calculateScreensDipCoordinates() + if err != nil { + return err + } + + return nil +} + +func (m *ScreenManager) GetAll() []*Screen { + return m.screens +} + +func (m *ScreenManager) GetPrimary() *Screen { + return m.primaryScreen +} + +// Reference: https://source.chromium.org/chromium/chromium/src/+/main:ui/display/win/screen_win.cc;l=317 +func (m *ScreenManager) calculateScreensDipCoordinates() error { + remainingScreens := []*Screen{} + + // Find the primary screen + m.primaryScreen = nil + for _, screen := range m.screens { + if screen.IsPrimary { + m.primaryScreen = screen + } else { + remainingScreens = append(remainingScreens, screen) + } + } + if m.primaryScreen == nil { + return errors.New("no primary screen found") + } else if len(remainingScreens) != len(m.screens)-1 { + return errors.New("invalid primary screen found") + } + + // Build screens tree using the primary screen as root + screensPlacements := []ScreenPlacement{} + availableParents := []*Screen{m.primaryScreen} + for len(availableParents) > 0 { + // Pop a parent + end := len(availableParents) - 1 + parent := availableParents[end] + availableParents = availableParents[:end] + // Find touching screens + for _, child := range m.findAndRemoveTouchingScreens(parent, &remainingScreens) { + screenPlacement := m.calculateScreenPlacement(child, parent) + screensPlacements = append(screensPlacements, screenPlacement) + availableParents = append(availableParents, child) + } + } + + // Apply screens DPI scaling and placement starting with + // the primary screen and then dependent screens + m.primaryScreen.applyDPIScaling() + for _, placement := range screensPlacements { + placement.screen.applyDPIScaling() + placement.apply() + } + + // Now that all the placements have been applied, + // we must detect and fix any overlapping screens. + m.deIntersectScreens(screensPlacements) + + return nil +} + +// Returns a ScreenPlacement for |screen| relative to |parent|. +// Note that ScreenPlacement's are always in DIPs, so this also performs the +// required scaling. +// References: +// - https://github.com/chromium/chromium/blob/main/ui/display/win/scaling_util.h#L25 +// - https://github.com/chromium/chromium/blob/main/ui/display/win/scaling_util.cc#L142 +func (m *ScreenManager) calculateScreenPlacement(screen, parent *Screen) ScreenPlacement { + // Examples (The offset is indicated by the arrow.): + // Scaled and Unscaled Coordinates + // +--------------+ + Since both screens are of the same scale + // | | | factor, relative positions remain the same. + // | Parent | V + // | 1x +----------+ + // | | | + // +--------------+ Screen | + // | 1x | + // +----------+ + // + // Unscaled Coordinates + // +--------------+ The 2x screen is offset to maintain a + // | | similar neighboring relationship with the 1x + // | Parent | parent. Screen's position is based off of the + // | 1x +----------+ percentage position along its parent. This + // | | | percentage position is preserved in the scaled + // +--------------+ Screen | coordinates. + // | 2x | + // +----------+ + // Scaled Coordinates + // +--------------+ + + // | | | + // | Parent | V + // | 1x +-----+ + // | | S 2x| + // +--------------+-----+ + // + // + // Unscaled Coordinates + // +--------------+ The parent screen has a 2x scale factor. + // | | The offset is adjusted to maintain the + // | | relative positioning of the 1x screen in + // | Parent +----------+ the scaled coordinate space. Screen's + // | 2x | | position is based off of the percentage + // | | Screen | position along its parent. This percentage + // | | 1x | position is preserved in the scaled + // +--------------+ | coordinates. + // | | + // +----------+ + // Scaled Coordinates + // +-------+ + + // | | V + // | Parent+----------+ + // | 2x | | + // +-------+ Screen | + // | 1x | + // | | + // | | + // +----------+ + // + // Unscaled Coordinates + // +----------+ In this case, parent lies between the top and + // | | bottom of parent. The roles are reversed when + // +-------+ | this occurs, and screen is placed to maintain + // | | Screen | parent's relative position along screen. + // | Parent| 1x | + // | 2x | | + // +-------+ | + // +----------+ + // Scaled Coordinates + // ^ +----------+ + // | | | + // + +----+ | + // |Prnt| Screen | + // | 2x | 1x | + // +----+ | + // | | + // +----------+ + // + // Scaled and Unscaled Coordinates + // +--------+ If the two screens are bottom aligned or + // | | right aligned, the ScreenPlacement will + // | +--------+ have an offset of 0 relative to the + // | | | end of the screen. + // | | | + // +--------+--------+ + + placement := ScreenPlacement{ + screen: screen, + parent: parent, + alignment: m.getScreenAlignment(screen, parent), + offset: 0, + offsetReference: BEGIN, + } + + screenBegin, screenEnd := 0, 0 + parentBegin, parentEnd := 0, 0 + + switch placement.alignment { + case TOP, BOTTOM: + screenBegin = screen.X + screenEnd = screen.right() + parentBegin = parent.X + parentEnd = parent.right() + case LEFT, RIGHT: + screenBegin = screen.Y + screenEnd = screen.bottom() + parentBegin = parent.Y + parentEnd = parent.bottom() + } + + // Since we're calculating offsets, make everything relative to parentBegin + parentEnd -= parentBegin + screenBegin -= parentBegin + screenEnd -= parentBegin + parentBegin = 0 + + // There are a few ways lines can intersect: + // End Aligned + // SCREEN's offset is relative to the END (BOTTOM or RIGHT). + // +-PARENT----------------+ + // +-SCREEN-------------+ + // + // Positioning based off of |screenBegin|. + // SCREEN's offset is simply a percentage of its position on PARENT. + // +-PARENT----------------+ + // ^+-SCREEN------------+ + // + // Positioning based off of |screenEnd|. + // SCREEN's offset is dependent on the percentage of its end position on PARENT. + // +-PARENT----------------+ + // +-SCREEN------------+^ + // + // Positioning based off of |parentBegin| on SCREEN. + // SCREEN's offset is dependent on the percentage of its position on PARENT. + // +-PARENT----------------+ + // ^+-SCREEN--------------------------+ + + if screenEnd == parentEnd { + placement.offsetReference = END + placement.offset = 0 + } else if screenBegin >= parentBegin { + placement.offsetReference = BEGIN + placement.offset = m.scaleOffset(parentEnd, parent.ScaleFactor, screenBegin) + } else if screenEnd <= parentEnd { + placement.offsetReference = END + placement.offset = m.scaleOffset(parentEnd, parent.ScaleFactor, parentEnd-screenEnd) + } else { + placement.offsetReference = BEGIN + placement.offset = m.scaleOffset(screenEnd-screenBegin, screen.ScaleFactor, screenBegin) + } + + return placement +} + +// Get screen alignment relative to parent (TOP, RIGHT, BOTTOM, LEFT) +func (m *ScreenManager) getScreenAlignment(screen, parent *Screen) Alignment { + maxLeft := max(screen.X, parent.X) + maxTop := max(screen.Y, parent.Y) + minRight := min(screen.right(), parent.right()) + minBottom := min(screen.bottom(), parent.bottom()) + + // Corners touching + if maxLeft == minRight && maxTop == minBottom { + if screen.Y == maxTop { + return BOTTOM + } else if parent.X == maxLeft { + return LEFT + } + return TOP + } + + // Vertical edge touching + if maxLeft == minRight { + if screen.X == maxLeft { + return RIGHT + } else { + return LEFT + } + } + + // Horizontal edge touching + if maxTop == minBottom { + if screen.Y == maxTop { + return BOTTOM + } else { + return TOP + } + } + + return -1 // Shouldn't be reached +} + +func (m *ScreenManager) deIntersectScreens(screensPlacements []ScreenPlacement) { + parentIDMap := make(map[string]string) + for _, placement := range screensPlacements { + parentIDMap[placement.screen.ID] = placement.parent.ID + } + + treeDepthMap := make(map[string]int) + for _, screen := range m.screens { + id, ok, depth := screen.ID, true, 0 + const maxDepth = 100 + for id != m.primaryScreen.ID && depth < maxDepth { + depth++ + id, ok = parentIDMap[id] + if !ok { + depth = maxDepth + } + } + treeDepthMap[screen.ID] = depth + } + + sortedScreens := make([]*Screen, len(m.screens)) + copy(sortedScreens, m.screens) + + // Sort the screens first by their depth in the screen hierarchy tree, + // and then by distance from screen origin to primary origin. This way we + // process the screens starting at the root (the primary screen), in the + // order of their descendance spanning out from the primary screen. + sort.Slice(sortedScreens, func(i, j int) bool { + s1, s2 := m.screens[i], m.screens[j] + s1_depth := treeDepthMap[s1.ID] + s2_depth := treeDepthMap[s2.ID] + + if s1_depth != s2_depth { + return s1_depth < s2_depth + } + + // Distance squared + s1_distance := s1.X*s1.X + s1.Y*s1.Y + s2_distance := s2.X*s2.X + s2.Y*s2.Y + if s1_distance != s2_distance { + return s1_distance < s2_distance + } + + return s1.ID < s2.ID + }) + + for i := 1; i < len(sortedScreens); i++ { + targetScreen := sortedScreens[i] + for j := 0; j < i; j++ { + sourceScreen := sortedScreens[j] + if targetScreen.intersects(sourceScreen) { + m.fixScreenIntersection(targetScreen, sourceScreen) + } + } + } +} + +// Offset the target screen along either X or Y axis away from the origin +// so that it removes the intersection with the source screen +// This function assume both screens already intersect. +func (m *ScreenManager) fixScreenIntersection(targetScreen, sourceScreen *Screen) { + offsetX, offsetY := 0, 0 + + if targetScreen.X >= 0 { + offsetX = sourceScreen.right() - targetScreen.X + } else { + offsetX = -(targetScreen.right() - sourceScreen.X) + } + + if targetScreen.Y >= 0 { + offsetY = sourceScreen.bottom() - targetScreen.Y + } else { + offsetY = -(targetScreen.bottom() - sourceScreen.Y) + } + + // Choose the smaller offset (X or Y) + if math.Abs(float64(offsetX)) <= math.Abs(float64(offsetY)) { + offsetY = 0 + } else { + offsetX = 0 + } + + // Apply the offset + newX := targetScreen.X + offsetX + newY := targetScreen.Y + offsetY + targetScreen.move(newX, newY) +} + +func (m *ScreenManager) findAndRemoveTouchingScreens(parent *Screen, screens *[]*Screen) []*Screen { + touchingScreens := []*Screen{} + remainingScreens := []*Screen{} + + for _, screen := range *screens { + if m.areScreensTouching(parent, screen) { + touchingScreens = append(touchingScreens, screen) + } else { + remainingScreens = append(remainingScreens, screen) + } + } + *screens = remainingScreens + return touchingScreens +} + +func (m *ScreenManager) areScreensTouching(a, b *Screen) bool { + maxLeft := max(a.X, b.X) + maxTop := max(a.Y, b.Y) + minRight := min(a.right(), b.right()) + minBottom := min(a.bottom(), b.bottom()) + return (maxLeft == minRight && maxTop <= minBottom) || (maxTop == minBottom && maxLeft <= minRight) +} + +// Scale |unscaledOffset| to the same relative position on |unscaledLength| +// based off of |unscaledLength|'s |scaleFactor| +func (m *ScreenManager) scaleOffset(unscaledLength int, scaleFactor float32, unscaledOffset int) int { + scaledLength := float32(unscaledLength) / scaleFactor + percent := float32(unscaledOffset) / float32(unscaledLength) + return int(math.Floor(float64(scaledLength * percent))) +} + +func (m *ScreenManager) screenNearestPoint(point Point, isPhysical bool) *Screen { + for _, screen := range m.screens { + if isPhysical { + if screen.PhysicalBounds.Contains(point) { + return screen + } + } else { + if screen.Bounds.Contains(point) { + return screen + } + } + } + return m.primaryScreen +} + +func (m *ScreenManager) screenNearestRect(rect Rect, isPhysical bool, excludedScreens map[string]bool) *Screen { + var nearestScreen *Screen + var distance, nearestScreenDistance int + for _, screen := range m.screens { + if excludedScreens[screen.ID] { + continue + } + if isPhysical { + distance = rect.distanceFromRectSquared(screen.PhysicalBounds) + } else { + distance = rect.distanceFromRectSquared(screen.Bounds) + } + if nearestScreen == nil || distance < nearestScreenDistance { + nearestScreen = screen + nearestScreenDistance = distance + } + } + if !isPhysical && len(excludedScreens) < len(m.screens)-1 { + // Make sure to give the same screen that would be given by the physical rect + // of this dip rect so transforming back and forth always gives the same result. + // This is important because it could happen that a dip rect intersects Screen1 + // more than Screen2 but in the physical layout Screen2 will scale up or Screen1 + // will scale down causing the intersection area to change so transforming back + // would give a different rect. + physicalRect := nearestScreen.dipToPhysicalRect(rect) + physicalRectScreen := m.screenNearestRect(physicalRect, true, nil) + if nearestScreen != physicalRectScreen { + if excludedScreens == nil { + excludedScreens = make(map[string]bool) + } + excludedScreens[nearestScreen.ID] = true + return m.screenNearestRect(rect, isPhysical, excludedScreens) + } + } + return nearestScreen +} + +func (m *ScreenManager) DipToPhysicalPoint(dipPoint Point) Point { + screen := m.ScreenNearestDipPoint(dipPoint) + return screen.dipToPhysicalPoint(dipPoint, false) +} + +func (m *ScreenManager) PhysicalToDipPoint(physicalPoint Point) Point { + screen := m.ScreenNearestPhysicalPoint(physicalPoint) + return screen.physicalToDipPoint(physicalPoint, false) +} + +func (m *ScreenManager) DipToPhysicalRect(dipRect Rect) Rect { + screen := m.ScreenNearestDipRect(dipRect) + return screen.dipToPhysicalRect(dipRect) +} + +func (m *ScreenManager) PhysicalToDipRect(physicalRect Rect) Rect { + screen := m.ScreenNearestPhysicalRect(physicalRect) + return screen.physicalToDipRect(physicalRect) +} + +func (m *ScreenManager) ScreenNearestPhysicalPoint(physicalPoint Point) *Screen { + return m.screenNearestPoint(physicalPoint, true) +} + +func (m *ScreenManager) ScreenNearestDipPoint(dipPoint Point) *Screen { + return m.screenNearestPoint(dipPoint, false) +} + +func (m *ScreenManager) ScreenNearestPhysicalRect(physicalRect Rect) *Screen { + return m.screenNearestRect(physicalRect, true, nil) +} + +func (m *ScreenManager) ScreenNearestDipRect(dipRect Rect) *Screen { + return m.screenNearestRect(dipRect, false, nil) +} + +// ================================================================================================ +// Exported application-level methods for internal convenience and availability to application devs + +func DipToPhysicalPoint(dipPoint Point) Point { + return globalApplication.Screen.DipToPhysicalPoint(dipPoint) +} + +func PhysicalToDipPoint(physicalPoint Point) Point { + return globalApplication.Screen.PhysicalToDipPoint(physicalPoint) +} + +func DipToPhysicalRect(dipRect Rect) Rect { + return globalApplication.Screen.DipToPhysicalRect(dipRect) +} + +func PhysicalToDipRect(physicalRect Rect) Rect { + return globalApplication.Screen.PhysicalToDipRect(physicalRect) +} + +func ScreenNearestPhysicalPoint(physicalPoint Point) *Screen { + return globalApplication.Screen.ScreenNearestPhysicalPoint(physicalPoint) +} + +func ScreenNearestDipPoint(dipPoint Point) *Screen { + return globalApplication.Screen.ScreenNearestDipPoint(dipPoint) +} + +func ScreenNearestPhysicalRect(physicalRect Rect) *Screen { + return globalApplication.Screen.ScreenNearestPhysicalRect(physicalRect) +} + +func ScreenNearestDipRect(dipRect Rect) *Screen { + return globalApplication.Screen.ScreenNearestDipRect(dipRect) +} diff --git a/v3/pkg/application/screenmanager_internal_test.go b/v3/pkg/application/screenmanager_internal_test.go new file mode 100644 index 000000000..9445c1f6d --- /dev/null +++ b/v3/pkg/application/screenmanager_internal_test.go @@ -0,0 +1,435 @@ +package application + +import ( + "testing" +) + +func TestAlignment_Constants(t *testing.T) { + if TOP != 0 { + t.Error("TOP should be 0") + } + if RIGHT != 1 { + t.Error("RIGHT should be 1") + } + if BOTTOM != 2 { + t.Error("BOTTOM should be 2") + } + if LEFT != 3 { + t.Error("LEFT should be 3") + } +} + +func TestOffsetReference_Constants(t *testing.T) { + if BEGIN != 0 { + t.Error("BEGIN should be 0") + } + if END != 1 { + t.Error("END should be 1") + } +} + +func TestRect_Origin(t *testing.T) { + rect := Rect{X: 10, Y: 20, Width: 100, Height: 200} + origin := rect.Origin() + + if origin.X != 10 { + t.Errorf("origin.X = %d, want 10", origin.X) + } + if origin.Y != 20 { + t.Errorf("origin.Y = %d, want 20", origin.Y) + } +} + +func TestRect_Corner(t *testing.T) { + rect := Rect{X: 10, Y: 20, Width: 100, Height: 200} + corner := rect.Corner() + + if corner.X != 110 { // 10 + 100 + t.Errorf("corner.X = %d, want 110", corner.X) + } + if corner.Y != 220 { // 20 + 200 + t.Errorf("corner.Y = %d, want 220", corner.Y) + } +} + +func TestRect_InsideCorner(t *testing.T) { + rect := Rect{X: 10, Y: 20, Width: 100, Height: 200} + inside := rect.InsideCorner() + + if inside.X != 109 { // 10 + 100 - 1 + t.Errorf("inside.X = %d, want 109", inside.X) + } + if inside.Y != 219 { // 20 + 200 - 1 + t.Errorf("inside.Y = %d, want 219", inside.Y) + } +} + +func TestRect_right(t *testing.T) { + rect := Rect{X: 10, Y: 20, Width: 100, Height: 200} + if rect.right() != 110 { + t.Errorf("right() = %d, want 110", rect.right()) + } +} + +func TestRect_bottom(t *testing.T) { + rect := Rect{X: 10, Y: 20, Width: 100, Height: 200} + if rect.bottom() != 220 { + t.Errorf("bottom() = %d, want 220", rect.bottom()) + } +} + +func TestRect_Size(t *testing.T) { + rect := Rect{X: 10, Y: 20, Width: 100, Height: 200} + size := rect.Size() + + if size.Width != 100 { + t.Errorf("Width = %d, want 100", size.Width) + } + if size.Height != 200 { + t.Errorf("Height = %d, want 200", size.Height) + } +} + +func TestRect_IsEmpty(t *testing.T) { + tests := []struct { + rect Rect + expected bool + }{ + {Rect{X: 0, Y: 0, Width: 0, Height: 0}, true}, + {Rect{X: 0, Y: 0, Width: 100, Height: 0}, true}, + {Rect{X: 0, Y: 0, Width: 0, Height: 100}, true}, + {Rect{X: 0, Y: 0, Width: -1, Height: 100}, true}, + {Rect{X: 0, Y: 0, Width: 100, Height: -1}, true}, + {Rect{X: 0, Y: 0, Width: 100, Height: 200}, false}, + {Rect{X: 10, Y: 20, Width: 1, Height: 1}, false}, + } + + for _, tt := range tests { + result := tt.rect.IsEmpty() + if result != tt.expected { + t.Errorf("Rect%v.IsEmpty() = %v, want %v", tt.rect, result, tt.expected) + } + } +} + +func TestRect_Contains(t *testing.T) { + rect := Rect{X: 10, Y: 20, Width: 100, Height: 200} + + tests := []struct { + point Point + expected bool + }{ + {Point{X: 10, Y: 20}, true}, // top-left corner + {Point{X: 50, Y: 100}, true}, // inside + {Point{X: 109, Y: 219}, true}, // inside corner + {Point{X: 110, Y: 220}, false}, // corner (exclusive) + {Point{X: 0, Y: 0}, false}, // outside + {Point{X: 9, Y: 20}, false}, // left of rect + {Point{X: 10, Y: 19}, false}, // above rect + {Point{X: 111, Y: 100}, false}, // right of rect + {Point{X: 50, Y: 221}, false}, // below rect + } + + for _, tt := range tests { + result := rect.Contains(tt.point) + if result != tt.expected { + t.Errorf("Rect%v.Contains(%v) = %v, want %v", rect, tt.point, result, tt.expected) + } + } +} + +func TestRect_Intersect(t *testing.T) { + tests := []struct { + name string + r1 Rect + r2 Rect + expected Rect + }{ + { + name: "overlapping", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 50, Y: 50, Width: 100, Height: 100}, + expected: Rect{X: 50, Y: 50, Width: 50, Height: 50}, + }, + { + name: "no overlap - horizontal", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 200, Y: 0, Width: 100, Height: 100}, + expected: Rect{}, + }, + { + name: "no overlap - vertical", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 0, Y: 200, Width: 100, Height: 100}, + expected: Rect{}, + }, + { + name: "contained", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 25, Y: 25, Width: 50, Height: 50}, + expected: Rect{X: 25, Y: 25, Width: 50, Height: 50}, + }, + { + name: "identical", + r1: Rect{X: 10, Y: 20, Width: 100, Height: 100}, + r2: Rect{X: 10, Y: 20, Width: 100, Height: 100}, + expected: Rect{X: 10, Y: 20, Width: 100, Height: 100}, + }, + { + name: "empty rect 1", + r1: Rect{}, + r2: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + expected: Rect{}, + }, + { + name: "empty rect 2", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{}, + expected: Rect{}, + }, + { + name: "touching edges - no intersection", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 100, Y: 0, Width: 100, Height: 100}, + expected: Rect{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.r1.Intersect(tt.r2) + if result != tt.expected { + t.Errorf("Intersect: got %v, want %v", result, tt.expected) + } + }) + } +} + +func TestRect_distanceFromRectSquared(t *testing.T) { + tests := []struct { + name string + r1 Rect + r2 Rect + expected int + }{ + { + name: "overlapping - negative area", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 50, Y: 50, Width: 100, Height: 100}, + expected: -(50 * 50), // intersection area + }, + { + name: "horizontal gap", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 110, Y: 0, Width: 100, Height: 100}, + expected: 100, // gap of 10, squared + }, + { + name: "vertical gap", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 0, Y: 120, Width: 100, Height: 100}, + expected: 400, // gap of 20, squared + }, + { + name: "diagonal gap", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 110, Y: 110, Width: 100, Height: 100}, + expected: 200, // dX=10, dY=10, 10^2 + 10^2 = 200 + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.r1.distanceFromRectSquared(tt.r2) + if result != tt.expected { + t.Errorf("distanceFromRectSquared: got %d, want %d", result, tt.expected) + } + }) + } +} + +func TestScreen_Origin(t *testing.T) { + screen := Screen{X: 100, Y: 200} + origin := screen.Origin() + + if origin.X != 100 { + t.Errorf("origin.X = %d, want 100", origin.X) + } + if origin.Y != 200 { + t.Errorf("origin.Y = %d, want 200", origin.Y) + } +} + +func TestScreen_scale(t *testing.T) { + screen := Screen{ScaleFactor: 2.0} + + tests := []struct { + value int + toDip bool + expected int + }{ + {100, true, 50}, // to DIP: 100 / 2 = 50 + {100, false, 200}, // to physical: 100 * 2 = 200 + {101, true, 51}, // to DIP: ceil(101 / 2) = 51 + {101, false, 202}, // to physical: floor(101 * 2) = 202 + {0, true, 0}, + {0, false, 0}, + } + + for _, tt := range tests { + result := screen.scale(tt.value, tt.toDip) + if result != tt.expected { + t.Errorf("scale(%d, %v) = %d, want %d", tt.value, tt.toDip, result, tt.expected) + } + } +} + +func TestScreen_scale_1_5(t *testing.T) { + screen := Screen{ScaleFactor: 1.5} + + tests := []struct { + value int + toDip bool + expected int + }{ + {150, true, 100}, // to DIP: ceil(150 / 1.5) = 100 + {100, false, 150}, // to physical: floor(100 * 1.5) = 150 + {100, true, 67}, // to DIP: ceil(100 / 1.5) = 67 + {67, false, 100}, // to physical: floor(67 * 1.5) = 100 + } + + for _, tt := range tests { + result := screen.scale(tt.value, tt.toDip) + if result != tt.expected { + t.Errorf("scale(%d, %v) with factor 1.5 = %d, want %d", tt.value, tt.toDip, result, tt.expected) + } + } +} + +func TestScreen_right(t *testing.T) { + screen := Screen{ + Bounds: Rect{X: 100, Y: 0, Width: 200, Height: 100}, + } + if screen.right() != 300 { + t.Errorf("right() = %d, want 300", screen.right()) + } +} + +func TestScreen_bottom(t *testing.T) { + screen := Screen{ + Bounds: Rect{X: 0, Y: 100, Width: 100, Height: 200}, + } + if screen.bottom() != 300 { + t.Errorf("bottom() = %d, want 300", screen.bottom()) + } +} + +func TestScreen_intersects(t *testing.T) { + screen1 := &Screen{ + X: 0, + Y: 0, + Bounds: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + } + + screen2 := &Screen{ + X: 50, + Y: 50, + Bounds: Rect{X: 50, Y: 50, Width: 100, Height: 100}, + } + + screen3 := &Screen{ + X: 200, + Y: 0, + Bounds: Rect{X: 200, Y: 0, Width: 100, Height: 100}, + } + + if !screen1.intersects(screen2) { + t.Error("screen1 and screen2 should intersect") + } + if screen1.intersects(screen3) { + t.Error("screen1 and screen3 should not intersect") + } +} + +func TestPoint_Fields(t *testing.T) { + pt := Point{X: 10, Y: 20} + if pt.X != 10 || pt.Y != 20 { + t.Error("Point fields not set correctly") + } +} + +func TestSize_Fields(t *testing.T) { + size := Size{Width: 100, Height: 200} + if size.Width != 100 || size.Height != 200 { + t.Error("Size fields not set correctly") + } +} + +func TestScreen_Fields(t *testing.T) { + screen := Screen{ + ID: "display-1", + Name: "Primary Display", + ScaleFactor: 2.0, + X: 0, + Y: 0, + Size: Size{Width: 1920, Height: 1080}, + Bounds: Rect{X: 0, Y: 0, Width: 1920, Height: 1080}, + IsPrimary: true, + Rotation: 0, + } + + if screen.ID != "display-1" { + t.Error("ID not set correctly") + } + if screen.Name != "Primary Display" { + t.Error("Name not set correctly") + } + if screen.ScaleFactor != 2.0 { + t.Error("ScaleFactor not set correctly") + } + if !screen.IsPrimary { + t.Error("IsPrimary not set correctly") + } +} + +func TestScreenPlacement_Fields(t *testing.T) { + parent := &Screen{ID: "parent"} + child := &Screen{ID: "child"} + + placement := ScreenPlacement{ + screen: child, + parent: parent, + alignment: RIGHT, + offset: 100, + offsetReference: BEGIN, + } + + if placement.screen != child { + t.Error("screen not set correctly") + } + if placement.parent != parent { + t.Error("parent not set correctly") + } + if placement.alignment != RIGHT { + t.Error("alignment not set correctly") + } + if placement.offset != 100 { + t.Error("offset not set correctly") + } + if placement.offsetReference != BEGIN { + t.Error("offsetReference not set correctly") + } +} + +func TestNewScreenManager(t *testing.T) { + sm := newScreenManager(nil) + if sm == nil { + t.Fatal("newScreenManager returned nil") + } + if sm.screens != nil { + t.Error("screens should be nil initially") + } + if sm.primaryScreen != nil { + t.Error("primaryScreen should be nil initially") + } +} diff --git a/v3/pkg/application/screenmanager_test.go b/v3/pkg/application/screenmanager_test.go new file mode 100644 index 000000000..1e58e3fd1 --- /dev/null +++ b/v3/pkg/application/screenmanager_test.go @@ -0,0 +1,716 @@ +package application_test + +import ( + "fmt" + "math" + "slices" + "strconv" + "testing" + + "github.com/matryer/is" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type ScreenDef struct { + id int + w, h int + s float32 + parent ScreenDefParent + name string +} + +type ScreenDefParent struct { + id int + align string + offset int +} + +type ScreensLayout struct { + name string + screens []ScreenDef +} + +type ParsedLayout struct { + name string + screens []*application.Screen +} + +func exampleLayouts() []ParsedLayout { + layouts := [][]ScreensLayout{ + { + // Normal examples (demonstrate real life scenarios) + { + name: "Single 4k monitor", + screens: []ScreenDef{ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + }, + }, + { + name: "Two monitors", + screens: []ScreenDef{ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + {id: 2, w: 1920, h: 1080, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI`}, + }, + }, + { + name: "Two monitors (2)", + screens: []ScreenDef{ + {id: 1, w: 1920, h: 1080, s: 1, name: `23" FHD 96DPI`}, + {id: 2, w: 1920, h: 1080, s: 1.25, parent: ScreenDefParent{id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI (125%)`}, + }, + }, + { + name: "Three monitors", + screens: []ScreenDef{ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + {id: 2, w: 1920, h: 1080, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI`}, + {id: 3, w: 1920, h: 1080, s: 1.25, parent: ScreenDefParent{id: 1, align: "l", offset: 0}, name: `23" FHD 96DPI (125%)`}, + }, + }, + { + name: "Four monitors", + screens: []ScreenDef{ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + {id: 2, w: 1920, h: 1080, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI`}, + {id: 3, w: 1920, h: 1080, s: 1.25, parent: ScreenDefParent{id: 2, align: "b", offset: 0}, name: `23" FHD 96DPI (125%)`}, + {id: 4, w: 1080, h: 1920, s: 1, parent: ScreenDefParent{id: 1, align: "l", offset: 0}, name: `23" FHD (90deg)`}, + }, + }, + }, + { + // Test cases examples (demonstrate the algorithm basics) + { + name: "Child scaled, Start offset", + screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1.5, parent: ScreenDefParent{id: 1, align: "r", offset: 600}, name: "Child"}, + }, + }, + { + name: "Child scaled, End offset", + screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1.5, parent: ScreenDefParent{id: 1, align: "r", offset: -600}, name: "Child"}, + }, + }, + { + name: "Parent scaled, Start offset percent", + screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 600}, name: "Child"}, + }, + }, + { + name: "Parent scaled, End offset percent", + screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: -600}, name: "Child"}, + }, + }, + { + name: "Parent scaled, Start align", + screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1100, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}, name: "Child"}, + }, + }, + { + name: "Parent scaled, End align", + screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}, name: "Child"}, + }, + }, + { + name: "Parent scaled, in-between", + screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1500, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: -250}, name: "Child"}, + }, + }, + }, + { + // Edge cases examples + { + name: "Parent order (5 is parent of 4)", + screens: []ScreenDef{ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 600, s: 1.25, parent: ScreenDefParent{id: 1, align: "r", offset: -200}}, + {id: 3, w: 800, h: 800, s: 1.25, parent: ScreenDefParent{id: 2, align: "b", offset: 0}}, + {id: 4, w: 800, h: 1080, s: 1.5, parent: ScreenDefParent{id: 2, align: "re", offset: 100}}, + {id: 5, w: 600, h: 600, s: 1, parent: ScreenDefParent{id: 3, align: "r", offset: 100}}, + }, + }, + { + name: "de-intersection reparent", + screens: []ScreenDef{ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1680, h: 1050, s: 1.25, parent: ScreenDefParent{id: 1, align: "r", offset: 10}}, + {id: 3, w: 1440, h: 900, s: 1.5, parent: ScreenDefParent{id: 1, align: "le", offset: 150}}, + {id: 4, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 3, align: "bc", offset: -200}}, + {id: 5, w: 1024, h: 768, s: 1.25, parent: ScreenDefParent{id: 4, align: "r", offset: 400}}, + }, + }, + { + name: "de-intersection (unattached child)", + screens: []ScreenDef{ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 768, s: 1.5, parent: ScreenDefParent{id: 1, align: "le", offset: 10}}, + {id: 3, w: 1024, h: 768, s: 1.25, parent: ScreenDefParent{id: 2, align: "b", offset: 100}}, + {id: 4, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 3, align: "r", offset: 500}}, + }, + }, + { + name: "Multiple de-intersection", + screens: []ScreenDef{ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 1, align: "be", offset: 0}}, + {id: 3, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 2, align: "b", offset: 300}}, + {id: 4, w: 1024, h: 768, s: 1.5, parent: ScreenDefParent{id: 2, align: "le", offset: 100}}, + {id: 5, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 4, align: "be", offset: 100}}, + }, + }, + { + name: "Multiple de-intersection (left-side)", + screens: []ScreenDef{ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 1, align: "le", offset: 0}}, + {id: 3, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 2, align: "b", offset: 300}}, + {id: 4, w: 1024, h: 768, s: 1.5, parent: ScreenDefParent{id: 2, align: "le", offset: 100}}, + {id: 5, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 4, align: "be", offset: 100}}, + }, + }, + { + name: "Parent de-intersection child offset", + screens: []ScreenDef{ + {id: 1, w: 1600, h: 1600, s: 1.5}, + {id: 2, w: 800, h: 800, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}}, + {id: 3, w: 800, h: 800, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 800}}, + {id: 4, w: 800, h: 1600, s: 1, parent: ScreenDefParent{id: 2, align: "r", offset: 0}}, + }, + }, + }, + } + + parsedLayouts := []ParsedLayout{} + + for _, section := range layouts { + for _, layout := range section { + parsedLayouts = append(parsedLayouts, parseLayout(layout)) + } + } + + return parsedLayouts +} + +// Parse screens layout from easy-to-define ScreenDef for testing to actual Screens layout +func parseLayout(layout ScreensLayout) ParsedLayout { + screens := []*application.Screen{} + + for _, screen := range layout.screens { + var x, y int + w := screen.w + h := screen.h + + if screen.parent.id > 0 { + idx := slices.IndexFunc(screens, func(s *application.Screen) bool { return s.ID == strconv.Itoa(screen.parent.id) }) + parent := screens[idx].Bounds + offset := screen.parent.offset + align := screen.parent.align + align2 := "" + + if len(align) == 2 { + align2 = string(align[1]) + align = string(align[0]) + } + + x = parent.X + y = parent.Y + // t: top, b: bottom, l: left, r: right, e: edge, c: corner + if align == "t" || align == "b" { + x += offset + if align2 == "e" || align2 == "c" { + x += parent.Width + } + if align2 == "e" { + x -= w + } + if align == "t" { + y -= h + } else { + y += parent.Height + } + } else { + y += offset + if align2 == "e" || align2 == "c" { + y += parent.Height + } + if align2 == "e" { + y -= h + } + if align == "l" { + x -= w + } else { + x += parent.Width + } + } + } + name := screen.name + if name == "" { + name = "Display" + strconv.Itoa(screen.id) + } + screens = append(screens, &application.Screen{ + ID: strconv.Itoa(screen.id), + Name: name, + ScaleFactor: float32(math.Round(float64(screen.s)*100) / 100), + X: x, + Y: y, + Size: application.Size{Width: w, Height: h}, + Bounds: application.Rect{X: x, Y: y, Width: w, Height: h}, + PhysicalBounds: application.Rect{X: x, Y: y, Width: w, Height: h}, + WorkArea: application.Rect{X: x, Y: y, Width: w, Height: h - int(40*screen.s)}, + PhysicalWorkArea: application.Rect{X: x, Y: y, Width: w, Height: h - int(40*screen.s)}, + IsPrimary: screen.id == 1, + Rotation: 0, + }) + } + return ParsedLayout{ + name: layout.name, + screens: screens, + } +} + +func matchRects(r1, r2 application.Rect) error { + threshold := 1.0 + if math.Abs(float64(r1.X-r2.X)) > threshold || + math.Abs(float64(r1.Y-r2.Y)) > threshold || + math.Abs(float64(r1.Width-r2.Width)) > threshold || + math.Abs(float64(r1.Height-r2.Height)) > threshold { + return fmt.Errorf("%v != %v", r1, r2) + } + return nil +} + +// Test screens layout (DPI transformation) +func TestScreenManager_ScreensLayout(t *testing.T) { + sm := application.ScreenManager{} + + t.Run("Child scaled", func(t *testing.T) { + is := is.New(t) + + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1}, + {id: 2, w: 1200, h: 1200, s: 1.5, parent: ScreenDefParent{id: 1, align: "r", offset: 600}}, + }}) + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + + screens := sm.GetAll() + is.Equal(len(screens), 2) // 2 screens + is.Equal(screens[0].PhysicalBounds, application.Rect{X: 0, Y: 0, Width: 1200, Height: 1200}) // Parent physical bounds + is.Equal(screens[0].Bounds, screens[0].PhysicalBounds) // Parent no scaling + is.Equal(screens[1].PhysicalBounds, application.Rect{X: 1200, Y: 600, Width: 1200, Height: 1200}) // Child physical bounds + is.Equal(screens[1].Bounds, application.Rect{X: 1200, Y: 600, Width: 800, Height: 800}) // Child DIP bounds + }) + + t.Run("Parent scaled", func(t *testing.T) { + is := is.New(t) + + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5}, + {id: 2, w: 1200, h: 1200, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 600}}, + }}) + + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + + screens := sm.GetAll() + is.Equal(len(screens), 2) // 2 screens + is.Equal(screens[0].PhysicalBounds, application.Rect{X: 0, Y: 0, Width: 1200, Height: 1200}) // Parent physical bounds + is.Equal(screens[0].Bounds, application.Rect{X: 0, Y: 0, Width: 800, Height: 800}) // Parent DIP bounds + is.Equal(screens[1].PhysicalBounds, application.Rect{X: 1200, Y: 600, Width: 1200, Height: 1200}) // Child physical bounds + is.Equal(screens[1].Bounds, application.Rect{X: 800, Y: 400, Width: 1200, Height: 1200}) // Child DIP bounds + }) +} + +// Test basic transformation between physical and DIP coordinates +func TestScreenManager_BasicTranformation(t *testing.T) { + sm := application.ScreenManager{} + is := is.New(t) + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1}, + {id: 2, w: 1200, h: 1200, s: 1.5, parent: ScreenDefParent{id: 1, align: "r", offset: 600}}, + }}) + + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + + pt := application.Point{X: 100, Y: 100} + is.Equal(sm.DipToPhysicalPoint(pt), pt) // DipToPhysicalPoint screen1 + is.Equal(sm.PhysicalToDipPoint(pt), pt) // PhysicalToDipPoint screen1 + + ptDip := application.Point{X: 1300, Y: 700} + ptPhysical := application.Point{X: 1350, Y: 750} + is.Equal(sm.DipToPhysicalPoint(ptDip), ptPhysical) // DipToPhysicalPoint screen2 + is.Equal(sm.PhysicalToDipPoint(ptPhysical), ptDip) // PhysicalToDipPoint screen2 + + rect := application.Rect{X: 100, Y: 100, Width: 200, Height: 300} + is.Equal(sm.DipToPhysicalRect(rect), rect) // DipToPhysicalRect screen1 + is.Equal(sm.PhysicalToDipRect(rect), rect) // DipToPhysicalRect screen1 + + rectDip := application.Rect{X: 1300, Y: 700, Width: 200, Height: 300} + rectPhysical := application.Rect{X: 1350, Y: 750, Width: 300, Height: 450} + is.Equal(sm.DipToPhysicalRect(rectDip), rectPhysical) // DipToPhysicalRect screen2 + is.Equal(sm.PhysicalToDipRect(rectPhysical), rectDip) // DipToPhysicalRect screen2 + + rectDip = application.Rect{X: 2200, Y: 250, Width: 200, Height: 300} + rectPhysical = application.Rect{X: 2700, Y: 75, Width: 300, Height: 450} + is.Equal(sm.DipToPhysicalRect(rectDip), rectPhysical) // DipToPhysicalRect outside screen2 + is.Equal(sm.PhysicalToDipRect(rectPhysical), rectDip) // DipToPhysicalRect outside screen2 +} + +func TestScreenManager_PrimaryScreen(t *testing.T) { + sm := application.ScreenManager{} + is := is.New(t) + + for _, layout := range exampleLayouts() { + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + is.Equal(sm.GetPrimary(), layout.screens[0]) // Primary screen + } + + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5}, + {id: 2, w: 1200, h: 1200, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 600}}, + }}) + + layout.screens[0], layout.screens[1] = layout.screens[1], layout.screens[0] + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + is.Equal(sm.GetPrimary(), layout.screens[1]) // Primary screen + + layout.screens[1].IsPrimary = false + err = sm.LayoutScreens(layout.screens) + is.True(err != nil) // Should error when no primary screen found +} + +// Test edge alignment between transformation +// (points and rects on the screen edge should transform to the same precise edge position) +func TestScreenManager_EdgeAlign(t *testing.T) { + sm := application.ScreenManager{} + is := is.New(t) + + for _, layout := range exampleLayouts() { + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + for _, screen := range sm.GetAll() { + ptOriginDip := screen.Bounds.Origin() + ptOriginPhysical := screen.PhysicalBounds.Origin() + ptCornerDip := screen.Bounds.InsideCorner() + ptCornerPhysical := screen.PhysicalBounds.InsideCorner() + + is.Equal(sm.DipToPhysicalPoint(ptOriginDip), ptOriginPhysical) // DipToPhysicalPoint Origin + is.Equal(sm.PhysicalToDipPoint(ptOriginPhysical), ptOriginDip) // PhysicalToDipPoint Origin + is.Equal(sm.DipToPhysicalPoint(ptCornerDip), ptCornerPhysical) // DipToPhysicalPoint Corner + is.Equal(sm.PhysicalToDipPoint(ptCornerPhysical), ptCornerDip) // PhysicalToDipPoint Corner + + rectOriginDip := application.Rect{X: ptOriginDip.X, Y: ptOriginDip.Y, Width: 100, Height: 100} + rectOriginPhysical := application.Rect{X: ptOriginPhysical.X, Y: ptOriginPhysical.Y, Width: 100, Height: 100} + rectCornerDip := application.Rect{X: ptCornerDip.X - 99, Y: ptCornerDip.Y - 99, Width: 100, Height: 100} + rectCornerPhysical := application.Rect{X: ptCornerPhysical.X - 99, Y: ptCornerPhysical.Y - 99, Width: 100, Height: 100} + + is.Equal(sm.DipToPhysicalRect(rectOriginDip).Origin(), rectOriginPhysical.Origin()) // DipToPhysicalRect Origin + is.Equal(sm.PhysicalToDipRect(rectOriginPhysical).Origin(), rectOriginDip.Origin()) // PhysicalToDipRect Origin + is.Equal(sm.DipToPhysicalRect(rectCornerDip).Corner(), rectCornerPhysical.Corner()) // DipToPhysicalRect Corner + is.Equal(sm.PhysicalToDipRect(rectCornerPhysical).Corner(), rectCornerDip.Corner()) // PhysicalToDipRect Corner + } + } +} + +func TestScreenManager_ProbePoints(t *testing.T) { + sm := application.ScreenManager{} + is := is.New(t) + threshold := 1.0 + steps := 3 + + for _, layout := range exampleLayouts() { + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + for _, screen := range sm.GetAll() { + for i := 0; i <= 1; i++ { + isDip := (i == 0) + + var b application.Rect + if isDip { + b = screen.Bounds + } else { + b = screen.PhysicalBounds + } + + xStep := b.Width / steps + yStep := b.Height / steps + if xStep < 1 { + xStep = 1 + } + if yStep < 1 { + yStep = 1 + } + pt := b.Origin() + xDone := false + yDone := false + + for !yDone { + if pt.Y > b.InsideCorner().Y { + pt.Y = b.InsideCorner().Y + yDone = true + } + + pt.X = b.X + xDone = false + + for !xDone { + if pt.X > b.InsideCorner().X { + pt.X = b.InsideCorner().X + xDone = true + } + var ptDblTransformed application.Point + + if isDip { + ptDblTransformed = sm.PhysicalToDipPoint(sm.DipToPhysicalPoint(pt)) + } else { + ptDblTransformed = sm.DipToPhysicalPoint(sm.PhysicalToDipPoint(pt)) + } + + is.True(math.Abs(float64(ptDblTransformed.X-pt.X)) <= threshold) + is.True(math.Abs(float64(ptDblTransformed.Y-pt.Y)) <= threshold) + pt.X += xStep + } + pt.Y += yStep + } + } + } + } +} + +// Test transformation drift over time +func TestScreenManager_TransformationDrift(t *testing.T) { + sm := application.ScreenManager{} + is := is.New(t) + + for _, layout := range exampleLayouts() { + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + for _, screen := range sm.GetAll() { + rectPhysicalOriginal := application.Rect{ + X: screen.PhysicalBounds.X + 100, + Y: screen.PhysicalBounds.Y + 100, + Width: 123, + Height: 123, + } + + // Slide the position to catch any rounding errors + for i := 0; i < 10; i++ { + rectPhysicalOriginal.X++ + rectPhysicalOriginal.Y++ + rectPhysical := rectPhysicalOriginal + // Transform back and forth several times to make sure no drift is introduced over time + for j := 0; j < 10; j++ { + rectDip := sm.PhysicalToDipRect(rectPhysical) + rectPhysical = sm.DipToPhysicalRect(rectDip) + } + is.NoErr(matchRects(rectPhysical, rectPhysicalOriginal)) + } + } + } +} + +func TestScreenManager_ScreenNearestRect(t *testing.T) { + sm := application.ScreenManager{} + is := is.New(t) + + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + {id: 2, w: 1920, h: 1080, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI`}, + {id: 3, w: 1920, h: 1080, s: 1.25, parent: ScreenDefParent{id: 1, align: "l", offset: 0}, name: `23" FHD 96DPI (125%)`}, + }}) + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + + type Rects map[string][]application.Rect + + t.Run("DIP rects", func(t *testing.T) { + is := is.New(t) + rects := Rects{ + "1": []application.Rect{ + {X: -150, Y: 260, Width: 400, Height: 300}, + {X: -250, Y: 750, Width: 400, Height: 300}, + {X: -450, Y: 950, Width: 400, Height: 300}, + {X: 800, Y: 1350, Width: 400, Height: 300}, + {X: 2000, Y: 100, Width: 400, Height: 300}, + {X: 2100, Y: 950, Width: 400, Height: 300}, + {X: 2350, Y: 1200, Width: 400, Height: 300}, + }, + "2": []application.Rect{ + {X: 2100, Y: 50, Width: 400, Height: 300}, + {X: 2150, Y: 950, Width: 400, Height: 300}, + {X: 2450, Y: 1150, Width: 400, Height: 300}, + {X: 4300, Y: 400, Width: 400, Height: 300}, + }, + "3": []application.Rect{ + {X: -2000, Y: 100, Width: 400, Height: 300}, + {X: -220, Y: 200, Width: 400, Height: 300}, + {X: -300, Y: 750, Width: 400, Height: 300}, + {X: -500, Y: 900, Width: 400, Height: 300}, + }, + } + + for screenID, screenRects := range rects { + for _, rect := range screenRects { + screen := sm.ScreenNearestDipRect(rect) + is.Equal(screen.ID, screenID) + } + } + }) + t.Run("Physical rects", func(t *testing.T) { + is := is.New(t) + rects := Rects{ + "1": []application.Rect{ + {X: -150, Y: 100, Width: 400, Height: 300}, + {X: -250, Y: 1500, Width: 400, Height: 300}, + {X: 3600, Y: 100, Width: 400, Height: 300}, + }, + "2": []application.Rect{ + {X: 3700, Y: 100, Width: 400, Height: 300}, + {X: 4000, Y: 1150, Width: 400, Height: 300}, + }, + "3": []application.Rect{ + {X: -250, Y: 100, Width: 400, Height: 300}, + {X: -300, Y: 950, Width: 400, Height: 300}, + {X: -1000, Y: 1000, Width: 400, Height: 300}, + }, + } + + for screenID, screenRects := range rects { + for _, rect := range screenRects { + screen := sm.ScreenNearestPhysicalRect(rect) + is.Equal(screen.ID, screenID) + } + } + }) + + // DIP rect is near screen1 but when transformed becomes near screen2. + // To have a consistent transformation back & forth, screen nearest physical rect + // should be the one given by ScreenNearestDipRect + t.Run("Edge case 1", func(t *testing.T) { + is := is.New(t) + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1}, + {id: 2, w: 1200, h: 1300, s: 1.5, parent: ScreenDefParent{id: 1, align: "r", offset: -20}}, + }}) + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + + rectDip := application.Rect{X: 1020, Y: 800, Width: 400, Height: 300} + rectPhysical := sm.DipToPhysicalRect(rectDip) + + screenDip := sm.ScreenNearestDipRect(rectDip) + screenPhysical := sm.ScreenNearestPhysicalRect(rectPhysical) + is.Equal(screenDip.ID, "2") // screenDip + is.Equal(screenPhysical.ID, "2") // screenPhysical + + rectDblTransformed := sm.PhysicalToDipRect(rectPhysical) + is.NoErr(matchRects(rectDblTransformed, rectDip)) // double transformation + }) +} + +// Unsolved edge cases +func TestScreenManager_UnsolvedEdgeCases(t *testing.T) { + sm := application.ScreenManager{} + is := is.New(t) + + // Edge case 1: invalid DIP rect location + // there could be a setup where some dip rects locations are invalid, meaning that there's no + // physical rect that could produce that dip rect at this location + // Not sure how to solve this scenario + t.Run("Edge case 1: invalid dip rect", func(t *testing.T) { + t.Skip("Unsolved edge case") + is := is.New(t) + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1}, + {id: 2, w: 1200, h: 1100, s: 1.5, parent: ScreenDefParent{id: 1, align: "r", offset: 0}}, + }}) + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + + rectDip := application.Rect{X: 1050, Y: 700, Width: 400, Height: 300} + rectPhysical := sm.DipToPhysicalRect(rectDip) + + screenDip := sm.ScreenNearestDipRect(rectDip) + screenPhysical := sm.ScreenNearestPhysicalRect(rectPhysical) + is.Equal(screenDip.ID, screenPhysical.ID) + + rectDblTransformed := sm.PhysicalToDipRect(rectPhysical) + is.NoErr(matchRects(rectDblTransformed, rectDip)) // double transformation + }) + + // Edge case 2: physical rect that changes when double transformed + // there could be a setup where a dip rect at some locations could be produced by two different physical rects + // causing one of these physical rects to be changed to the other when double transformed + // Not sure how to solve this scenario + t.Run("Edge case 2: changed physical rect", func(t *testing.T) { + t.Skip("Unsolved edge case") + is := is.New(t) + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5}, + {id: 2, w: 1200, h: 900, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}}, + }}) + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + + rectPhysical := application.Rect{X: 1050, Y: 890, Width: 400, Height: 300} + rectDblTransformed := sm.DipToPhysicalRect(sm.PhysicalToDipRect(rectPhysical)) + is.NoErr(matchRects(rectDblTransformed, rectPhysical)) // double transformation + }) +} + +func BenchmarkScreenManager_LayoutScreens(b *testing.B) { + sm := application.ScreenManager{} + layouts := exampleLayouts() + screens := layouts[3].screens + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + sm.LayoutScreens(screens) + } +} + +func BenchmarkScreenManager_TransformPoint(b *testing.B) { + sm := application.ScreenManager{} + layouts := exampleLayouts() + screens := layouts[3].screens + sm.LayoutScreens(screens) + + pt := application.Point{X: 500, Y: 500} + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + sm.DipToPhysicalPoint(pt) + } +} + +func BenchmarkScreenManager_TransformRect(b *testing.B) { + sm := application.ScreenManager{} + layouts := exampleLayouts() + screens := layouts[3].screens + sm.LayoutScreens(screens) + + rect := application.Rect{X: 500, Y: 500, Width: 800, Height: 600} + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + sm.DipToPhysicalRect(rect) + } +} diff --git a/v3/pkg/application/services.go b/v3/pkg/application/services.go new file mode 100644 index 000000000..607e4c586 --- /dev/null +++ b/v3/pkg/application/services.go @@ -0,0 +1,134 @@ +package application + +import ( + "context" + "reflect" +) + +// Service wraps a bound type instance. +// The zero value of Service is invalid. +// Valid values may only be obtained by calling [NewService]. +type Service struct { + instance any + options ServiceOptions +} + +// ServiceOptions provides optional parameters for calls to [NewService]. +type ServiceOptions struct { + // Name can be set to override the name of the service + // for logging and debugging purposes. + // + // If empty, it will default + // either to the value obtained through the [ServiceName] interface, + // or to the type name. + Name string + + // If the service instance implements [http.Handler], + // it will be mounted on the internal asset server + // at the prefix specified by Route. + Route string + + // MarshalError will be called if non-nil + // to marshal to JSON the error values returned by this service's methods. + // + // MarshalError is not allowed to fail, + // but it may return a nil slice to fall back + // to the globally configured error handler. + // + // If the returned slice is not nil, it must contain valid JSON. + MarshalError func(error) []byte +} + +// DefaultServiceOptions specifies the default values of service options, +// used when no [ServiceOptions] instance is provided to [NewService]. +var DefaultServiceOptions = ServiceOptions{} + +// NewService returns a Service value wrapping the given pointer. +// If T is not a concrete named type, the returned value is invalid. +func NewService[T any](instance *T) Service { + return Service{instance, DefaultServiceOptions} +} + +// NewServiceWithOptions returns a Service value wrapping the given pointer +// and specifying the given service options. +// If T is not a concrete named type, the returned value is invalid. +func NewServiceWithOptions[T any](instance *T, options ServiceOptions) Service { + service := NewService(instance) // Delegate to NewService so that the static analyser may detect T. Do not remove this call. + service.options = options + return service +} + +// Instance returns the service instance provided to [NewService]. +func (s Service) Instance() any { + return s.instance +} + +// ServiceName returns the name of the service +// +// This is an *optional* method that may be implemented by service instances. +// It is used for logging and debugging purposes. +// +// If a non-empty name is provided with [ServiceOptions], +// it takes precedence over the one returned by the ServiceName method. +type ServiceName interface { + ServiceName() string +} + +// ServiceStartup is an *optional* method that may be implemented by service instances. +// +// This method will be called during application startup and will receive a copy of the options +// specified at creation time. It can be used for initialising resources. +// +// The context will be valid as long as the application is running, +// and will be cancelled right before shutdown. +// +// Services are guaranteed to receive the startup notification +// in the exact order in which they were either +// listed in [Options.Services] or registered with [App.RegisterService], +// with those from [Options.Services] coming first. +// +// If the return value is non-nil, the startup process aborts +// and [App.Run] returns the error wrapped with [fmt.Errorf] +// in a user-friendly message comprising the service name. +// The original error can be retrieved either by calling the Unwrap method +// or through the [errors.As] API. +// +// When that happens, service instances that have been already initialised +// receive a shutdown notification. +type ServiceStartup interface { + ServiceStartup(ctx context.Context, options ServiceOptions) error +} + +// ServiceShutdown is an *optional* method that may be implemented by service instances. +// +// This method will be called during application shutdown. It can be used for cleaning up resources. +// If a service has received a startup notification, +// then it is guaranteed to receive a shutdown notification too, +// except in case of unhandled panics during shutdown. +// +// Services receive shutdown notifications in reverse registration order, +// after all user-provided shutdown hooks have run (see [App.OnShutdown]). +// +// If the return value is non-nil, it is passed to the application's +// configured error handler at [Options.ErrorHandler], +// wrapped with [fmt.Errorf] in a user-friendly message comprising the service name. +// The default behaviour is to log the error along with the service name. +// The original error can be retrieved either by calling the Unwrap method +// or through the [errors.As] API. +type ServiceShutdown interface { + ServiceShutdown() error +} + +func getServiceName(service Service) string { + if service.options.Name != "" { + return service.options.Name + } + + // Check if the service implements the ServiceName interface + if s, ok := service.Instance().(ServiceName); ok { + return s.ServiceName() + } + + // Finally, get the name from the type. + return reflect.TypeOf(service.Instance()).Elem().String() +} diff --git a/v3/pkg/application/services_test.go b/v3/pkg/application/services_test.go new file mode 100644 index 000000000..79fbb64ea --- /dev/null +++ b/v3/pkg/application/services_test.go @@ -0,0 +1,203 @@ +package application + +import ( + "context" + "testing" +) + +// Test service implementations +type testService struct { + name string +} + +func (s *testService) ServiceName() string { + return s.name +} + +type testServiceWithStartup struct { + started bool +} + +func (s *testServiceWithStartup) ServiceStartup(ctx context.Context, options ServiceOptions) error { + s.started = true + return nil +} + +type testServiceWithShutdown struct { + shutdown bool +} + +func (s *testServiceWithShutdown) ServiceShutdown() error { + s.shutdown = true + return nil +} + +type testServiceNoInterface struct { + value int +} + +func TestNewService(t *testing.T) { + svc := &testService{name: "test"} + service := NewService(svc) + + if service.instance == nil { + t.Error("NewService should set instance") + } + if service.Instance() != svc { + t.Error("Instance() should return the original service") + } +} + +func TestNewServiceWithOptions(t *testing.T) { + svc := &testService{name: "original"} + opts := ServiceOptions{ + Name: "custom-name", + Route: "/api", + } + service := NewServiceWithOptions(svc, opts) + + if service.Instance() != svc { + t.Error("Instance() should return the original service") + } + if service.options.Name != "custom-name" { + t.Errorf("options.Name = %q, want %q", service.options.Name, "custom-name") + } + if service.options.Route != "/api" { + t.Errorf("options.Route = %q, want %q", service.options.Route, "/api") + } +} + +func TestGetServiceName_FromOptions(t *testing.T) { + svc := &testService{name: "service-name"} + opts := ServiceOptions{Name: "options-name"} + service := NewServiceWithOptions(svc, opts) + + name := getServiceName(service) + if name != "options-name" { + t.Errorf("getServiceName() = %q, want %q (options takes precedence)", name, "options-name") + } +} + +func TestGetServiceName_FromInterface(t *testing.T) { + svc := &testService{name: "interface-name"} + service := NewService(svc) + + name := getServiceName(service) + if name != "interface-name" { + t.Errorf("getServiceName() = %q, want %q (from interface)", name, "interface-name") + } +} + +func TestGetServiceName_FromType(t *testing.T) { + svc := &testServiceNoInterface{value: 42} + service := NewService(svc) + + name := getServiceName(service) + // Should contain the type name + if name == "" { + t.Error("getServiceName() should return type name for services without ServiceName interface") + } + // The name should contain "testServiceNoInterface" + expected := "application.testServiceNoInterface" + if name != expected { + t.Errorf("getServiceName() = %q, want %q", name, expected) + } +} + +func TestService_Instance(t *testing.T) { + svc := &testService{name: "test"} + service := NewService(svc) + + instance := service.Instance() + if instance == nil { + t.Error("Instance() should not return nil") + } + + // Type assertion to verify it's the correct type + if _, ok := instance.(*testService); !ok { + t.Error("Instance() should return the correct type") + } +} + +func TestDefaultServiceOptions(t *testing.T) { + // Verify DefaultServiceOptions is zero-valued + if DefaultServiceOptions.Name != "" { + t.Errorf("DefaultServiceOptions.Name should be empty, got %q", DefaultServiceOptions.Name) + } + if DefaultServiceOptions.Route != "" { + t.Errorf("DefaultServiceOptions.Route should be empty, got %q", DefaultServiceOptions.Route) + } + if DefaultServiceOptions.MarshalError != nil { + t.Error("DefaultServiceOptions.MarshalError should be nil") + } +} + +func TestNewService_UsesDefaultOptions(t *testing.T) { + svc := &testService{name: "test"} + service := NewService(svc) + + // Service created with NewService should use DefaultServiceOptions + if service.options.Name != DefaultServiceOptions.Name { + t.Error("NewService should use DefaultServiceOptions") + } +} + +func TestServiceOptions_WithMarshalError(t *testing.T) { + customMarshal := func(err error) []byte { + return []byte(`{"error": "custom"}`) + } + + svc := &testService{name: "test"} + opts := ServiceOptions{ + MarshalError: customMarshal, + } + service := NewServiceWithOptions(svc, opts) + + if service.options.MarshalError == nil { + t.Error("MarshalError should be set") + } + + result := service.options.MarshalError(nil) + expected := `{"error": "custom"}` + if string(result) != expected { + t.Errorf("MarshalError result = %q, want %q", string(result), expected) + } +} + +func TestServiceStartupInterface(t *testing.T) { + svc := &testServiceWithStartup{} + service := NewService(svc) + + // Verify the service implements ServiceStartup + instance := service.Instance() + if startup, ok := instance.(ServiceStartup); ok { + err := startup.ServiceStartup(context.Background(), ServiceOptions{}) + if err != nil { + t.Errorf("ServiceStartup returned error: %v", err) + } + if !svc.started { + t.Error("ServiceStartup should have been called") + } + } else { + t.Error("testServiceWithStartup should implement ServiceStartup") + } +} + +func TestServiceShutdownInterface(t *testing.T) { + svc := &testServiceWithShutdown{} + service := NewService(svc) + + // Verify the service implements ServiceShutdown + instance := service.Instance() + if shutdown, ok := instance.(ServiceShutdown); ok { + err := shutdown.ServiceShutdown() + if err != nil { + t.Errorf("ServiceShutdown returned error: %v", err) + } + if !svc.shutdown { + t.Error("ServiceShutdown should have been called") + } + } else { + t.Error("testServiceWithShutdown should implement ServiceShutdown") + } +} diff --git a/v3/pkg/application/signal_handler_android.go b/v3/pkg/application/signal_handler_android.go new file mode 100644 index 000000000..61c8a297c --- /dev/null +++ b/v3/pkg/application/signal_handler_android.go @@ -0,0 +1,19 @@ +//go:build android + +package application + +import ( + "os" +) + +// setupSignalHandler sets up signal handling for Android +// On Android, we don't handle Unix signals directly as the app lifecycle +// is managed by the Android runtime +func setupSignalHandler() { + // No-op on Android - lifecycle managed by Android framework +} + +// handleSignal processes a signal +func handleSignal(_ os.Signal) { + // No-op on Android +} diff --git a/v3/pkg/application/signal_handler_desktop.go b/v3/pkg/application/signal_handler_desktop.go new file mode 100644 index 000000000..c4f756c94 --- /dev/null +++ b/v3/pkg/application/signal_handler_desktop.go @@ -0,0 +1,20 @@ +//go:build !ios + +package application + +import ( + "os" + + "github.com/wailsapp/wails/v3/internal/signal" +) + +// setupSignalHandler sets up signal handling for desktop platforms +func (a *App) setupSignalHandler(options Options) { + if !options.DisableDefaultSignalHandler { + a.signalHandler = signal.NewSignalHandler(a.Quit) + a.signalHandler.Logger = a.Logger + a.signalHandler.ExitMessage = func(sig os.Signal) string { + return "Quitting application..." + } + } +} diff --git a/v3/pkg/application/signal_handler_ios.go b/v3/pkg/application/signal_handler_ios.go new file mode 100644 index 000000000..b16713811 --- /dev/null +++ b/v3/pkg/application/signal_handler_ios.go @@ -0,0 +1,11 @@ +//go:build ios + +package application + +// setupSignalHandler is a no-op on iOS as signal handling is not supported +// iOS apps run in a sandboxed environment where signal handling is restricted +// and can cause crashes if attempted +func (app *App) setupSignalHandler(options Options) { + // No signal handling on iOS - the OS manages app lifecycle + // Signal handlers would cause crashes due to iOS sandbox restrictions +} \ No newline at end of file diff --git a/v3/pkg/application/signal_handler_types_android.go b/v3/pkg/application/signal_handler_types_android.go new file mode 100644 index 000000000..ed5835c20 --- /dev/null +++ b/v3/pkg/application/signal_handler_types_android.go @@ -0,0 +1,8 @@ +//go:build android + +package application + +import "os" + +// signalType is a placeholder type for Android where signals are not used +type signalType = os.Signal diff --git a/v3/pkg/application/signal_handler_types_desktop.go b/v3/pkg/application/signal_handler_types_desktop.go new file mode 100644 index 000000000..5b7e5c7d9 --- /dev/null +++ b/v3/pkg/application/signal_handler_types_desktop.go @@ -0,0 +1,10 @@ +//go:build !ios + +package application + +import "github.com/wailsapp/wails/v3/internal/signal" + +// platformSignalHandler holds the signal handler for desktop platforms +type platformSignalHandler struct { + signalHandler *signal.SignalHandler +} \ No newline at end of file diff --git a/v3/pkg/application/signal_handler_types_ios.go b/v3/pkg/application/signal_handler_types_ios.go new file mode 100644 index 000000000..1e5c640d7 --- /dev/null +++ b/v3/pkg/application/signal_handler_types_ios.go @@ -0,0 +1,8 @@ +//go:build ios + +package application + +// platformSignalHandler is empty on iOS as signal handling is not supported +type platformSignalHandler struct { + // No signal handler on iOS +} \ No newline at end of file diff --git a/v3/pkg/application/single_instance.go b/v3/pkg/application/single_instance.go new file mode 100644 index 000000000..7f04320eb --- /dev/null +++ b/v3/pkg/application/single_instance.go @@ -0,0 +1,216 @@ +package application + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "errors" + "os" + "path/filepath" + "sync" + + "encoding/json" +) + +var alreadyRunningError = errors.New("application is already running") +var secondInstanceBuffer = make(chan string, 1) +var once sync.Once + +// SecondInstanceData contains information about the second instance launch +type SecondInstanceData struct { + Args []string `json:"args"` + WorkingDir string `json:"workingDir"` + AdditionalData map[string]string `json:"additionalData,omitempty"` +} + +// SingleInstanceOptions defines options for single instance functionality +type SingleInstanceOptions struct { + // UniqueID is used to identify the application instance + // This should be unique per application, e.g. "com.myapp.myapplication" + UniqueID string + + // OnSecondInstanceLaunch is called when a second instance of the application is launched + // The callback receives data about the second instance launch + OnSecondInstanceLaunch func(data SecondInstanceData) + + // AdditionalData allows passing custom data from second instance to first + AdditionalData map[string]string + + // ExitCode is the exit code to use when the second instance exits + ExitCode int + + // EncryptionKey is a 32-byte key used for encrypting instance communication + // If not provided (zero array), data will be sent unencrypted + EncryptionKey [32]byte +} + +// platformLock is the interface that platform-specific lock implementations must implement +type platformLock interface { + // acquire attempts to acquire the lock + acquire(uniqueID string) error + // release releases the lock and cleans up resources + release() + // notify sends data to the first instance + notify(data string) error +} + +// singleInstanceManager handles the single instance functionality +type singleInstanceManager struct { + options *SingleInstanceOptions + lock platformLock + app *App +} + +func newSingleInstanceManager(app *App, options *SingleInstanceOptions) (*singleInstanceManager, error) { + if options == nil { + return nil, nil + } + + manager := &singleInstanceManager{ + options: options, + app: app, + } + + // Launch second instance data listener + once.Do(func() { + go func() { + defer handlePanic() + for encryptedData := range secondInstanceBuffer { + var secondInstanceData SecondInstanceData + var jsonData []byte + var err error + + // Check if encryption key is non-zero + var zeroKey [32]byte + if options.EncryptionKey != zeroKey { + // Try to decrypt the data + jsonData, err = decrypt(options.EncryptionKey, encryptedData) + if err != nil { + continue // Skip invalid data + } + } else { + jsonData = []byte(encryptedData) + } + + if err := json.Unmarshal(jsonData, &secondInstanceData); err == nil && manager.options.OnSecondInstanceLaunch != nil { + manager.options.OnSecondInstanceLaunch(secondInstanceData) + } + } + }() + }) + + // Create platform-specific lock + lock, err := newPlatformLock(manager) + if err != nil { + return nil, err + } + + manager.lock = lock + + // Try to acquire the lock + err = lock.acquire(options.UniqueID) + if err != nil { + return manager, err + } + + return manager, nil +} + +func (m *singleInstanceManager) cleanup() { + if m == nil || m.lock == nil { + return + } + m.lock.release() +} + +// encrypt encrypts data using AES-256-GCM +func encrypt(key [32]byte, plaintext []byte) (string, error) { + block, err := aes.NewCipher(key[:]) + if err != nil { + return "", err + } + + nonce := make([]byte, 12) + if _, err := rand.Read(nonce); err != nil { + return "", err + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + + ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil) + encrypted := append(nonce, ciphertext...) + return base64.StdEncoding.EncodeToString(encrypted), nil +} + +// decrypt decrypts data using AES-256-GCM +func decrypt(key [32]byte, encrypted string) ([]byte, error) { + data, err := base64.StdEncoding.DecodeString(encrypted) + if err != nil { + return nil, err + } + + if len(data) < 12 { + return nil, errors.New("invalid encrypted data") + } + + block, err := aes.NewCipher(key[:]) + if err != nil { + return nil, err + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + nonce := data[:12] + ciphertext := data[12:] + + return aesgcm.Open(nil, nonce, ciphertext, nil) +} + +// notifyFirstInstance sends data to the first instance of the application +func (m *singleInstanceManager) notifyFirstInstance() error { + data := SecondInstanceData{ + Args: os.Args, + WorkingDir: getCurrentWorkingDir(), + AdditionalData: m.options.AdditionalData, + } + + serialized, err := json.Marshal(data) + if err != nil { + return err + } + + // Check if encryption key is non-zero + var zeroKey [32]byte + if m.options.EncryptionKey != zeroKey { + encrypted, err := encrypt(m.options.EncryptionKey, serialized) + if err != nil { + return err + } + return m.lock.notify(encrypted) + } + + return m.lock.notify(string(serialized)) +} + +func getCurrentWorkingDir() string { + dir, err := os.Getwd() + if err != nil { + return "" + } + return dir +} + +// getLockPath returns the path to the lock file for Unix systems +func getLockPath(uniqueID string) string { + // Use system temp directory + tmpDir := os.TempDir() + lockFileName := uniqueID + ".lock" + return filepath.Join(tmpDir, lockFileName) +} diff --git a/v3/pkg/application/single_instance_android.go b/v3/pkg/application/single_instance_android.go new file mode 100644 index 000000000..e08cbc172 --- /dev/null +++ b/v3/pkg/application/single_instance_android.go @@ -0,0 +1,33 @@ +//go:build android + +package application + +// setupSingleInstance sets up single instance on Android +func (a *App) setupSingleInstance() error { + // Android apps handle single instance via launch mode in manifest + return nil +} + +type androidLock struct { + manager *singleInstanceManager +} + +func newPlatformLock(manager *singleInstanceManager) (platformLock, error) { + return &androidLock{ + manager: manager, + }, nil +} + +func (l *androidLock) acquire(uniqueID string) error { + // Android apps handle single instance via launch mode in manifest + return nil +} + +func (l *androidLock) release() { + // Android apps handle single instance via launch mode in manifest +} + +func (l *androidLock) notify(data string) error { + // Android apps handle single instance via launch mode in manifest + return nil +} diff --git a/v3/pkg/application/single_instance_darwin.go b/v3/pkg/application/single_instance_darwin.go new file mode 100644 index 000000000..546bf7259 --- /dev/null +++ b/v3/pkg/application/single_instance_darwin.go @@ -0,0 +1,96 @@ +//go:build darwin && !ios + +package application + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework Cocoa + +#include +#import +#import + +static void SendDataToFirstInstance(char *singleInstanceUniqueId, char* message) { + [[NSDistributedNotificationCenter defaultCenter] + postNotificationName:[NSString stringWithUTF8String:singleInstanceUniqueId] + object:nil + userInfo:@{@"message": [NSString stringWithUTF8String:message]} + deliverImmediately:YES]; +} + +*/ +import "C" +import ( + "os" + "syscall" + "unsafe" +) + +type darwinLock struct { + file *os.File + uniqueID string + manager *singleInstanceManager +} + +func newPlatformLock(manager *singleInstanceManager) (platformLock, error) { + return &darwinLock{ + manager: manager, + }, nil +} + +func (l *darwinLock) acquire(uniqueID string) error { + l.uniqueID = uniqueID + lockFilePath := os.TempDir() + lockFileName := uniqueID + ".lock" + var err error + l.file, err = createLockFile(lockFilePath + "/" + lockFileName) + if err != nil { + return alreadyRunningError + } + return nil +} + +func (l *darwinLock) release() { + if l.file != nil { + syscall.Flock(int(l.file.Fd()), syscall.LOCK_UN) + l.file.Close() + os.Remove(l.file.Name()) + l.file = nil + } +} + +func (l *darwinLock) notify(data string) error { + singleInstanceUniqueId := C.CString(l.uniqueID) + defer C.free(unsafe.Pointer(singleInstanceUniqueId)) + cData := C.CString(data) + defer C.free(unsafe.Pointer(cData)) + + C.SendDataToFirstInstance(singleInstanceUniqueId, cData) + + os.Exit(l.manager.options.ExitCode) + return nil +} + +// CreateLockFile tries to create a file with given name and acquire an +// exclusive lock on it. If the file already exists AND is still locked, it will +// fail. +func createLockFile(filename string) (*os.File, error) { + file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + return nil, err + } + + err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) + if err != nil { + file.Close() + return nil, err + } + + return file, nil +} + +//export handleSecondInstanceData +func handleSecondInstanceData(secondInstanceMessage *C.char) { + message := C.GoString(secondInstanceMessage) + secondInstanceBuffer <- message +} diff --git a/v3/pkg/application/single_instance_ios.go b/v3/pkg/application/single_instance_ios.go new file mode 100644 index 000000000..fd7e8eae3 --- /dev/null +++ b/v3/pkg/application/single_instance_ios.go @@ -0,0 +1,33 @@ +//go:build ios + +package application + +// setupSingleInstance sets up single instance on iOS +func (a *App) setupSingleInstance() error { + // iOS apps are always single instance + return nil +} + +type iosLock struct { + manager *singleInstanceManager +} + +func newPlatformLock(manager *singleInstanceManager) (platformLock, error) { + return &iosLock{ + manager: manager, + }, nil +} + +func (l *iosLock) acquire(uniqueID string) error { + // iOS apps are always single instance + return nil +} + +func (l *iosLock) release() { + // iOS apps are always single instance +} + +func (l *iosLock) notify(data string) error { + // iOS apps are always single instance + return nil +} \ No newline at end of file diff --git a/v3/pkg/application/single_instance_linux.go b/v3/pkg/application/single_instance_linux.go new file mode 100644 index 000000000..1d2a55c18 --- /dev/null +++ b/v3/pkg/application/single_instance_linux.go @@ -0,0 +1,101 @@ +//go:build linux && !android && !server + +package application + +import ( + "errors" + "os" + "strings" + "sync" + "syscall" + + "github.com/godbus/dbus/v5" +) + +type dbusHandler func(string) + +var setup sync.Once + +func (f dbusHandler) SendMessage(message string) *dbus.Error { + f(message) + return nil +} + +type linuxLock struct { + file *os.File + uniqueID string + dbusPath string + dbusName string + manager *singleInstanceManager +} + +func newPlatformLock(manager *singleInstanceManager) (platformLock, error) { + return &linuxLock{ + manager: manager, + }, nil +} + +func (l *linuxLock) acquire(uniqueID string) error { + if uniqueID == "" { + return errors.New("UniqueID is required for single instance lock") + } + + id := "wails_app_" + strings.ReplaceAll(strings.ReplaceAll(uniqueID, "-", "_"), ".", "_") + + l.dbusName = "org." + id + ".SingleInstance" + l.dbusPath = "/org/" + id + "/SingleInstance" + + conn, err := dbus.ConnectSessionBus() + // if we will reach any error during establishing connection or sending message we will just continue. + // It should not be the case that such thing will happen actually, but just in case. + if err != nil { + return err + } + + setup.Do(func() { + f := dbusHandler(func(message string) { + secondInstanceBuffer <- message + }) + + err = conn.Export(f, dbus.ObjectPath(l.dbusPath), l.dbusName) + }) + if err != nil { + return err + } + + reply, err := conn.RequestName(l.dbusName, dbus.NameFlagDoNotQueue) + if err != nil { + return err + } + + // if name already taken, try to send args to existing instance, if no success just launch new instance + if reply == dbus.RequestNameReplyExists { + return alreadyRunningError + } + return nil +} + +func (l *linuxLock) release() { + if l.file != nil { + syscall.Flock(int(l.file.Fd()), syscall.LOCK_UN) + l.file.Close() + os.Remove(l.file.Name()) + l.file = nil + } +} + +func (l *linuxLock) notify(data string) error { + conn, err := dbus.ConnectSessionBus() + // if we will reach any error during establishing connection or sending message we will just continue. + // It should not be the case that such thing will happen actually, but just in case. + if err != nil { + return err + } + + err = conn.Object(l.dbusName, dbus.ObjectPath(l.dbusPath)).Call(l.dbusName+".SendMessage", 0, data).Store() + if err != nil { + return err + } + os.Exit(l.manager.options.ExitCode) + return nil +} diff --git a/v3/pkg/application/single_instance_test.go b/v3/pkg/application/single_instance_test.go new file mode 100644 index 000000000..230f8e07d --- /dev/null +++ b/v3/pkg/application/single_instance_test.go @@ -0,0 +1,229 @@ +package application + +import ( + "os" + "path/filepath" + "testing" +) + +func TestEncryptDecrypt(t *testing.T) { + key := [32]byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + } + + plaintext := []byte("Hello, World! This is a test message.") + + encrypted, err := encrypt(key, plaintext) + if err != nil { + t.Fatalf("encrypt failed: %v", err) + } + + if encrypted == "" { + t.Error("encrypted should not be empty") + } + + if encrypted == string(plaintext) { + t.Error("encrypted should be different from plaintext") + } + + decrypted, err := decrypt(key, encrypted) + if err != nil { + t.Fatalf("decrypt failed: %v", err) + } + + if string(decrypted) != string(plaintext) { + t.Errorf("decrypted = %q, want %q", string(decrypted), string(plaintext)) + } +} + +func TestEncryptDecrypt_EmptyData(t *testing.T) { + key := [32]byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + } + + plaintext := []byte{} + + encrypted, err := encrypt(key, plaintext) + if err != nil { + t.Fatalf("encrypt failed: %v", err) + } + + decrypted, err := decrypt(key, encrypted) + if err != nil { + t.Fatalf("decrypt failed: %v", err) + } + + if len(decrypted) != 0 { + t.Errorf("decrypted should be empty, got %d bytes", len(decrypted)) + } +} + +func TestDecrypt_InvalidData(t *testing.T) { + key := [32]byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + } + + tests := []struct { + name string + data string + }{ + {"invalid base64", "not-valid-base64!!!"}, + {"too short", "YWJj"}, // "abc" base64 encoded (3 bytes) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := decrypt(key, tt.data) + if err == nil { + t.Error("decrypt should return error for invalid data") + } + }) + } +} + +func TestDecrypt_WrongKey(t *testing.T) { + key1 := [32]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f} + + key2 := [32]byte{0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f} + + plaintext := []byte("Secret message") + + encrypted, err := encrypt(key1, plaintext) + if err != nil { + t.Fatalf("encrypt failed: %v", err) + } + + _, err = decrypt(key2, encrypted) + if err == nil { + t.Error("decrypt with wrong key should return error") + } +} + +func TestGetLockPath(t *testing.T) { + uniqueID := "com.example.myapp" + path := getLockPath(uniqueID) + + if path == "" { + t.Error("getLockPath should return non-empty path") + } + + expectedFileName := uniqueID + ".lock" + actualFileName := filepath.Base(path) + if actualFileName != expectedFileName { + t.Errorf("filename = %q, want %q", actualFileName, expectedFileName) + } + + // Path should be in temp directory + // Use filepath.Clean to normalize paths (os.TempDir may have trailing slash on macOS) + tmpDir := filepath.Clean(os.TempDir()) + if filepath.Dir(path) != tmpDir { + t.Errorf("path should be in temp directory %q, got %q", tmpDir, filepath.Dir(path)) + } +} + +func TestGetCurrentWorkingDir(t *testing.T) { + dir := getCurrentWorkingDir() + + // Should return a non-empty path + if dir == "" { + t.Error("getCurrentWorkingDir should return non-empty path") + } + + // Should match os.Getwd() + expected, err := os.Getwd() + if err != nil { + t.Skipf("os.Getwd failed: %v", err) + } + + if dir != expected { + t.Errorf("getCurrentWorkingDir() = %q, want %q", dir, expected) + } +} + +func TestSecondInstanceData_Fields(t *testing.T) { + data := SecondInstanceData{ + Args: []string{"arg1", "arg2"}, + WorkingDir: "/home/user", + AdditionalData: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + } + + if len(data.Args) != 2 { + t.Error("Args not set correctly") + } + if data.WorkingDir != "/home/user" { + t.Error("WorkingDir not set correctly") + } + if len(data.AdditionalData) != 2 { + t.Error("AdditionalData not set correctly") + } +} + +func TestSingleInstanceOptions_Defaults(t *testing.T) { + opts := SingleInstanceOptions{} + + if opts.UniqueID != "" { + t.Error("UniqueID should default to empty string") + } + if opts.OnSecondInstanceLaunch != nil { + t.Error("OnSecondInstanceLaunch should default to nil") + } + if opts.AdditionalData != nil { + t.Error("AdditionalData should default to nil") + } + if opts.ExitCode != 0 { + t.Error("ExitCode should default to 0") + } + var zeroKey [32]byte + if opts.EncryptionKey != zeroKey { + t.Error("EncryptionKey should default to zero array") + } +} + +func TestSingleInstanceManager_Cleanup_Nil(t *testing.T) { + // Calling cleanup on nil manager should not panic + var m *singleInstanceManager + m.cleanup() // Should not panic +} + +func TestSingleInstanceManager_Cleanup_NilLock(t *testing.T) { + // Calling cleanup with nil lock should not panic + m := &singleInstanceManager{} + m.cleanup() // Should not panic +} + +func TestNewSingleInstanceManager_NilOptions(t *testing.T) { + manager, err := newSingleInstanceManager(nil, nil) + if err != nil { + t.Errorf("newSingleInstanceManager(nil, nil) should not return error: %v", err) + } + if manager != nil { + t.Error("newSingleInstanceManager(nil, nil) should return nil manager") + } +} + +func TestAlreadyRunningError(t *testing.T) { + if alreadyRunningError == nil { + t.Error("alreadyRunningError should not be nil") + } + if alreadyRunningError.Error() != "application is already running" { + t.Errorf("alreadyRunningError.Error() = %q", alreadyRunningError.Error()) + } +} diff --git a/v3/pkg/application/single_instance_windows.go b/v3/pkg/application/single_instance_windows.go new file mode 100644 index 000000000..b92b2749a --- /dev/null +++ b/v3/pkg/application/single_instance_windows.go @@ -0,0 +1,129 @@ +//go:build windows + +package application + +import ( + "errors" + "syscall" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/w32" + "golang.org/x/sys/windows" +) + +var ( + user32 = syscall.NewLazyDLL("user32.dll") +) + +type windowsLock struct { + handle syscall.Handle + uniqueID string + msgString string + hwnd w32.HWND + manager *singleInstanceManager + className string + windowName string +} + +func newPlatformLock(manager *singleInstanceManager) (platformLock, error) { + return &windowsLock{ + manager: manager, + }, nil +} + +func (l *windowsLock) acquire(uniqueID string) error { + if uniqueID == "" { + return errors.New("UniqueID is required for single instance lock") + } + + l.uniqueID = uniqueID + id := "wails-app-" + uniqueID + l.className = id + "-sic" + l.windowName = id + "-siw" + mutexName := id + "-sim" + + _, err := windows.CreateMutex(nil, false, windows.StringToUTF16Ptr(mutexName)) + if err != nil { + // Find the window + return alreadyRunningError + } else { + l.hwnd = createEventTargetWindow(l.className, l.windowName) + } + + return nil +} + +func (l *windowsLock) release() { + if l.handle != 0 { + syscall.CloseHandle(l.handle) + l.handle = 0 + } + if l.hwnd != 0 { + w32.DestroyWindow(l.hwnd) + l.hwnd = 0 + } +} + +func (l *windowsLock) notify(data string) error { + + // app is already running + hwnd := w32.FindWindowW(windows.StringToUTF16Ptr(l.className), windows.StringToUTF16Ptr(l.windowName)) + + if hwnd == 0 { + return errors.New("unable to notify other instance") + } + + w32.SendMessageToWindow(hwnd, data) + + return nil +} + +func createEventTargetWindow(className string, windowName string) w32.HWND { + var class w32.WNDCLASSEX + class.Size = uint32(unsafe.Sizeof(class)) + class.Style = 0 + class.WndProc = syscall.NewCallback(wndProc) + class.ClsExtra = 0 + class.WndExtra = 0 + class.Instance = w32.GetModuleHandle("") + class.Icon = 0 + class.Cursor = 0 + class.Background = 0 + class.MenuName = nil + class.ClassName = w32.MustStringToUTF16Ptr(className) + class.IconSm = 0 + + w32.RegisterClassEx(&class) + + // Create hidden message-only window + hwnd := w32.CreateWindowEx( + 0, + w32.MustStringToUTF16Ptr(className), + w32.MustStringToUTF16Ptr(windowName), + 0, + 0, + 0, + 0, + 0, + w32.HWND_MESSAGE, + 0, + w32.GetModuleHandle(""), + nil, + ) + + return hwnd +} + +func wndProc(hwnd w32.HWND, msg uint32, wparam w32.WPARAM, lparam w32.LPARAM) w32.LRESULT { + if msg == w32.WM_COPYDATA { + ldata := (*w32.COPYDATASTRUCT)(unsafe.Pointer(lparam)) + + if ldata.DwData == w32.WMCOPYDATA_SINGLE_INSTANCE_DATA { + serialized := windows.UTF16PtrToString((*uint16)(unsafe.Pointer(ldata.LpData))) + secondInstanceBuffer <- serialized + } + return w32.LRESULT(0) + } + + return w32.DefWindowProc(hwnd, msg, wparam, lparam) +} diff --git a/v3/pkg/application/system_tray_manager.go b/v3/pkg/application/system_tray_manager.go new file mode 100644 index 000000000..16bdf5153 --- /dev/null +++ b/v3/pkg/application/system_tray_manager.go @@ -0,0 +1,44 @@ +package application + +// SystemTrayManager manages system tray-related operations +type SystemTrayManager struct { + app *App +} + +// newSystemTrayManager creates a new SystemTrayManager instance +func newSystemTrayManager(app *App) *SystemTrayManager { + return &SystemTrayManager{ + app: app, + } +} + +// New creates a new system tray +func (stm *SystemTrayManager) New() *SystemTray { + id := stm.getNextID() + newSystemTray := newSystemTray(id) + + stm.app.systemTraysLock.Lock() + stm.app.systemTrays[id] = newSystemTray + stm.app.systemTraysLock.Unlock() + + stm.app.runOrDeferToAppRun(newSystemTray) + + return newSystemTray +} + +// getNextID generates the next system tray ID (internal use) +func (stm *SystemTrayManager) getNextID() uint { + stm.app.systemTrayIDLock.Lock() + defer stm.app.systemTrayIDLock.Unlock() + stm.app.systemTrayID++ + return stm.app.systemTrayID +} + +// destroy destroys a system tray (internal use) +func (stm *SystemTrayManager) destroy(tray *SystemTray) { + // Remove the system tray from the app.systemTrays map + stm.app.systemTraysLock.Lock() + delete(stm.app.systemTrays, tray.id) + stm.app.systemTraysLock.Unlock() + tray.destroy() +} diff --git a/v3/pkg/application/systemtray.go b/v3/pkg/application/systemtray.go new file mode 100644 index 000000000..0735b52e2 --- /dev/null +++ b/v3/pkg/application/systemtray.go @@ -0,0 +1,340 @@ +package application + +import ( + "errors" + "runtime" + "sync" + "time" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +type IconPosition int + +const ( + NSImageNone = iota + NSImageOnly + NSImageLeft + NSImageRight + NSImageBelow + NSImageAbove + NSImageOverlaps + NSImageLeading + NSImageTrailing +) + +type systemTrayImpl interface { + setLabel(label string) + setTooltip(tooltip string) + run() + setIcon(icon []byte) + setMenu(menu *Menu) + setIconPosition(position IconPosition) + setTemplateIcon(icon []byte) + destroy() + setDarkModeIcon(icon []byte) + bounds() (*Rect, error) + getScreen() (*Screen, error) + positionWindow(window Window, offset int) error + openMenu() + Show() + Hide() +} + +type SystemTray struct { + id uint + label string + tooltip string + icon []byte + darkModeIcon []byte + iconPosition IconPosition + + clickHandler func() + rightClickHandler func() + doubleClickHandler func() + rightDoubleClickHandler func() + mouseEnterHandler func() + mouseLeaveHandler func() + onMenuOpen func() + onMenuClose func() + + // Platform specific implementation + impl systemTrayImpl + menu *Menu + isTemplateIcon bool + attachedWindow WindowAttachConfig +} + +func newSystemTray(id uint) *SystemTray { + result := &SystemTray{ + id: id, + label: "", + tooltip: "", + iconPosition: NSImageLeading, + attachedWindow: WindowAttachConfig{ + Window: nil, + Offset: 0, + Debounce: 200 * time.Millisecond, + }, + } + result.clickHandler = result.defaultClickHandler + return result +} + +func (s *SystemTray) SetLabel(label string) { + if s.impl == nil { + s.label = label + return + } + InvokeSync(func() { + s.impl.setLabel(label) + }) +} + +func (s *SystemTray) Label() string { + return s.label +} + +func (s *SystemTray) Run() { + + // exit early if application isn't running. + // app.Run() will call this + if globalApplication == nil || globalApplication.running == false { + return + } + + s.impl = newSystemTrayImpl(s) + + if s.attachedWindow.Window != nil { + // Setup listener + s.attachedWindow.Window.OnWindowEvent(events.Common.WindowLostFocus, func(event *WindowEvent) { + s.attachedWindow.Window.Hide() + // Special handler for Windows + if runtime.GOOS == "windows" { + // We don't do this unless the window has already been shown + if s.attachedWindow.hasBeenShown == false { + return + } + s.attachedWindow.justClosed = true + go func() { + defer handlePanic() + time.Sleep(s.attachedWindow.Debounce) + s.attachedWindow.justClosed = false + }() + } + }) + } + + InvokeSync(s.impl.run) +} + +func (s *SystemTray) PositionWindow(window Window, offset int) error { + if s.impl == nil { + return errors.New("system tray not running") + } + return InvokeSyncWithError(func() error { + return s.impl.positionWindow(window, offset) + }) +} + +func (s *SystemTray) SetIcon(icon []byte) *SystemTray { + if s.impl == nil { + s.icon = icon + } else { + InvokeSync(func() { + s.impl.setIcon(icon) + }) + } + return s +} + +func (s *SystemTray) SetDarkModeIcon(icon []byte) *SystemTray { + if s.impl == nil { + s.darkModeIcon = icon + } else { + InvokeSync(func() { + s.impl.setDarkModeIcon(icon) + }) + } + return s +} + +func (s *SystemTray) SetMenu(menu *Menu) *SystemTray { + if s.impl == nil { + s.menu = menu + } else { + InvokeSync(func() { + s.impl.setMenu(menu) + }) + } + return s +} + +func (s *SystemTray) SetIconPosition(iconPosition IconPosition) *SystemTray { + if s.impl == nil { + s.iconPosition = iconPosition + } else { + InvokeSync(func() { + s.impl.setIconPosition(iconPosition) + }) + } + return s +} + +func (s *SystemTray) SetTemplateIcon(icon []byte) *SystemTray { + if s.impl == nil { + s.icon = icon + s.isTemplateIcon = true + } else { + InvokeSync(func() { + s.impl.setTemplateIcon(icon) + }) + } + return s +} + +func (s *SystemTray) SetTooltip(tooltip string) { + if s.impl == nil { + s.tooltip = tooltip + return + } + InvokeSync(func() { + s.impl.setTooltip(tooltip) + }) +} + +func (s *SystemTray) Destroy() { + globalApplication.SystemTray.destroy(s) +} + +func (s *SystemTray) destroy() { + if s.impl == nil { + return + } + s.impl.destroy() +} + +func (s *SystemTray) OnClick(handler func()) *SystemTray { + s.clickHandler = handler + return s +} + +func (s *SystemTray) OnRightClick(handler func()) *SystemTray { + s.rightClickHandler = handler + return s +} + +func (s *SystemTray) OnDoubleClick(handler func()) *SystemTray { + s.doubleClickHandler = handler + return s +} + +func (s *SystemTray) OnRightDoubleClick(handler func()) *SystemTray { + s.rightDoubleClickHandler = handler + return s +} + +func (s *SystemTray) OnMouseEnter(handler func()) *SystemTray { + s.mouseEnterHandler = handler + return s +} + +func (s *SystemTray) OnMouseLeave(handler func()) *SystemTray { + s.mouseLeaveHandler = handler + return s +} + +func (s *SystemTray) Show() { + if s.impl == nil { + return + } + InvokeSync(func() { + s.impl.Show() + }) +} + +func (s *SystemTray) Hide() { + if s.impl == nil { + return + } + InvokeSync(func() { + s.impl.Hide() + }) +} + +type WindowAttachConfig struct { + // Window is the window to attach to the system tray. If it's null, the request to attach will be ignored. + Window Window + + // Offset indicates the gap in pixels between the system tray and the window + Offset int + + // Debounce is used by Windows to indicate how long to wait before responding to a mouse + // up event on the notification icon. See https://stackoverflow.com/questions/4585283/alternate-showing-hiding-window-when-notify-icon-is-clicked + Debounce time.Duration + + // Indicates that the window has just been closed + justClosed bool + + // Indicates that the window has been shown a first time + hasBeenShown bool + + // Used to ensure that the window state is read on first click + initialClick sync.Once +} + +// AttachWindow attaches a window to the system tray. The window will be shown when the system tray icon is clicked. +// The window will be hidden when the system tray icon is clicked again, or when the window loses focus. +func (s *SystemTray) AttachWindow(window Window) *SystemTray { + s.attachedWindow.Window = window + return s +} + +// WindowOffset sets the gap in pixels between the system tray and the window +func (s *SystemTray) WindowOffset(offset int) *SystemTray { + s.attachedWindow.Offset = offset + return s +} + +// WindowDebounce is used by Windows to indicate how long to wait before responding to a mouse +// up event on the notification icon. This prevents the window from being hidden and then immediately +// shown when the user clicks on the system tray icon. +// See https://stackoverflow.com/questions/4585283/alternate-showing-hiding-window-when-notify-icon-is-clicked +func (s *SystemTray) WindowDebounce(debounce time.Duration) *SystemTray { + s.attachedWindow.Debounce = debounce + return s +} + +func (s *SystemTray) defaultClickHandler() { + if s.attachedWindow.Window == nil { + s.OpenMenu() + return + } + + // Check the initial visibility state + s.attachedWindow.initialClick.Do(func() { + s.attachedWindow.hasBeenShown = s.attachedWindow.Window.IsVisible() + }) + + if runtime.GOOS == "windows" && s.attachedWindow.justClosed { + return + } + + if s.attachedWindow.Window.IsVisible() { + s.attachedWindow.Window.Hide() + } else { + s.attachedWindow.hasBeenShown = true + _ = s.PositionWindow(s.attachedWindow.Window, s.attachedWindow.Offset) + s.attachedWindow.Window.Show().Focus() + } +} + +func (s *SystemTray) OpenMenu() { + if s.menu == nil { + return + } + if s.impl == nil { + return + } + InvokeSync(s.impl.openMenu) +} diff --git a/v3/pkg/application/systemtray_android.go b/v3/pkg/application/systemtray_android.go new file mode 100644 index 000000000..489a58853 --- /dev/null +++ b/v3/pkg/application/systemtray_android.go @@ -0,0 +1,102 @@ +//go:build android + +package application + +// Android doesn't have system tray support +// These are placeholder implementations + +func (t *SystemTray) update() {} + +func (t *SystemTray) setMenu(menu *Menu) { + // Android doesn't have system tray +} + +func (t *SystemTray) close() { + // Android doesn't have system tray +} + +func (t *SystemTray) attachWindow(window *WebviewWindow) { + // Android doesn't have system tray +} + +func (t *SystemTray) detachWindow(windowID uint) { + // Android doesn't have system tray +} + +type androidSystemTray struct { + parent *SystemTray +} + +func newSystemTrayImpl(s *SystemTray) systemTrayImpl { + return &androidSystemTray{ + parent: s, + } +} + +func (s *androidSystemTray) run() { + // Android doesn't have system tray +} + +func (s *androidSystemTray) setLabel(_ string) { + // Android doesn't have system tray +} + +func (s *androidSystemTray) setMenu(_ *Menu) { + // Android doesn't have system tray +} + +func (s *androidSystemTray) setIcon(_ []byte) { + // Android doesn't have system tray +} + +func (s *androidSystemTray) setDarkModeIcon(_ []byte) { + // Android doesn't have system tray +} + +func (s *androidSystemTray) destroy() { + // Android doesn't have system tray +} + +func (s *androidSystemTray) setIconPosition(_ IconPosition) { + // Android doesn't have system tray +} + +func (s *androidSystemTray) positionWindow(_ Window, _ int) error { + return nil +} + +func (s *androidSystemTray) detachWindowPositioning(_ uint) { + // Android doesn't have system tray +} + +func (s *androidSystemTray) setTemplateIcon(_ []byte) { + // Android doesn't have system tray +} + +func (s *androidSystemTray) openMenu() { + // Android doesn't have system tray +} + +func (s *androidSystemTray) setTooltip(_ string) { + // Android doesn't have system tray +} + +func (s *androidSystemTray) bounds() (*Rect, error) { + return nil, nil +} + +func (s *androidSystemTray) getScreen() (*Screen, error) { + screens, err := getScreens() + if err != nil || len(screens) == 0 { + return nil, err + } + return screens[0], nil +} + +func (s *androidSystemTray) Show() { + // Android doesn't have system tray +} + +func (s *androidSystemTray) Hide() { + // Android doesn't have system tray +} diff --git a/v3/pkg/application/systemtray_bench_test.go b/v3/pkg/application/systemtray_bench_test.go new file mode 100644 index 000000000..3691f0f59 --- /dev/null +++ b/v3/pkg/application/systemtray_bench_test.go @@ -0,0 +1,374 @@ +//go:build bench + +package application + +import ( + "testing" + "time" +) + +// Note: SystemTray benchmarks are limited since actual system tray operations +// require platform-specific GUI initialization. These benchmarks focus on +// the Go-side logic that can be tested without a running GUI. + +// BenchmarkSystemTrayCreation measures the cost of creating SystemTray instances +func BenchmarkSystemTrayCreation(b *testing.B) { + for b.Loop() { + tray := newSystemTray(1) + _ = tray + } +} + +// BenchmarkSystemTrayConfiguration measures configuration operations +func BenchmarkSystemTrayConfiguration(b *testing.B) { + b.Run("SetLabel", func(b *testing.B) { + tray := newSystemTray(1) + // impl is nil, so this just sets the field + for b.Loop() { + tray.SetLabel("Test Label") + } + }) + + b.Run("SetTooltip", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.SetTooltip("Test Tooltip") + } + }) + + b.Run("SetIcon", func(b *testing.B) { + tray := newSystemTray(1) + icon := make([]byte, 1024) // 1KB icon data + for b.Loop() { + tray.SetIcon(icon) + } + }) + + b.Run("SetDarkModeIcon", func(b *testing.B) { + tray := newSystemTray(1) + icon := make([]byte, 1024) + for b.Loop() { + tray.SetDarkModeIcon(icon) + } + }) + + b.Run("SetTemplateIcon", func(b *testing.B) { + tray := newSystemTray(1) + icon := make([]byte, 1024) + for b.Loop() { + tray.SetTemplateIcon(icon) + } + }) + + b.Run("SetIconPosition", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.SetIconPosition(NSImageLeading) + } + }) + + b.Run("ChainedConfiguration", func(b *testing.B) { + icon := make([]byte, 1024) + for b.Loop() { + tray := newSystemTray(1) + tray.SetIcon(icon). + SetDarkModeIcon(icon). + SetIconPosition(NSImageLeading) + } + }) +} + +// BenchmarkClickHandlerExecution measures handler registration and invocation +func BenchmarkClickHandlerExecution(b *testing.B) { + b.Run("RegisterClickHandler", func(b *testing.B) { + for b.Loop() { + tray := newSystemTray(1) + tray.OnClick(func() {}) + } + }) + + b.Run("RegisterAllHandlers", func(b *testing.B) { + for b.Loop() { + tray := newSystemTray(1) + tray.OnClick(func() {}) + tray.OnRightClick(func() {}) + tray.OnDoubleClick(func() {}) + tray.OnRightDoubleClick(func() {}) + tray.OnMouseEnter(func() {}) + tray.OnMouseLeave(func() {}) + } + }) + + b.Run("InvokeClickHandler", func(b *testing.B) { + tray := newSystemTray(1) + counter := 0 + tray.OnClick(func() { + counter++ + }) + + b.ResetTimer() + for b.Loop() { + if tray.clickHandler != nil { + tray.clickHandler() + } + } + }) + + b.Run("InvokeAllHandlers", func(b *testing.B) { + tray := newSystemTray(1) + counter := 0 + handler := func() { counter++ } + tray.OnClick(handler) + tray.OnRightClick(handler) + tray.OnDoubleClick(handler) + tray.OnRightDoubleClick(handler) + tray.OnMouseEnter(handler) + tray.OnMouseLeave(handler) + + b.ResetTimer() + for b.Loop() { + if tray.clickHandler != nil { + tray.clickHandler() + } + if tray.rightClickHandler != nil { + tray.rightClickHandler() + } + if tray.doubleClickHandler != nil { + tray.doubleClickHandler() + } + if tray.rightDoubleClickHandler != nil { + tray.rightDoubleClickHandler() + } + if tray.mouseEnterHandler != nil { + tray.mouseEnterHandler() + } + if tray.mouseLeaveHandler != nil { + tray.mouseLeaveHandler() + } + } + }) +} + +// BenchmarkWindowAttachment measures window attachment configuration +func BenchmarkWindowAttachment(b *testing.B) { + b.Run("AttachWindow", func(b *testing.B) { + // We can't create real windows, but we can test the attachment logic + for b.Loop() { + tray := newSystemTray(1) + // AttachWindow accepts nil gracefully + tray.AttachWindow(nil) + } + }) + + b.Run("WindowOffset", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.WindowOffset(10) + } + }) + + b.Run("WindowDebounce", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.WindowDebounce(200 * time.Millisecond) + } + }) + + b.Run("ChainedAttachment", func(b *testing.B) { + for b.Loop() { + tray := newSystemTray(1) + tray.AttachWindow(nil). + WindowOffset(10). + WindowDebounce(200 * time.Millisecond) + } + }) +} + +// BenchmarkMenuConfiguration measures menu setup operations +func BenchmarkMenuConfiguration(b *testing.B) { + b.Run("SetNilMenu", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.SetMenu(nil) + } + }) + + b.Run("SetSimpleMenu", func(b *testing.B) { + menu := NewMenu() + menu.Add("Item 1") + menu.Add("Item 2") + menu.Add("Item 3") + + tray := newSystemTray(1) + b.ResetTimer() + for b.Loop() { + tray.SetMenu(menu) + } + }) + + b.Run("SetComplexMenu", func(b *testing.B) { + menu := NewMenu() + for i := 0; i < 20; i++ { + menu.Add("Item") + } + submenu := NewMenu() + for i := 0; i < 10; i++ { + submenu.Add("Subitem") + } + + tray := newSystemTray(1) + b.ResetTimer() + for b.Loop() { + tray.SetMenu(menu) + } + }) +} + +// BenchmarkIconSizes measures icon handling with different sizes +func BenchmarkIconSizes(b *testing.B) { + sizes := []struct { + name string + size int + }{ + {"16x16", 16 * 16 * 4}, // 1KB - small icon + {"32x32", 32 * 32 * 4}, // 4KB - medium icon + {"64x64", 64 * 64 * 4}, // 16KB - large icon + {"128x128", 128 * 128 * 4}, // 64KB - retina icon + {"256x256", 256 * 256 * 4}, // 256KB - high-res icon + } + + for _, size := range sizes { + b.Run(size.name, func(b *testing.B) { + icon := make([]byte, size.size) + tray := newSystemTray(1) + + b.ResetTimer() + for b.Loop() { + tray.SetIcon(icon) + } + }) + } +} + +// BenchmarkWindowAttachConfigInit measures WindowAttachConfig initialization +func BenchmarkWindowAttachConfigInit(b *testing.B) { + b.Run("DefaultConfig", func(b *testing.B) { + for b.Loop() { + config := WindowAttachConfig{ + Window: nil, + Offset: 0, + Debounce: 200 * time.Millisecond, + } + _ = config + } + }) + + b.Run("FullConfig", func(b *testing.B) { + for b.Loop() { + config := WindowAttachConfig{ + Window: nil, + Offset: 10, + Debounce: 300 * time.Millisecond, + justClosed: false, + hasBeenShown: true, + } + _ = config + } + }) +} + +// BenchmarkSystemTrayShowHide measures show/hide state changes +// Note: These operations are no-ops when impl is nil, but we measure the check overhead +func BenchmarkSystemTrayShowHide(b *testing.B) { + b.Run("Show", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.Show() + } + }) + + b.Run("Hide", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.Hide() + } + }) + + b.Run("ToggleShowHide", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.Show() + tray.Hide() + } + }) +} + +// BenchmarkIconPositionConstants measures icon position constant access +func BenchmarkIconPositionConstants(b *testing.B) { + positions := []IconPosition{ + NSImageNone, + NSImageOnly, + NSImageLeft, + NSImageRight, + NSImageBelow, + NSImageAbove, + NSImageOverlaps, + NSImageLeading, + NSImageTrailing, + } + + b.Run("SetAllPositions", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + for _, pos := range positions { + tray.SetIconPosition(pos) + } + } + }) +} + +// BenchmarkLabelOperations measures label getter/setter performance +func BenchmarkLabelOperations(b *testing.B) { + b.Run("SetLabel", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.SetLabel("System Tray Label") + } + }) + + b.Run("GetLabel", func(b *testing.B) { + tray := newSystemTray(1) + tray.SetLabel("System Tray Label") + b.ResetTimer() + for b.Loop() { + _ = tray.Label() + } + }) + + b.Run("SetGetLabel", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.SetLabel("Label") + _ = tray.Label() + } + }) +} + +// BenchmarkDefaultClickHandler measures the default click handler logic +func BenchmarkDefaultClickHandler(b *testing.B) { + b.Run("NoAttachedWindow", func(b *testing.B) { + tray := newSystemTray(1) + // With no menu and no attached window, defaultClickHandler returns early + for b.Loop() { + tray.defaultClickHandler() + } + }) + + b.Run("WithNilWindow", func(b *testing.B) { + tray := newSystemTray(1) + tray.attachedWindow.Window = nil + for b.Loop() { + tray.defaultClickHandler() + } + }) +} diff --git a/v3/pkg/application/systemtray_darwin.go b/v3/pkg/application/systemtray_darwin.go new file mode 100644 index 000000000..c88c835c0 --- /dev/null +++ b/v3/pkg/application/systemtray_darwin.go @@ -0,0 +1,287 @@ +//go:build darwin && !ios + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -framework WebKit + +#include "Cocoa/Cocoa.h" +#include "menuitem_darwin.h" +#include "systemtray_darwin.h" + +// Show the system tray icon +static void systemTrayShow(void* nsStatusItem) { + dispatch_async(dispatch_get_main_queue(), ^{ + // Get the NSStatusItem + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + [statusItem setVisible:YES]; + }); +} + +// Hide the system tray icon +static void systemTrayHide(void* nsStatusItem) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + [statusItem setVisible:NO]; + }); +} + +*/ +import "C" +import ( + "errors" + "unsafe" + + "github.com/leaanthony/go-ansi-parser" +) + +type macosSystemTray struct { + id uint + label string + icon []byte + menu *Menu + + nsStatusItem unsafe.Pointer + nsImage unsafe.Pointer + nsMenu unsafe.Pointer + iconPosition IconPosition + isTemplateIcon bool + parent *SystemTray + lastClickedScreen unsafe.Pointer +} + +func (s *macosSystemTray) Show() { + if s.nsStatusItem == nil { + return + } + C.systemTrayShow(s.nsStatusItem) +} + +func (s *macosSystemTray) Hide() { + if s.nsStatusItem == nil { + return + } + C.systemTrayHide(s.nsStatusItem) +} + +func (s *macosSystemTray) openMenu() { + if s.nsMenu == nil { + return + } + C.showMenu(s.nsStatusItem, s.nsMenu) +} + +type button int + +const ( + leftButtonDown button = 1 + rightButtonDown button = 3 +) + +// system tray map +var systemTrayMap = make(map[uint]*macosSystemTray) + +//export systrayClickCallback +func systrayClickCallback(id C.long, buttonID C.int) { + // Get the system tray + systemTray := systemTrayMap[uint(id)] + if systemTray == nil { + globalApplication.error("system tray not found: %v", id) + return + } + systemTray.processClick(button(buttonID)) +} + +func (s *macosSystemTray) setIconPosition(position IconPosition) { + s.iconPosition = position +} + +func (s *macosSystemTray) setMenu(menu *Menu) { + s.menu = menu +} + +func (s *macosSystemTray) positionWindow(window Window, offset int) error { + // Get the platform-specific window implementation + nativeWindow := window.NativeWindow() + if nativeWindow == nil { + return errors.New("window native implementation unavailable") + } + + // Position the window relative to the systray + C.systemTrayPositionWindow(s.nsStatusItem, nativeWindow, C.int(offset)) + + return nil +} + +func (s *macosSystemTray) getScreen() (*Screen, error) { + if s.lastClickedScreen != nil { + // Get the screen frame + frame := C.NSScreen_frame(s.lastClickedScreen) + result := &Screen{ + Bounds: Rect{ + X: int(frame.origin.x), + Y: int(frame.origin.y), + Width: int(frame.size.width), + Height: int(frame.size.height), + }, + } + return result, nil + } + return nil, errors.New("no screen available") +} + +func (s *macosSystemTray) bounds() (*Rect, error) { + var rect C.NSRect + var screen unsafe.Pointer + C.systemTrayGetBounds(s.nsStatusItem, &rect, &screen) + + // Store the screen for use in positionWindow + s.lastClickedScreen = screen + + // Return the screen-relative coordinates + result := &Rect{ + X: int(rect.origin.x), + Y: int(rect.origin.y), + Width: int(rect.size.width), + Height: int(rect.size.height), + } + return result, nil +} + +func (s *macosSystemTray) run() { + globalApplication.dispatchOnMainThread(func() { + if s.nsStatusItem != nil { + Fatal("System tray '%d' already running", s.id) + } + s.nsStatusItem = unsafe.Pointer(C.systemTrayNew(C.long(s.id))) + + if s.label != "" { + s.setLabel(s.label) + } + if s.icon != nil { + s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&s.icon[0]), C.int(len(s.icon)))) + C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon)) + } + if s.menu != nil { + s.menu.Update() + // Convert impl to macosMenu object + s.nsMenu = (s.menu.impl).(*macosMenu).nsMenu + } + }) +} + +func (s *macosSystemTray) setIcon(icon []byte) { + s.icon = icon + globalApplication.dispatchOnMainThread(func() { + s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&icon[0]), C.int(len(icon)))) + C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon)) + }) +} + +func (s *macosSystemTray) setDarkModeIcon(icon []byte) { + s.setIcon(icon) +} + +func (s *macosSystemTray) setTemplateIcon(icon []byte) { + s.icon = icon + s.isTemplateIcon = true + globalApplication.dispatchOnMainThread(func() { + s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&icon[0]), C.int(len(icon)))) + C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon)) + }) +} + +func (s *macosSystemTray) setTooltip(tooltip string) { + // Tooltips not supported on macOS +} + +func newSystemTrayImpl(s *SystemTray) systemTrayImpl { + result := &macosSystemTray{ + parent: s, + id: s.id, + label: s.label, + icon: s.icon, + menu: s.menu, + iconPosition: s.iconPosition, + isTemplateIcon: s.isTemplateIcon, + } + systemTrayMap[s.id] = result + return result +} + +func extractAnsiTextParts(text *ansi.StyledText) (label *C.char, fg *C.char, bg *C.char) { + label = C.CString(text.Label) + if text.FgCol != nil { + fg = C.CString(text.FgCol.Hex) + } + if text.BgCol != nil { + bg = C.CString(text.BgCol.Hex) + } + return +} + +func (s *macosSystemTray) setLabel(label string) { + s.label = label + if !ansi.HasEscapeCodes(label) { + C.systemTraySetLabel(s.nsStatusItem, C.CString(label)) + } else { + parsed, err := ansi.Parse(label) + if err != nil { + C.systemTraySetLabel(s.nsStatusItem, C.CString(label)) + return + } + if len(parsed) == 0 { + return + } + label, fg, bg := extractAnsiTextParts(parsed[0]) + var attributedString = C.createAttributedString(label, fg, bg) + if len(parsed) > 1 { + for _, parsedPart := range parsed[1:] { + label, fg, bg = extractAnsiTextParts(parsedPart) + attributedString = C.appendAttributedString(attributedString, label, fg, bg) + } + } + + C.systemTraySetANSILabel(s.nsStatusItem, attributedString) + } +} + +func (s *macosSystemTray) destroy() { + // Remove the status item from the status bar and its associated menu + C.systemTrayDestroy(s.nsStatusItem) +} + +func (s *macosSystemTray) processClick(b button) { + switch b { + case leftButtonDown: + // Check if we have a callback + if s.parent.clickHandler != nil { + s.parent.clickHandler() + return + } + if s.parent.attachedWindow.Window != nil { + s.parent.defaultClickHandler() + return + } + if s.menu != nil { + C.showMenu(s.nsStatusItem, s.nsMenu) + } + case rightButtonDown: + // Check if we have a callback + if s.parent.rightClickHandler != nil { + s.parent.rightClickHandler() + return + } + if s.menu != nil { + if s.parent.attachedWindow.Window != nil { + s.parent.attachedWindow.Window.Hide() + } + C.showMenu(s.nsStatusItem, s.nsMenu) + return + } + if s.parent.attachedWindow.Window != nil { + s.parent.defaultClickHandler() + } + } +} diff --git a/v3/pkg/application/systemtray_darwin.h b/v3/pkg/application/systemtray_darwin.h new file mode 100644 index 000000000..29404e095 --- /dev/null +++ b/v3/pkg/application/systemtray_darwin.h @@ -0,0 +1,24 @@ +//go:build darwin && !ios + +#include + +@interface StatusItemController : NSObject +@property long id; +- (void)statusItemClicked:(id)sender; +@end + +void* systemTrayNew(long id); +void systemTraySetLabel(void* nsStatusItem, char *label); +void systemTraySetANSILabel(void* nsStatusItem, void* attributedString); +void systemTraySetLabelColor(void* nsStatusItem, char *fg, char *bg); +void* createAttributedString(char *title, char *FG, char *BG); +void* appendAttributedString(void* original, char* label, char* fg, char* bg); +NSImage* imageFromBytes(const unsigned char *bytes, int length); +void systemTraySetIcon(void* nsStatusItem, void* nsImage, int position, bool isTemplate); +void systemTrayDestroy(void* nsStatusItem); +void showMenu(void* nsStatusItem, void *nsMenu); +void systemTrayGetBounds(void* nsStatusItem, NSRect *rect, void **screen); +NSRect NSScreen_frame(void* screen); +void windowSetScreen(void* window, void* screen, int yOffset); +int statusBarHeight(); +void systemTrayPositionWindow(void* nsStatusItem, void* nsWindow, int offset); \ No newline at end of file diff --git a/v3/pkg/application/systemtray_darwin.m b/v3/pkg/application/systemtray_darwin.m new file mode 100644 index 000000000..a715fc125 --- /dev/null +++ b/v3/pkg/application/systemtray_darwin.m @@ -0,0 +1,244 @@ +//go:build darwin && !ios + +#include "Cocoa/Cocoa.h" +#include "menuitem_darwin.h" +#include "systemtray_darwin.h" + +extern void systrayClickCallback(long, int); + +// StatusItemController.m +@implementation StatusItemController + +- (void)statusItemClicked:(id)sender { + NSEvent *event = [NSApp currentEvent]; + systrayClickCallback(self.id, event.type); +} + +@end + +// Create a new system tray +void* systemTrayNew(long id) { + StatusItemController *controller = [[StatusItemController alloc] init]; + controller.id = id; + NSStatusItem *statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength] retain]; + [statusItem setTarget:controller]; + [statusItem setAction:@selector(statusItemClicked:)]; + NSButton *button = statusItem.button; + [button sendActionOn:(NSEventMaskLeftMouseDown|NSEventMaskRightMouseDown)]; + return (void*)statusItem; +} + +void systemTraySetLabel(void* nsStatusItem, char *label) { + if( label == NULL ) { + return; + } + // Set the label on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + statusItem.button.title = [NSString stringWithUTF8String:label]; + free(label); + }); +} + +void systemTraySetANSILabel(void* nsStatusItem, void* label) { + if( label == NULL ) { + return; + } + + NSMutableAttributedString* attributedString = (NSMutableAttributedString*) label; + + // Set the label + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + [statusItem setAttributedTitle:attributedString]; + // [attributedString release]; +} + +void* appendAttributedString(void *currentString, char *title, char *FG, char *BG) { + + NSMutableAttributedString* newString = createAttributedString(title, FG, BG); + if( currentString != NULL ) { + NSMutableAttributedString* current = (NSMutableAttributedString*)currentString; + [current appendAttributedString:newString]; + newString = current; + } + + return (void*)newString; +} + +void* createAttributedString(char *title, char *FG, char *BG) { + + NSMutableDictionary *dictionary = [NSMutableDictionary new]; + + // RGBA + if(FG != NULL && strlen(FG) > 0) { + unsigned short r, g, b, a; + + // white by default + r = g = b = a = 255; + int count = sscanf(FG, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a); + if (count > 0) { + NSColor *colour = [NSColor colorWithCalibratedRed:(CGFloat)r / 255.0 + green:(CGFloat)g / 255.0 + blue:(CGFloat)b / 255.0 + alpha:(CGFloat)a / 255.0]; + dictionary[NSForegroundColorAttributeName] = colour; + + } + } + + // Calculate BG colour + if(BG != NULL && strlen(BG) > 0) { + unsigned short r, g, b, a; + + // white by default + r = g = b = a = 255; + int count = sscanf(BG, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a); + if (count > 0) { + NSColor *colour = [NSColor colorWithCalibratedRed:(CGFloat)r / 255.0 + green:(CGFloat)g / 255.0 + blue:(CGFloat)b / 255.0 + alpha:(CGFloat)a / 255.0]; + dictionary[NSBackgroundColorAttributeName] = colour; + } + } + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:title] attributes:dictionary]; + return (void*)attributedString; +} + +// Create an nsimage from a byte array +NSImage* imageFromBytes(const unsigned char *bytes, int length) { + NSData *data = [NSData dataWithBytes:bytes length:length]; + NSImage *image = [[NSImage alloc] initWithData:data]; + return image; +} + +// Set the icon on the system tray +void systemTraySetIcon(void* nsStatusItem, void* nsImage, int position, bool isTemplate) { + // Set the icon on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + NSImage *image = (NSImage *)nsImage; + + NSStatusBar *statusBar = [NSStatusBar systemStatusBar]; + CGFloat thickness = [statusBar thickness]; + [image setSize:NSMakeSize(thickness, thickness)]; + if( isTemplate ) { + [image setTemplate:YES]; + } + statusItem.button.image = [image autorelease]; + statusItem.button.imagePosition = position; + }); +} + +// Destroy system tray +void systemTrayDestroy(void* nsStatusItem) { + // Remove the status item from the status bar and its associated menu + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + [[NSStatusBar systemStatusBar] removeStatusItem:statusItem]; + [statusItem release]; + }); +} + +void showMenu(void* nsStatusItem, void *nsMenu) { + // Show the menu on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + [statusItem popUpStatusItemMenu:(NSMenu *)nsMenu]; + // Post a mouse up event so the statusitem defocuses + NSEvent *event = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp + location:[NSEvent mouseLocation] + modifierFlags:0 + timestamp:[[NSProcessInfo processInfo] systemUptime] + windowNumber:0 + context:nil + eventNumber:0 + clickCount:1 + pressure:1]; + [NSApp postEvent:event atStart:NO]; + [statusItem.button highlight:NO]; + }); +} + +void systemTrayGetBounds(void* nsStatusItem, NSRect *rect, void **outScreen) { + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + NSStatusBarButton *button = statusItem.button; + + // Get mouse location and find the screen it's on + NSPoint mouseLocation = [NSEvent mouseLocation]; + NSScreen *screen = nil; + NSArray *screens = [NSScreen screens]; + + for (NSScreen *candidate in screens) { + NSRect frame = [candidate frame]; + if (NSPointInRect(mouseLocation, frame)) { + screen = candidate; + break; + } + } + if (!screen) { + screen = [NSScreen mainScreen]; + } + + // Get button frame in screen coordinates + NSRect buttonFrame = button.frame; + NSRect buttonFrameScreen = [button.window convertRectToScreen:buttonFrame]; + + *rect = buttonFrameScreen; + *outScreen = (void*)screen; +} + +NSRect NSScreen_frame(void* screen) { + return [(NSScreen*)screen frame]; +} + +int statusBarHeight() { + NSMenu *mainMenu = [NSApp mainMenu]; + CGFloat menuBarHeight = [mainMenu menuBarHeight]; + return (int)menuBarHeight; +} + +void systemTrayPositionWindow(void* nsStatusItem, void* nsWindow, int offset) { + // Get the status item's button + NSStatusBarButton *button = [(NSStatusItem*)nsStatusItem button]; + + // Get the frame in screen coordinates + NSRect frame = [button.window convertRectToScreen:button.frame]; + + // Get the screen that contains the status item + NSScreen *screen = [button.window screen]; + if (screen == nil) { + screen = [NSScreen mainScreen]; + } + + // Get screen's backing scale factor (DPI) + CGFloat scaleFactor = [screen backingScaleFactor]; + + // Get the window's frame + NSRect windowFrame = [(NSWindow*)nsWindow frame]; + + // Calculate the horizontal position (centered under the status item) + CGFloat windowX = frame.origin.x + (frame.size.width - windowFrame.size.width) / 2; + + // If the window would go off the right edge of the screen, adjust it + if (windowX + windowFrame.size.width > screen.frame.origin.x + screen.frame.size.width) { + windowX = screen.frame.origin.x + screen.frame.size.width - windowFrame.size.width; + } + // If the window would go off the left edge of the screen, adjust it + if (windowX < screen.frame.origin.x) { + windowX = screen.frame.origin.x; + } + + // Get screen metrics + NSRect screenFrame = [screen frame]; + NSRect visibleFrame = [screen visibleFrame]; + + // Calculate the vertical position + CGFloat scaledOffset = offset * scaleFactor; + CGFloat windowY = visibleFrame.origin.y + visibleFrame.size.height - windowFrame.size.height - scaledOffset; + + // Set the window's frame + windowFrame.origin.x = windowX; + windowFrame.origin.y = windowY; + [(NSWindow*)nsWindow setFrame:windowFrame display:YES animate:NO]; +} diff --git a/v3/pkg/application/systemtray_ios.go b/v3/pkg/application/systemtray_ios.go new file mode 100644 index 000000000..faa4d0427 --- /dev/null +++ b/v3/pkg/application/systemtray_ios.go @@ -0,0 +1,103 @@ +//go:build ios + +package application + +// iOS doesn't have system tray support +// These are placeholder implementations + +func (t *SystemTray) update() {} + +func (t *SystemTray) setMenu(menu *Menu) { + // iOS doesn't have system tray +} + +func (t *SystemTray) close() { + // iOS doesn't have system tray +} + + +func (t *SystemTray) attachWindow(window *WebviewWindow) { + // iOS doesn't have system tray +} + +func (t *SystemTray) detachWindow(windowID uint) { + // iOS doesn't have system tray +} + +type iosSystemTray struct { + parent *SystemTray +} + +func newSystemTrayImpl(s *SystemTray) systemTrayImpl { + return &iosSystemTray{ + parent: s, + } +} + +func (s *iosSystemTray) run() { + // iOS doesn't have system tray +} + +func (s *iosSystemTray) setLabel(_ string) { + // iOS doesn't have system tray +} + +func (s *iosSystemTray) setMenu(_ *Menu) { + // iOS doesn't have system tray +} + +func (s *iosSystemTray) setIcon(_ []byte) { + // iOS doesn't have system tray +} + +func (s *iosSystemTray) setDarkModeIcon(_ []byte) { + // iOS doesn't have system tray +} + +func (s *iosSystemTray) destroy() { + // iOS doesn't have system tray +} + +func (s *iosSystemTray) setIconPosition(_ IconPosition) { + // iOS doesn't have system tray +} + +func (s *iosSystemTray) positionWindow(_ Window, _ int) error { + return nil +} + +func (s *iosSystemTray) detachWindowPositioning(_ uint) { + // iOS doesn't have system tray +} + +func (s *iosSystemTray) setTemplateIcon(_ []byte) { + // iOS doesn't have system tray +} + +func (s *iosSystemTray) openMenu() { + // iOS doesn't have system tray +} + +func (s *iosSystemTray) setTooltip(_ string) { + // iOS doesn't have system tray +} + +func (s *iosSystemTray) bounds() (*Rect, error) { + return nil, nil +} + +func (s *iosSystemTray) getScreen() (*Screen, error) { + screens, err := getScreens() + if err != nil || len(screens) == 0 { + return nil, err + } + return screens[0], nil +} + +func (s *iosSystemTray) Show() { + // iOS doesn't have system tray +} + +func (s *iosSystemTray) Hide() { + // iOS doesn't have system tray +} \ No newline at end of file diff --git a/v3/pkg/application/systemtray_linux.go b/v3/pkg/application/systemtray_linux.go new file mode 100644 index 000000000..df44da586 --- /dev/null +++ b/v3/pkg/application/systemtray_linux.go @@ -0,0 +1,761 @@ +//go:build linux && !android && !server + +/* +Portions of this code are derived from the project: +- https://github.com/fyne-io/systray +*/ +package application + +import "C" +import ( + "fmt" + "os" + + "github.com/godbus/dbus/v5" + "github.com/godbus/dbus/v5/introspect" + "github.com/godbus/dbus/v5/prop" + "github.com/wailsapp/wails/v3/internal/dbus/menu" + "github.com/wailsapp/wails/v3/internal/dbus/notifier" + "github.com/wailsapp/wails/v3/pkg/icons" +) + +const ( + itemPath = "/StatusNotifierItem" + menuPath = "/StatusNotifierMenu" +) + +type linuxSystemTray struct { + parent *SystemTray + + id uint + label string + icon []byte + menu *Menu + + iconPosition IconPosition + isTemplateIcon bool + + quitChan chan struct{} + conn *dbus.Conn + props *prop.Properties + menuProps *prop.Properties + + menuVersion uint32 // need to bump this anytime we change anything + itemMap map[int32]*systrayMenuItem + tooltip string +} + +func (s *linuxSystemTray) getScreen() (*Screen, error) { + _, _, result := getMousePosition() + return result, nil +} + +// dbusMenu is a named struct to map into generated bindings. +// It represents the layout of a menu item +type dbusMenu = struct { + V0 int32 // items' unique id + V1 map[string]dbus.Variant // layout properties + V2 []dbus.Variant // child menu(s) +} + +// systrayMenuItem is an implementation of the menuItemImpl interface +type systrayMenuItem struct { + sysTray *linuxSystemTray + menuItem *MenuItem + dbusItem *dbusMenu +} + +func (s *systrayMenuItem) setBitmap(data []byte) { + s.dbusItem.V1["icon-data"] = dbus.MakeVariant(data) + s.sysTray.update(s) +} + +func (s *systrayMenuItem) setTooltip(v string) { + s.dbusItem.V1["tooltip"] = dbus.MakeVariant(v) + s.sysTray.update(s) +} + +func (s *systrayMenuItem) setLabel(v string) { + s.dbusItem.V1["label"] = dbus.MakeVariant(v) + s.sysTray.update(s) +} + +func (s *systrayMenuItem) setDisabled(disabled bool) { + v := dbus.MakeVariant(!disabled) + if s.dbusItem.V1["toggle-state"] != v { + s.dbusItem.V1["enabled"] = v + s.sysTray.update(s) + } +} + +func (s *systrayMenuItem) destroy() {} + +func (s *systrayMenuItem) setChecked(checked bool) { + v := dbus.MakeVariant(0) + if checked { + v = dbus.MakeVariant(1) + } + if s.dbusItem.V1["toggle-state"] != v { + s.dbusItem.V1["toggle-state"] = v + s.sysTray.update(s) + } +} + +func (s *systrayMenuItem) setAccelerator(accelerator *accelerator) {} +func (s *systrayMenuItem) setHidden(hidden bool) { + s.dbusItem.V1["visible"] = dbus.MakeVariant(!hidden) + s.sysTray.update(s) +} + +func (s *systrayMenuItem) dbus() *dbusMenu { + item := &dbusMenu{ + V0: int32(s.menuItem.id), + V1: map[string]dbus.Variant{}, + V2: []dbus.Variant{}, + } + return item +} + +func (s *linuxSystemTray) setIconPosition(position IconPosition) { + s.iconPosition = position +} + +func (s *linuxSystemTray) processMenu(menu *Menu, parentId int32) { + parentItem, ok := s.itemMap[int32(parentId)] + if !ok { + return + } + parent := parentItem.dbusItem + + for _, item := range menu.items { + menuItem := &dbusMenu{ + V0: int32(item.id), + V1: map[string]dbus.Variant{}, + V2: []dbus.Variant{}, + } + item.impl = &systrayMenuItem{ + sysTray: s, + menuItem: item, + dbusItem: menuItem, + } + s.itemMap[int32(item.id)] = item.impl.(*systrayMenuItem) + + menuItem.V1["enabled"] = dbus.MakeVariant(!item.disabled) + menuItem.V1["visible"] = dbus.MakeVariant(!item.hidden) + if item.label != "" { + menuItem.V1["label"] = dbus.MakeVariant(item.label) + } + if item.bitmap != nil { + menuItem.V1["icon-data"] = dbus.MakeVariant(item.bitmap) + } + switch item.itemType { + case checkbox: + menuItem.V1["toggle-type"] = dbus.MakeVariant("checkmark") + v := dbus.MakeVariant(0) + if item.checked { + v = dbus.MakeVariant(1) + } + menuItem.V1["toggle-state"] = v + case submenu: + menuItem.V1["children-display"] = dbus.MakeVariant("submenu") + s.processMenu(item.submenu, int32(item.id)) + case text: + case radio: + menuItem.V1["toggle-type"] = dbus.MakeVariant("radio") + v := dbus.MakeVariant(0) + if item.checked { + v = dbus.MakeVariant(1) + } + menuItem.V1["toggle-state"] = v + case separator: + menuItem.V1["type"] = dbus.MakeVariant("separator") + } + + parent.V2 = append(parent.V2, dbus.MakeVariant(menuItem)) + } +} + +func (s *linuxSystemTray) refresh() { + s.menuVersion++ + if err := s.menuProps.Set("com.canonical.dbusmenu", "Version", + dbus.MakeVariant(s.menuVersion)); err != nil { + globalApplication.error("systray error: failed to update menu version: %w", err) + return + } + if err := menu.Emit(s.conn, &menu.Dbusmenu_LayoutUpdatedSignal{ + Path: menuPath, + Body: &menu.Dbusmenu_LayoutUpdatedSignalBody{ + Revision: s.menuVersion, + }, + }); err != nil { + globalApplication.error("systray error: failed to emit layout updated signal: %w", err) + } +} + +func (s *linuxSystemTray) setMenu(menu *Menu) { + if s.parent.attachedWindow.Window != nil { + temp := menu + menu = NewMenu() + title := "Open" + if s.parent.attachedWindow.Window.Name() != "" { + title += " " + s.parent.attachedWindow.Window.Name() + } else { + title += " window" + } + openMenuItem := menu.Add(title) + openMenuItem.OnClick(func(*Context) { + s.parent.clickHandler() + }) + menu.AddSeparator() + menu.Append(temp) + } + s.itemMap = map[int32]*systrayMenuItem{} + // our root menu element + s.itemMap[0] = &systrayMenuItem{ + menuItem: nil, + dbusItem: &dbusMenu{ + V0: int32(0), + V1: map[string]dbus.Variant{}, + V2: []dbus.Variant{}, + }, + } + menu.processRadioGroups() + s.processMenu(menu, 0) + s.menu = menu + s.refresh() +} + +func (s *linuxSystemTray) positionWindow(window Window, offset int) error { + // Get the mouse location on the screen + mouseX, mouseY, currentScreen := getMousePosition() + screenBounds := currentScreen.Size + + // Calculate new X position + newX := mouseX - (window.Width() / 2) + + // Check if the window goes out of the screen bounds on the left side + if newX < 0 { + newX = 0 + } + + // Check if the window goes out of the screen bounds on the right side + if newX+window.Width() > screenBounds.Width { + newX = screenBounds.Width - window.Width() + } + + // Calculate new Y position + newY := mouseY - (window.Height() / 2) + + // Check if the window goes out of the screen bounds on the top + if newY < 0 { + newY = 0 + } + + // Check if the window goes out of the screen bounds on the bottom + if newY+window.Height() > screenBounds.Height { + newY = screenBounds.Height - window.Height() - offset + } + + // Set the new position of the window + window.SetPosition(newX, newY) + return nil +} + +func (s *linuxSystemTray) bounds() (*Rect, error) { + + // Best effort guess at the screen bounds + + return &Rect{}, nil + +} + +func (s *linuxSystemTray) run() { + conn, err := dbus.SessionBus() + if err != nil { + globalApplication.error("systray error: failed to connect to DBus: %w\n", err) + return + } + err = notifier.ExportStatusNotifierItem(conn, itemPath, s) + if err != nil { + globalApplication.error("systray error: failed to export status notifier item: %w\n", err) + } + + err = menu.ExportDbusmenu(conn, menuPath, s) + if err != nil { + globalApplication.error("systray error: failed to export status notifier menu: %w", err) + return + } + + name := fmt.Sprintf("org.kde.StatusNotifierItem-%d-1", os.Getpid()) // register id 1 for this process + _, err = conn.RequestName(name, dbus.NameFlagDoNotQueue) + if err != nil { + globalApplication.error("systray error: failed to request name: %w", err) + // it's not critical error: continue + } + props, err := prop.Export(conn, itemPath, s.createPropSpec()) + if err != nil { + globalApplication.error("systray error: failed to export notifier item properties to bus: %w", err) + return + } + menuProps, err := prop.Export(conn, menuPath, s.createMenuPropSpec()) + if err != nil { + globalApplication.error("systray error: failed to export notifier menu properties to bus: %w", err) + return + } + + s.conn = conn + s.props = props + s.menuProps = menuProps + + node := introspect.Node{ + Name: itemPath, + Interfaces: []introspect.Interface{ + introspect.IntrospectData, + prop.IntrospectData, + notifier.IntrospectDataStatusNotifierItem, + }, + } + err = conn.Export(introspect.NewIntrospectable(&node), itemPath, "org.freedesktop.DBus.Introspectable") + if err != nil { + globalApplication.error("systray error: failed to export node introspection: %w", err) + return + } + menuNode := introspect.Node{ + Name: menuPath, + Interfaces: []introspect.Interface{ + introspect.IntrospectData, + prop.IntrospectData, + menu.IntrospectDataDbusmenu, + }, + } + err = conn.Export(introspect.NewIntrospectable(&menuNode), menuPath, + "org.freedesktop.DBus.Introspectable") + if err != nil { + globalApplication.error("systray error: failed to export menu node introspection: %w", err) + return + } + s.setLabel(s.label) + go func() { + defer handlePanic() + s.register() + + if err := conn.AddMatchSignal( + dbus.WithMatchObjectPath("/org/freedesktop/DBus"), + dbus.WithMatchInterface("org.freedesktop.DBus"), + dbus.WithMatchSender("org.freedesktop.DBus"), + dbus.WithMatchMember("NameOwnerChanged"), + dbus.WithMatchArg(0, "org.kde.StatusNotifierWatcher"), + ); err != nil { + globalApplication.error("systray error: failed to register signal matching: %w", err) + return + } + + sc := make(chan *dbus.Signal, 10) + conn.Signal(sc) + + for { + select { + case sig := <-sc: + if sig == nil { + return // We get a nil signal when closing the window. + } + // sig.Body has the args, which are [name old_owner new_owner] + if sig.Body[2] != "" { + s.register() + } + + case <-s.quitChan: + return + } + } + }() + + if s.parent.label != "" { + s.setLabel(s.parent.label) + } + + if s.parent.tooltip != "" { + s.setTooltip(s.parent.tooltip) + } + s.setMenu(s.menu) +} + +func (s *linuxSystemTray) setTooltip(_ string) { + // TBD +} + +func (s *linuxSystemTray) setIcon(icon []byte) { + + s.icon = icon + + iconPx, err := iconToPX(icon) + if err != nil { + globalApplication.error("systray error: failed to convert icon to PX: %w", err) + return + } + s.props.SetMust("org.kde.StatusNotifierItem", "IconPixmap", []PX{iconPx}) + + if s.conn == nil { + return + } + + err = notifier.Emit(s.conn, ¬ifier.StatusNotifierItem_NewIconSignal{ + Path: itemPath, + Body: ¬ifier.StatusNotifierItem_NewIconSignalBody{}, + }) + if err != nil { + globalApplication.error("systray error: failed to emit new icon signal: %w", err) + return + } +} + +func (s *linuxSystemTray) setDarkModeIcon(icon []byte) { + s.setIcon(icon) +} + +func (s *linuxSystemTray) setTemplateIcon(icon []byte) { + s.icon = icon + s.isTemplateIcon = true + s.setIcon(icon) +} + +func newSystemTrayImpl(s *SystemTray) systemTrayImpl { + label := s.label + if label == "" { + label = "Wails" + } + + return &linuxSystemTray{ + parent: s, + id: s.id, + label: label, + icon: s.icon, + menu: s.menu, + iconPosition: s.iconPosition, + isTemplateIcon: s.isTemplateIcon, + quitChan: make(chan struct{}), + menuVersion: 1, + } +} + +func (s *linuxSystemTray) openMenu() { + // FIXME: Emit com.canonical to open? + globalApplication.info("systray error: openMenu not implemented on Linux") +} + +func (s *linuxSystemTray) setLabel(label string) { + s.label = label + + if err := s.props.Set("org.kde.StatusNotifierItem", "Title", dbus.MakeVariant(label)); err != nil { + globalApplication.error("systray error: failed to set Title prop: %w", err) + return + } + + if s.conn == nil { + return + } + + if err := notifier.Emit(s.conn, ¬ifier.StatusNotifierItem_NewTitleSignal{ + Path: itemPath, + Body: ¬ifier.StatusNotifierItem_NewTitleSignalBody{}, + }); err != nil { + globalApplication.error("systray error: failed to emit new title signal: %w", err) + return + } + +} + +func (s *linuxSystemTray) destroy() { + close(s.quitChan) +} + +func (s *linuxSystemTray) createMenuPropSpec() map[string]map[string]*prop.Prop { + return map[string]map[string]*prop.Prop{ + "com.canonical.dbusmenu": { + // update version each time we change something + "Version": { + Value: s.menuVersion, + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + }, + "TextDirection": { + Value: "ltr", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Status": { + Value: "normal", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "IconThemePath": { + Value: []string{}, + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + }, + } +} + +func (s *linuxSystemTray) createPropSpec() map[string]map[string]*prop.Prop { + props := map[string]*prop.Prop{ + "Status": { + Value: "Active", // Passive, Active or NeedsAttention + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Title": { + Value: s.label, + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Id": { + Value: s.label, + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Category": { + Value: "ApplicationStatus", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "IconData": { + Value: "", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + + "IconName": { + Value: "", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "IconThemePath": { + Value: "", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "ItemIsMenu": { + Value: true, + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Menu": { + Value: dbus.ObjectPath(menuPath), + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + }, + "ToolTip": { + Value: tooltip{V2: s.label}, + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + }, + } + + if s.icon == nil { + // set a basic default one if one isn't set + s.icon = icons.WailsLogoWhiteTransparent + } + if iconPx, err := iconToPX(s.icon); err == nil { + props["IconPixmap"] = &prop.Prop{ + Value: []PX{iconPx}, + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + } + } + + return map[string]map[string]*prop.Prop{ + "org.kde.StatusNotifierItem": props, + } +} + +func (s *linuxSystemTray) update(i *systrayMenuItem) { + s.itemMap[int32(i.menuItem.id)] = i + s.refresh() +} + +func (s *linuxSystemTray) register() bool { + obj := s.conn.Object("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher") + call := obj.Call("org.kde.StatusNotifierWatcher.RegisterStatusNotifierItem", 0, itemPath) + if call.Err != nil { + globalApplication.error("systray error: failed to register: %w", call.Err) + return false + } + + return true +} + +type PX struct { + W, H int + Pix []byte +} + +func iconToPX(icon []byte) (PX, error) { + img, err := pngToImage(icon) + if err != nil { + return PX{}, err + } + w, h, bytes := ToARGB(img) + return PX{ + W: w, + H: h, + Pix: bytes, + }, nil +} + +// AboutToShow is an implementation of the com.canonical.dbusmenu.AboutToShow method. +func (s *linuxSystemTray) AboutToShow(id int32) (needUpdate bool, err *dbus.Error) { + return +} + +// AboutToShowGroup is an implementation of the com.canonical.dbusmenu.AboutToShowGroup method. +func (s *linuxSystemTray) AboutToShowGroup(ids []int32) (updatesNeeded []int32, idErrors []int32, err *dbus.Error) { + return +} + +// GetProperty is an implementation of the com.canonical.dbusmenu.GetProperty method. +func (s *linuxSystemTray) GetProperty(id int32, name string) (value dbus.Variant, err *dbus.Error) { + if item, ok := s.itemMap[id]; ok { + if p, ok := item.dbusItem.V1[name]; ok { + return p, nil + } + } + return +} + +// Event is com.canonical.dbusmenu.Event method. +func (s *linuxSystemTray) Event(id int32, eventID string, data dbus.Variant, timestamp uint32) (err *dbus.Error) { + switch eventID { + case "clicked": + if item, ok := s.itemMap[id]; ok { + InvokeAsync(item.menuItem.handleClick) + } + case "opened": + if s.parent.clickHandler != nil { + s.parent.clickHandler() + } + if s.parent.onMenuOpen != nil { + s.parent.onMenuOpen() + } + case "closed": + if s.parent.onMenuClose != nil { + s.parent.onMenuClose() + } + } + return +} + +// EventGroup is an implementation of the com.canonical.dbusmenu.EventGroup method. +func (s *linuxSystemTray) EventGroup(events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 +}) (idErrors []int32, err *dbus.Error) { + for _, event := range events { + fmt.Printf("EventGroup: %v, %v, %v, %v\n", event.V0, event.V1, event.V2, event.V3) + if event.V1 == "clicked" { + item, ok := s.itemMap[event.V0] + if ok { + InvokeAsync(item.menuItem.handleClick) + } + } + } + return +} + +// GetGroupProperties is an implementation of the com.canonical.dbusmenu.GetGroupProperties method. +func (s *linuxSystemTray) GetGroupProperties(ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant +}, err *dbus.Error) { + // FIXME: RLock? + /* instance.menuLock.Lock() + defer instance.menuLock.Unlock() + */ + for _, id := range ids { + if m, ok := s.itemMap[id]; ok { + p := struct { + V0 int32 + V1 map[string]dbus.Variant + }{ + V0: m.dbusItem.V0, + V1: make(map[string]dbus.Variant, len(m.dbusItem.V1)), + } + for k, v := range m.dbusItem.V1 { + p.V1[k] = v + } + properties = append(properties, p) + } + } + return properties, nil +} + +// GetLayout is an implementation of the com.canonical.dbusmenu.GetLayout method. +func (s *linuxSystemTray) GetLayout(parentID int32, recursionDepth int32, propertyNames []string) (revision uint32, layout dbusMenu, err *dbus.Error) { + // FIXME: RLock? + if m, ok := s.itemMap[parentID]; ok { + return s.menuVersion, *m.dbusItem, nil + } + + return +} + +// Activate implements org.kde.StatusNotifierItem.Activate method. +func (s *linuxSystemTray) Activate(x int32, y int32) (err *dbus.Error) { + if s.parent.doubleClickHandler != nil { + s.parent.doubleClickHandler() + } + return +} + +// ContextMenu is org.kde.StatusNotifierItem.ContextMenu method +func (s *linuxSystemTray) ContextMenu(x int32, y int32) (err *dbus.Error) { + fmt.Println("ContextMenu", x, y) + return nil +} + +func (s *linuxSystemTray) Scroll(delta int32, orientation string) (err *dbus.Error) { + fmt.Println("Scroll", delta, orientation) + return +} + +// SecondaryActivate implements org.kde.StatusNotifierItem.SecondaryActivate method. +func (s *linuxSystemTray) SecondaryActivate(x int32, y int32) (err *dbus.Error) { + s.parent.rightClickHandler() + return +} + +// Show is a no-op for Linux +func (s *linuxSystemTray) Show() { + // No-op +} + +// Hide is a no-op for Linux +func (s *linuxSystemTray) Hide() { + // No-op +} + +// tooltip is our data for a tooltip property. +// Param names need to match the generated code... +type tooltip = struct { + V0 string // name + V1 []PX // icons + V2 string // title + V3 string // description +} diff --git a/v3/pkg/application/systemtray_windows.go b/v3/pkg/application/systemtray_windows.go new file mode 100644 index 000000000..55bea46e8 --- /dev/null +++ b/v3/pkg/application/systemtray_windows.go @@ -0,0 +1,636 @@ +//go:build windows + +package application + +import ( + "errors" + "fmt" + "syscall" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/icons" + + "github.com/wailsapp/wails/v3/pkg/events" + "github.com/wailsapp/wails/v3/pkg/w32" +) + +const ( + wmUserSystray = w32.WM_USER + 1 +) + +type windowsSystemTray struct { + parent *SystemTray + + menu *Win32Menu + + cancelTheme func() + uid uint32 + hwnd w32.HWND + + lightModeIcon w32.HICON + lightModeIconOwned bool + darkModeIcon w32.HICON + darkModeIconOwned bool + currentIcon w32.HICON + currentIconOwned bool +} + +// releaseIcon destroys an icon handle only when we own it and no new handle reuses it. +// Shared handles (e.g. from LoadIcon/LoadIconWithResourceID) must not be passed to DestroyIcon per https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-destroyicon. +func (s *windowsSystemTray) releaseIcon(handle w32.HICON, owned bool, keep ...w32.HICON) { + if !owned || handle == 0 { + return + } + for _, k := range keep { + if handle == k { + return + } + } + w32.DestroyIcon(handle) +} + +func (s *windowsSystemTray) openMenu() { + if s.menu == nil { + return + } + // Get the system tray bounds + trayBounds, err := s.bounds() + if err != nil { + return + } + if trayBounds == nil { + return + } + + // Show the menu at the tray bounds + s.menu.ShowAt(trayBounds.X, trayBounds.Y) +} + +func (s *windowsSystemTray) positionWindow(window Window, offset int) error { + // Get the current screen trayBounds + currentScreen, err := s.getScreen() + if err != nil { + return err + } + + screenBounds := currentScreen.WorkArea + windowBounds := window.Bounds() + + newX := screenBounds.Width - windowBounds.Width - offset + newY := screenBounds.Height - windowBounds.Height - offset + + // systray icons in windows can either be in the taskbar + // or in a flyout menu. + var iconIsInTrayBounds bool + iconIsInTrayBounds, err = s.iconIsInTrayBounds() + if err != nil { + return err + } + + var trayBounds *Rect + var centerAlignX, centerAlignY int + + // we only need the traybounds if the icon is in the tray + if iconIsInTrayBounds { + trayBounds, err = s.bounds() + if err != nil { + return err + } + if trayBounds == nil { + return errors.New("failed to get system tray bounds") + } + *trayBounds = PhysicalToDipRect(*trayBounds) + centerAlignX = trayBounds.X + (trayBounds.Width / 2) - (windowBounds.Width / 2) + centerAlignY = trayBounds.Y + (trayBounds.Height / 2) - (windowBounds.Height / 2) + } + + taskbarBounds := w32.GetTaskbarPosition() + if taskbarBounds == nil { + return errors.New("failed to get taskbar position") + } + + // Set the window position based on the icon location + // if the icon is in the taskbar (traybounds) then we need + // to adjust the position so the window is centered on the icon + switch taskbarBounds.UEdge { + case w32.ABE_LEFT: + if iconIsInTrayBounds && centerAlignY <= newY { + newY = centerAlignY + } + newX = screenBounds.X + offset + case w32.ABE_TOP: + if iconIsInTrayBounds && centerAlignX <= newX { + newX = centerAlignX + } + newY = screenBounds.Y + offset + case w32.ABE_RIGHT: + if iconIsInTrayBounds && centerAlignY <= newY { + newY = centerAlignY + } + case w32.ABE_BOTTOM: + if iconIsInTrayBounds && centerAlignX <= newX { + newX = centerAlignX + } + } + newPos := currentScreen.relativeToAbsoluteDipPoint(Point{X: newX, Y: newY}) + windowBounds.X = newPos.X + windowBounds.Y = newPos.Y + window.SetBounds(windowBounds) + return nil +} + +func (s *windowsSystemTray) bounds() (*Rect, error) { + if s.hwnd == 0 { + return nil, errors.New("system tray window handle not initialized") + } + + bounds, err := w32.GetSystrayBounds(s.hwnd, s.uid) + if err != nil { + return nil, err + } + if bounds == nil { + return nil, errors.New("GetSystrayBounds returned nil") + } + + monitor := w32.MonitorFromWindow(s.hwnd, w32.MONITOR_DEFAULTTONEAREST) + if monitor == 0 { + return nil, errors.New("failed to get monitor") + } + + return &Rect{ + X: int(bounds.Left), + Y: int(bounds.Top), + Width: int(bounds.Right - bounds.Left), + Height: int(bounds.Bottom - bounds.Top), + }, nil +} + +func (s *windowsSystemTray) iconIsInTrayBounds() (bool, error) { + if s.hwnd == 0 { + return false, errors.New("system tray window handle not initialized") + } + + bounds, err := w32.GetSystrayBounds(s.hwnd, s.uid) + if err != nil { + return false, err + } + if bounds == nil { + return false, errors.New("GetSystrayBounds returned nil") + } + + taskbarRect := w32.GetTaskbarPosition() + if taskbarRect == nil { + return false, errors.New("failed to get taskbar position") + } + + inTasksBar := w32.RectInRect(bounds, &taskbarRect.Rc) + if inTasksBar { + return true, nil + } + + return false, nil +} + +func (s *windowsSystemTray) getScreen() (*Screen, error) { + if s.hwnd == 0 { + return nil, errors.New("system tray window handle not initialized") + } + // Get the screen for this systray + return getScreenForWindowHwnd(s.hwnd) +} + +func (s *windowsSystemTray) setMenu(menu *Menu) { + s.updateMenu(menu) +} + +func (s *windowsSystemTray) run() { + s.hwnd = w32.CreateWindowEx( + 0, + w32.MustStringToUTF16Ptr(globalApplication.options.Windows.WndClass), + nil, + 0, + 0, + 0, + 0, + 0, + w32.HWND_MESSAGE, + 0, + 0, + nil) + if s.hwnd == 0 { + globalApplication.fatal("failed to create tray window: %s", syscall.GetLastError()) + return + } + + s.uid = uint32(s.parent.id) + + // Resolve the base icons once so we can reuse them for light/dark modes + defaultIcon := w32.LoadIconWithResourceID(w32.GetModuleHandle(""), w32.RT_ICON) + + // Priority: custom icon > default app icon > built-in icon + if s.parent.icon != nil { + icon, err := w32.CreateSmallHIconFromImage(s.parent.icon) + if err == nil { + s.lightModeIcon = icon + s.lightModeIconOwned = true + } else { + globalApplication.warning("failed to create systray icon: %v", err) + } + } + + if s.lightModeIcon == 0 && defaultIcon != 0 { + s.lightModeIcon = defaultIcon + s.lightModeIconOwned = false + } + + if s.lightModeIcon == 0 { + icon, err := w32.CreateSmallHIconFromImage(icons.SystrayLight) + if err != nil { + globalApplication.warning("failed to create systray icon: %v", err) + s.lightModeIcon = 0 + s.lightModeIconOwned = false + } else { + s.lightModeIcon = icon + s.lightModeIconOwned = true + } + } + + if s.parent.darkModeIcon != nil { + icon, err := w32.CreateSmallHIconFromImage(s.parent.darkModeIcon) + if err == nil { + s.darkModeIcon = icon + s.darkModeIconOwned = true + } else { + globalApplication.warning("failed to create systray dark mode icon: %v", err) + } + } + + if s.darkModeIcon == 0 && s.parent.icon != nil && s.lightModeIcon != 0 { + s.darkModeIcon = s.lightModeIcon + s.darkModeIconOwned = false + } + + if s.darkModeIcon == 0 && defaultIcon != 0 { + s.darkModeIcon = defaultIcon + s.darkModeIconOwned = false + } + + if s.darkModeIcon == 0 { + icon, err := w32.CreateSmallHIconFromImage(icons.SystrayDark) + if err != nil { + globalApplication.warning("failed to create systray dark mode icon: %v", err) + s.darkModeIcon = 0 + s.darkModeIconOwned = false + } else { + s.darkModeIcon = icon + s.darkModeIconOwned = true + } + } + + if _, err := s.show(); err != nil { + // Initial systray add can fail when the shell is not available. This is handled downstream via TaskbarCreated message. + globalApplication.warning("initial systray add failed: %v", err) + } + + if s.parent.menu != nil { + s.updateMenu(s.parent.menu) + } + + // Set Default Callbacks + if s.parent.clickHandler == nil { + s.parent.clickHandler = func() { + globalApplication.debug("Left Button Clicked") + } + } + + if s.parent.rightClickHandler == nil { + s.parent.rightClickHandler = func() { + if s.menu != nil { + s.openMenu() + } + } + } + + // Listen for dark mode changes + s.cancelTheme = globalApplication.Event.OnApplicationEvent(events.Windows.SystemThemeChanged, func(event *ApplicationEvent) { + s.updateIcon() + }) + + // Register the system tray + getNativeApplication().registerSystemTray(s) +} + +func (s *windowsSystemTray) updateIcon() { + var newIcon w32.HICON + if w32.IsCurrentlyDarkMode() { + newIcon = s.darkModeIcon + } else { + newIcon = s.lightModeIcon + } + if s.currentIcon == newIcon { + return + } + + // Store the old icon to destroy it after updating + oldIcon := s.currentIcon + oldIconOwned := s.currentIconOwned + + s.currentIcon = newIcon + nid := s.newNotifyIconData() + nid.UFlags = w32.NIF_ICON + if s.currentIcon != 0 { + nid.HIcon = s.currentIcon + } + + if !w32.ShellNotifyIcon(w32.NIM_MODIFY, &nid) { + panic(syscall.GetLastError()) + } + + // Track ownership of the current icon so we know if we can destroy it later + currentOwned := false + if newIcon != 0 { + if newIcon == s.lightModeIcon && s.lightModeIconOwned { + currentOwned = true + } else if newIcon == s.darkModeIcon && s.darkModeIconOwned { + currentOwned = true + } + } + s.currentIconOwned = currentOwned + + // Destroy the old icon handle if it exists, we owned it, and nothing else references it + s.releaseIcon(oldIcon, oldIconOwned, s.lightModeIcon, s.darkModeIcon) +} + +func (s *windowsSystemTray) newNotifyIconData() w32.NOTIFYICONDATA { + nid := w32.NOTIFYICONDATA{ + UID: s.uid, + HWnd: s.hwnd, + } + nid.CbSize = uint32(unsafe.Sizeof(nid)) + return nid +} + +func (s *windowsSystemTray) setIcon(icon []byte) { + newIcon, err := w32.CreateSmallHIconFromImage(icon) + if err != nil { + globalApplication.error("failed to create systray light mode icon: %v", err) + return + } + + oldLight := s.lightModeIcon + oldLightOwned := s.lightModeIconOwned + oldDark := s.darkModeIcon + oldDarkOwned := s.darkModeIconOwned + + s.lightModeIcon = newIcon + s.lightModeIconOwned = true + + // Keep dark mode in sync when both modes shared the same handle (or dark was unset). + if s.darkModeIcon == 0 || s.darkModeIcon == oldLight { + s.darkModeIcon = newIcon + s.darkModeIconOwned = false + } + + // Only free previous handles we own that are no longer referenced. + s.releaseIcon(oldLight, oldLightOwned, s.lightModeIcon, s.darkModeIcon) + if oldDark != s.darkModeIcon { + s.releaseIcon(oldDark, oldDarkOwned, s.lightModeIcon, s.darkModeIcon) + } + + s.updateIcon() +} + +func (s *windowsSystemTray) setDarkModeIcon(icon []byte) { + newIcon, err := w32.CreateSmallHIconFromImage(icon) + if err != nil { + globalApplication.error("failed to create systray dark mode icon: %v", err) + return + } + + oldDark := s.darkModeIcon + oldDarkOwned := s.darkModeIconOwned + oldLight := s.lightModeIcon + oldLightOwned := s.lightModeIconOwned + + s.darkModeIcon = newIcon + s.darkModeIconOwned = true + + lightReplaced := false + + // Keep light mode in sync when both modes shared the same handle (or light was unset). + if s.lightModeIcon == 0 || s.lightModeIcon == oldDark { + s.lightModeIcon = newIcon + s.lightModeIconOwned = false + lightReplaced = true + } + + // Only free the previous handle if nothing else keeps a reference to it. + s.releaseIcon(oldDark, oldDarkOwned, s.lightModeIcon, s.darkModeIcon) + if lightReplaced { + s.releaseIcon(oldLight, oldLightOwned, s.lightModeIcon, s.darkModeIcon) + } + + s.updateIcon() +} + +func newSystemTrayImpl(parent *SystemTray) systemTrayImpl { + return &windowsSystemTray{ + parent: parent, + } +} + +func (s *windowsSystemTray) wndProc(msg uint32, wParam, lParam uintptr) uintptr { + switch msg { + case wmUserSystray: + msg := lParam & 0xffff + switch msg { + case w32.WM_LBUTTONUP: + if s.parent.clickHandler != nil { + s.parent.clickHandler() + } + case w32.WM_RBUTTONUP: + if s.parent.rightClickHandler != nil { + s.parent.rightClickHandler() + } + case w32.WM_LBUTTONDBLCLK: + if s.parent.doubleClickHandler != nil { + s.parent.doubleClickHandler() + } + case w32.WM_RBUTTONDBLCLK: + if s.parent.rightDoubleClickHandler != nil { + s.parent.rightDoubleClickHandler() + } + case w32.NIN_POPUPOPEN: + if s.parent.mouseEnterHandler != nil { + s.parent.mouseEnterHandler() + } + case w32.NIN_POPUPCLOSE: + if s.parent.mouseLeaveHandler != nil { + s.parent.mouseLeaveHandler() + } + } + // println(w32.WMMessageToString(msg)) + + // Menu processing + case w32.WM_COMMAND: + cmdMsgID := int(wParam & 0xffff) + if s.menu != nil { + s.menu.ProcessCommand(cmdMsgID) + } + default: + // msg := int(wParam & 0xffff) + // println(w32.WMMessageToString(uintptr(msg))) + } + + return w32.DefWindowProc(s.hwnd, msg, wParam, lParam) +} + +func (s *windowsSystemTray) updateMenu(menu *Menu) { + s.menu = NewPopupMenu(s.hwnd, menu) + s.menu.onMenuOpen = s.parent.onMenuOpen + s.menu.onMenuClose = s.parent.onMenuClose + s.menu.Update() +} + +func (s *windowsSystemTray) setTooltip(tooltip string) { + // Create a new NOTIFYICONDATA structure + nid := s.newNotifyIconData() + nid.UFlags = w32.NIF_TIP | w32.NIF_SHOWTIP + + // Ensure the tooltip length is within the limit (128 characters including null terminate characters for szTip for Windows 2000 and later) + // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-notifyicondataw + tooltipUTF16, err := w32.StringToUTF16(truncateUTF16(tooltip, 127)) + if err != nil { + return + } + + copy(nid.SzTip[:], tooltipUTF16) + + // Modify the tray icon with the new tooltip + if !w32.ShellNotifyIcon(w32.NIM_MODIFY, &nid) { + return + } +} + +// ---- Unsupported ---- +func (s *windowsSystemTray) setLabel(label string) {} + +func (s *windowsSystemTray) setTemplateIcon(_ []byte) { + // Unsupported - do nothing +} + +func (s *windowsSystemTray) setIconPosition(position IconPosition) { + // Unsupported - do nothing +} + +func (s *windowsSystemTray) destroy() { + if s.cancelTheme != nil { + s.cancelTheme() + s.cancelTheme = nil + } + // Remove and delete the system tray + getNativeApplication().unregisterSystemTray(s) + if s.menu != nil { + s.menu.Destroy() + } + + // destroy the notification icon + nid := s.newNotifyIconData() + if !w32.ShellNotifyIcon(w32.NIM_DELETE, &nid) { + globalApplication.debug(syscall.GetLastError().Error()) + } + + // Clean up icon handles + lightIcon := s.lightModeIcon + darkIcon := s.darkModeIcon + currentIcon := s.currentIcon + + s.releaseIcon(lightIcon, s.lightModeIconOwned) + s.releaseIcon(darkIcon, s.darkModeIconOwned, lightIcon) + s.releaseIcon(currentIcon, s.currentIconOwned, lightIcon, darkIcon) + + s.lightModeIcon = 0 + s.lightModeIconOwned = false + s.darkModeIcon = 0 + s.darkModeIconOwned = false + s.currentIcon = 0 + s.currentIconOwned = false + + w32.DestroyWindow(s.hwnd) + s.hwnd = 0 +} + +func (s *windowsSystemTray) Show() { + if s.hwnd == 0 { + return + } + + nid := s.newNotifyIconData() + nid.UFlags = w32.NIF_STATE + nid.DwStateMask = w32.NIS_HIDDEN + nid.DwState = 0 + if !w32.ShellNotifyIcon(w32.NIM_MODIFY, &nid) { + globalApplication.debug("ShellNotifyIcon NIM_MODIFY show failed", "error", syscall.GetLastError()) + } +} + +func (s *windowsSystemTray) Hide() { + if s.hwnd == 0 { + return + } + + nid := s.newNotifyIconData() + nid.UFlags = w32.NIF_STATE + nid.DwStateMask = w32.NIS_HIDDEN + nid.DwState = w32.NIS_HIDDEN + if !w32.ShellNotifyIcon(w32.NIM_MODIFY, &nid) { + globalApplication.debug("ShellNotifyIcon NIM_MODIFY hide failed", "error", syscall.GetLastError()) + } +} + +func (s *windowsSystemTray) show() (w32.NOTIFYICONDATA, error) { + nid := s.newNotifyIconData() + nid.UFlags = w32.NIF_ICON | w32.NIF_MESSAGE + nid.HIcon = s.currentIcon + nid.UCallbackMessage = wmUserSystray + + if !w32.ShellNotifyIcon(w32.NIM_ADD, &nid) { + err := syscall.GetLastError() + return nid, fmt.Errorf("ShellNotifyIcon NIM_ADD failed: %w", err) + } + + nid.UVersion = w32.NOTIFYICON_VERSION_4 + if !w32.ShellNotifyIcon(w32.NIM_SETVERSION, &nid) { + err := syscall.GetLastError() + return nid, fmt.Errorf("ShellNotifyIcon NIM_SETVERSION failed: %w", err) + } + + s.updateIcon() + + if s.parent.tooltip != "" { + s.setTooltip(s.parent.tooltip) + } + + return nid, nil +} + +func truncateUTF16(s string, maxUnits int) string { + var units int + for i, r := range s { + var u int + + // check if rune will take 2 UTF-16 units + if r > 0xFFFF { + u = 2 + } else { + u = 1 + } + + if units+u > maxUnits { + return s[:i] + } + units += u + } + + return s +} diff --git a/v3/pkg/application/transport.go b/v3/pkg/application/transport.go new file mode 100644 index 000000000..a8437e954 --- /dev/null +++ b/v3/pkg/application/transport.go @@ -0,0 +1,67 @@ +package application + +import ( + "context" + "net/http" +) + +// Transport defines the interface for custom IPC transport implementations. +// Developers can provide their own transport (e.g., WebSocket, custom protocol) +// while retaining all Wails generated bindings and event communication. +// +// The transport is responsible for: +// - Receiving runtime call requests from the frontend +// - Processing them through Wails' MessageProcessor +// - Sending responses back to the frontend +// +// Example use case: Implementing WebSocket-based transport instead of HTTP fetch. +type Transport interface { + // Start initializes and starts the transport layer. + // The provided handler should be called to process Wails runtime requests. + // The context is the application context and will be cancelled on shutdown. + Start(ctx context.Context, messageProcessor *MessageProcessor) error + + JSClient() []byte + + // Stop gracefully shuts down the transport. + Stop() error +} + +// AssetServerTransport is an optional interface that transports can implement +// to serve assets over HTTP, enabling browser-based deployments. +// +// When a transport implements this interface, Wails will call ServeAssets() +// after Start() to provide the asset server handler. The transport should +// integrate this handler into its HTTP server to serve HTML, CSS, JS, and +// other static assets alongside the IPC transport. +// +// This is useful for: +// - Running Wails apps in a browser instead of a webview +// - Exposing the app over a network +// - Custom server configurations with both assets and IPC +type AssetServerTransport interface { + Transport + + // ServeAssets configures the transport to serve assets. + // The assetHandler is Wails' internal asset server that handles: + // - All static assets (HTML, CSS, JS, images, etc.) + // - /wails/runtime.js - The Wails runtime library + // + // The transport should integrate this handler into its HTTP server. + // Typically this means mounting it at "/" and ensuring the IPC endpoint + // (e.g., /wails/ws for WebSocket) is handled separately. + // + // This method is called after Start() completes successfully. + ServeAssets(assetHandler http.Handler) error +} + +// TransportHTTPHandler is an optional interface that transports can implement +// to provide HTTP middleware for the Wails asset server in webview scenarios. +// +// When a transport implements this interface, Wails will use Handler() in +// asset server middlewares that may provide handling for request done from webview to wails:// URLs. +// +// This is used by the default HTTP transport to handle IPC endpoints. +type TransportHTTPHandler interface { + Handler() func(next http.Handler) http.Handler +} diff --git a/v3/pkg/application/transport_event_ipc.go b/v3/pkg/application/transport_event_ipc.go new file mode 100644 index 000000000..4b3309ee6 --- /dev/null +++ b/v3/pkg/application/transport_event_ipc.go @@ -0,0 +1,17 @@ +package application + +type EventIPCTransport struct { + app *App +} + +func (t *EventIPCTransport) DispatchWailsEvent(event *CustomEvent) { + // Snapshot windows under RLock + t.app.windowsLock.RLock() + defer t.app.windowsLock.RUnlock() + for _, window := range t.app.windows { + if event.IsCancelled() { + return + } + window.DispatchWailsEvent(event) + } +} diff --git a/v3/pkg/application/transport_http.go b/v3/pkg/application/transport_http.go new file mode 100644 index 000000000..0c2b0fd45 --- /dev/null +++ b/v3/pkg/application/transport_http.go @@ -0,0 +1,216 @@ +package application + +import ( + "bytes" + "context" + "errors" + "io" + "log/slog" + "net/http" + "strconv" + "sync" + + "encoding/json" + + "github.com/wailsapp/wails/v3/pkg/errs" +) + +// bufferPool reduces allocations for reading request bodies. +// Buffers larger than maxPooledBufferSize are not returned to the pool +// to prevent memory bloat from occasional large requests (e.g., images). +const maxPooledBufferSize = 512 * 1024 // 512KB + +var bufferPool = sync.Pool{ + New: func() any { + return bytes.NewBuffer(make([]byte, 0, 4096)) + }, +} + +type HTTPTransport struct { + messageProcessor *MessageProcessor + logger *slog.Logger +} + +func NewHTTPTransport(opts ...HTTPTransportOption) *HTTPTransport { + t := &HTTPTransport{ + logger: slog.Default(), + } + + // Apply options + for _, opt := range opts { + opt(t) + } + + return t +} + +// HTTPTransportOption is a functional option for configuring HTTPTransport +type HTTPTransportOption func(*HTTPTransport) + +// HTTPTransportWithLogger is a functional option to set the logger for HTTPTransport. +func HTTPTransportWithLogger(logger *slog.Logger) HTTPTransportOption { + return func(t *HTTPTransport) { + t.logger = logger + } +} + +func (t *HTTPTransport) Start(ctx context.Context, processor *MessageProcessor) error { + t.messageProcessor = processor + + return nil +} + +func (t *HTTPTransport) JSClient() []byte { + return nil +} + +func (t *HTTPTransport) Stop() error { + return nil +} + +type request struct { + Object *int `json:"object"` + Method *int `json:"method"` + Args json.RawMessage `json:"args"` +} + +func (t *HTTPTransport) Handler() func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + path := req.URL.Path + switch path { + case "/wails/runtime": + t.handleRuntimeRequest(rw, req) + default: + next.ServeHTTP(rw, req) + } + }) + } +} + +func (t *HTTPTransport) handleRuntimeRequest(rw http.ResponseWriter, r *http.Request) { + // Use pooled buffer to reduce allocations + buf := bufferPool.Get().(*bytes.Buffer) + buf.Reset() + defer func() { + // Don't return large buffers to pool to prevent memory bloat + if buf.Cap() <= maxPooledBufferSize { + bufferPool.Put(buf) + } + }() + + _, err := io.Copy(buf, r.Body) + if err != nil { + t.httpError(rw, errs.WrapInvalidRuntimeCallErrorf(err, "Unable to read request body")) + return + } + + var body request + err = json.Unmarshal(buf.Bytes(), &body) + if err != nil { + t.httpError(rw, errs.WrapInvalidRuntimeCallErrorf(err, "Unable to parse request body as JSON")) + return + } + + if body.Object == nil { + t.httpError(rw, errs.NewInvalidRuntimeCallErrorf("missing object value")) + return + } + + if body.Method == nil { + t.httpError(rw, errs.NewInvalidRuntimeCallErrorf("missing method value")) + return + } + + windowIdStr := r.Header.Get(webViewRequestHeaderWindowId) + windowId := 0 + if windowIdStr != "" { + windowId, err = strconv.Atoi(windowIdStr) + if err != nil { + t.httpError(rw, errs.WrapInvalidRuntimeCallErrorf(err, "error decoding windowId value")) + return + } + } + + windowName := r.Header.Get(webViewRequestHeaderWindowName) + clientId := r.Header.Get("x-wails-client-id") + + resp, err := t.messageProcessor.HandleRuntimeCallWithIDs(r.Context(), &RuntimeRequest{ + Object: *body.Object, + Method: *body.Method, + Args: &Args{body.Args}, + WebviewWindowID: uint32(windowId), + WebviewWindowName: windowName, + ClientID: clientId, + }) + + if err != nil { + t.httpError(rw, err) + return + } + + if stringResp, ok := resp.(string); ok { + t.text(rw, stringResp) + return + } + + t.json(rw, resp) +} + +func (t *HTTPTransport) text(rw http.ResponseWriter, data string) { + rw.Header().Set("Content-Type", "text/plain") + rw.WriteHeader(http.StatusOK) + _, err := rw.Write([]byte(data)) + if err != nil { + t.error("Unable to write json payload. Please report this to the Wails team!", "error", err) + return + } +} + +func (t *HTTPTransport) json(rw http.ResponseWriter, data any) { + rw.Header().Set("Content-Type", "application/json") + rw.WriteHeader(http.StatusOK) + // convert data to json + var jsonPayload = []byte("{}") + var err error + if data != nil { + jsonPayload, err = json.Marshal(data) + if err != nil { + t.error("Unable to convert data to JSON. Please report this to the Wails team!", "error", err) + return + } + } + _, err = rw.Write(jsonPayload) + if err != nil { + t.error("Unable to write json payload. Please report this to the Wails team!", "error", err) + return + } +} + +func (t *HTTPTransport) httpError(rw http.ResponseWriter, err error) { + t.error(err.Error()) + // return JSON error if it's a CallError + var bytes []byte + if cerr := (*CallError)(nil); errors.As(err, &cerr) { + if data, jsonErr := json.Marshal(cerr); jsonErr == nil { + rw.Header().Set("Content-Type", "application/json") + bytes = data + } else { + rw.Header().Set("Content-Type", "text/plain") + bytes = []byte(err.Error()) + } + } else { + rw.Header().Set("Content-Type", "text/plain") + bytes = []byte(err.Error()) + } + rw.WriteHeader(http.StatusUnprocessableEntity) + + _, err = rw.Write(bytes) + if err != nil { + t.error("Unable to write error response:", "error", err) + } +} + +func (t *HTTPTransport) error(message string, args ...any) { + t.logger.Error(message, args...) +} diff --git a/v3/pkg/application/urlvalidator.go b/v3/pkg/application/urlvalidator.go new file mode 100644 index 000000000..f9eecf0bb --- /dev/null +++ b/v3/pkg/application/urlvalidator.go @@ -0,0 +1,49 @@ +package application + +import ( + "errors" + "fmt" + "net/url" + "regexp" + "strings" +) + +func ValidateAndSanitizeURL(rawURL string) (string, error) { + if strings.Contains(rawURL, "\x00") { + return "", errors.New("null bytes not allowed in URL") + } + + for i, r := range rawURL { + if r < 32 && r != 9 { + return "", fmt.Errorf("control character at position %d not allowed", i) + } + } + + shellDangerous := `[;|` + "`" + `$\\<>*{}\[\]()~! \t\n\r]` + if matched, _ := regexp.MatchString(shellDangerous, rawURL); matched { + return "", errors.New("shell metacharacters not allowed") + } + + unicodeDangerous := "[\u0000-\u001F\u007F\u00A0\u1680\u2000-\u200F\u2028-\u202F\u205F\u3000\uFEFF\u200B-\u200D\u2060\u2061\u2062\u2063\u2064\u206A-\u206F\uFFF0-\uFFFF]" + if matched, _ := regexp.MatchString(unicodeDangerous, rawURL); matched { + return "", errors.New("dangerous unicode characters not allowed") + } + + parsedURL, err := url.Parse(rawURL) + if err != nil { + return "", fmt.Errorf("invalid URL format: %v", err) + } + + scheme := strings.ToLower(parsedURL.Scheme) + + if scheme == "javascript" || scheme == "data" || scheme == "file" || scheme == "ftp" || scheme == "" { + return "", errors.New("scheme not allowed") + } + + if (scheme == "http" || scheme == "https") && parsedURL.Host == "" { + return "", fmt.Errorf("missing host for %s URL", scheme) + } + + sanitizedURL := parsedURL.String() + return sanitizedURL, nil +} diff --git a/v3/pkg/application/urlvalidator_test.go b/v3/pkg/application/urlvalidator_test.go new file mode 100644 index 000000000..803098899 --- /dev/null +++ b/v3/pkg/application/urlvalidator_test.go @@ -0,0 +1,262 @@ +package application_test + +import ( + "strings" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func TestValidateURL(t *testing.T) { + testCases := []struct { + name string + url string + shouldErr bool + errMsg string + expected string + }{ + { + name: "valid https URL", + url: "https://www.example.com", + shouldErr: false, + expected: "https://www.example.com", + }, + { + name: "valid http URL", + url: "http://example.com", + shouldErr: false, + expected: "http://example.com", + }, + { + name: "URL with query parameters", + url: "https://example.com/search?q=cats&dogs", + shouldErr: false, + expected: "https://example.com/search?q=cats&dogs", + }, + { + name: "URL with port", + url: "https://example.com:8080/path", + shouldErr: false, + expected: "https://example.com:8080/path", + }, + { + name: "URL with fragment", + url: "https://example.com/page#section", + shouldErr: false, + expected: "https://example.com/page#section", + }, + { + name: "urlencode params", + url: "http://google.com/ ----browser-subprocess-path=C:\\\\Users\\\\Public\\\\test.bat", + shouldErr: true, + errMsg: "shell metacharacters", + }, + { + name: "javascript scheme", + url: "javascript:alert('XSS')", + shouldErr: true, + errMsg: "shell metacharacters", + }, + { + name: "data scheme", + url: "data:text/html,", + shouldErr: true, + errMsg: "shell metacharacters", + }, + { + name: "file scheme", + url: "file:///etc/passwd", + shouldErr: true, + errMsg: "scheme not allowed", + }, + { + name: "ftp scheme", + url: "ftp://ftp.example.com/file", + shouldErr: true, + errMsg: "scheme not allowed", + }, + { + name: "missing scheme", + url: "example.com", + shouldErr: true, + errMsg: "scheme not allowed", + }, + { + name: "empty string", + url: "", + shouldErr: true, + errMsg: "scheme not allowed", + }, + { + name: "null byte in URL", + url: "https://example.com\x00/malicious", + shouldErr: true, + errMsg: "null bytes not allowed", + }, + { + name: "control character", + url: "https://example.com\x01", + shouldErr: true, + errMsg: "control character", + }, + { + name: "shell injection with semicolon", + url: "https://example.com/;rm -rf /", + shouldErr: true, + errMsg: "shell metacharacters", + }, + { + name: "shell injection with pipe", + url: "https://example.com/|cat /etc/passwd", + shouldErr: true, + errMsg: "shell metacharacters", + }, + { + name: "shell injection with backtick", + url: "https://example.com/`whoami`", + shouldErr: true, + errMsg: "shell metacharacters", + }, + { + name: "shell injection with dollar", + url: "https://example.com/$(whoami)", + shouldErr: true, + errMsg: "shell metacharacters", + }, + { + name: "unicode null", + url: "https://example.com/\u0000", + shouldErr: true, + errMsg: "null bytes not allowed", + }, + { + name: "missing host for http", + url: "http:///path", + shouldErr: true, + errMsg: "missing host", + }, + { + name: "missing host for https", + url: "https:///path", + shouldErr: true, + errMsg: "missing host", + }, + { + name: "URL with newline", + url: "https://example.com/path\n/newline", + shouldErr: true, + errMsg: "control character", + }, + { + name: "URL with carriage return", + url: "https://example.com/path\r/return", + shouldErr: true, + errMsg: "control character", + }, + { + name: "URL with tab", + url: "https://example.com/path\t/tab", + shouldErr: true, + errMsg: "shell metacharacters", + }, + { + name: "URL with space in path", + url: "https://example.com/path with spaces", + shouldErr: true, + errMsg: "shell metacharacters", + }, + { + name: "URL with angle brackets", + url: "https://example.com/ + + \ No newline at end of file diff --git a/v3/tests/window-visibility-test/go.mod b/v3/tests/window-visibility-test/go.mod new file mode 100644 index 000000000..bc824bdac --- /dev/null +++ b/v3/tests/window-visibility-test/go.mod @@ -0,0 +1,52 @@ +module window-visibility-test + +go 1.24 + +replace github.com/wailsapp/wails/v3 => ../../ + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.62 + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-git/go-git/v5 v5.16.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/lmittmann/tint v1.1.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.52.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/wailsapp/go-webview2 v1.0.23 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +// Add any other dependencies that might be needed +// These will be resolved when the user runs go mod tidy diff --git a/v3/tests/window-visibility-test/go.sum b/v3/tests/window-visibility-test/go.sum new file mode 100644 index 000000000..56f1153ea --- /dev/null +++ b/v3/tests/window-visibility-test/go.sum @@ -0,0 +1,147 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= +github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/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-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/tests/window-visibility-test/main.go b/v3/tests/window-visibility-test/main.go new file mode 100644 index 000000000..247c1035c --- /dev/null +++ b/v3/tests/window-visibility-test/main.go @@ -0,0 +1,308 @@ +package main + +import ( + "embed" + "fmt" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// WindowTestService provides methods for testing window visibility scenarios +type WindowTestService struct { + app *application.App +} + +// NewWindowTestService creates a new window test service +func NewWindowTestService() *WindowTestService { + return &WindowTestService{} +} + +// SetApp sets the application reference (internal method, not exposed to frontend) +func (w *WindowTestService) setApp(app *application.App) { + w.app = app +} + +// CreateNormalWindow creates a standard window - should show immediately +func (w *WindowTestService) CreateNormalWindow() string { + log.Println("Creating normal window...") + + w.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Normal Window - Should Show Immediately", + Width: 600, + Height: 400, + X: 100, + Y: 100, + HTML: "Normal Window

        ✅ Normal Window

        This window should have appeared immediately after clicking the button.

        Timestamp: " + time.Now().Format("15:04:05") + "

        ", + }) + + return "Normal window created" +} + +// CreateDelayedContentWindow creates a window with delayed content to test navigation timing +func (w *WindowTestService) CreateDelayedContentWindow() string { + log.Println("Creating delayed content window...") + + // Use HTML that will take time to load (simulates heavy Vue app) + delayedHTML := ` + + + Delayed Content + + + +

        ⏳ Delayed Content Window

        +

        This window tests navigation completion timing.

        +
        +

        Loading... (simulates heavy content)

        + +

        Window container should be visible immediately, even during load.

        + + ` + + w.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Delayed Content Window - Test Navigation Timing", + Width: 600, + Height: 400, + X: 150, + Y: 150, + HTML: delayedHTML, + }) + + return "Delayed content window created" +} + +// CreateHiddenThenShowWindow creates a hidden window then shows it after delay +func (w *WindowTestService) CreateHiddenThenShowWindow() string { + log.Println("Creating hidden then show window...") + + window := w.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Hidden Then Show Window - Test Show() Robustness", + Width: 600, + Height: 400, + X: 200, + Y: 200, + HTML: "Hidden Then Show

        🔄 Hidden Then Show Window

        This window was created hidden and then shown after 2 seconds.

        Should test the robustness of the show() method.

        Created at: " + time.Now().Format("15:04:05") + "

        ", + Hidden: true, // Start hidden + }) + + // Show after 2 seconds to test delayed showing + go func() { + time.Sleep(2 * time.Second) + log.Println("Showing previously hidden window...") + window.Show() + }() + + return "Hidden window created, will show in 2 seconds" +} + +// CreateMultipleWindows creates multiple windows simultaneously to test performance +func (w *WindowTestService) CreateMultipleWindows() string { + log.Println("Creating multiple windows...") + + for i := 0; i < 3; i++ { + bgColors := []string{"#ff9a9e,#fecfef", "#a18cd1,#fbc2eb", "#fad0c4,#ffd1ff"} + content := fmt.Sprintf(` + + + Batch Window %d + + + +

        🔢 Batch Window %d

        +

        Part of multiple windows stress test

        +

        All windows should appear quickly and simultaneously

        +

        Created at: %s

        + + `, i+1, bgColors[i], i+1, time.Now().Format("15:04:05")) + + w.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: fmt.Sprintf("Batch Window %d - Stress Test", i+1), + Width: 400, + Height: 300, + X: 250 + (i * 50), + Y: 250 + (i * 50), + HTML: content, + }) + } + + return "Created 3 windows simultaneously" +} + +// CreateEfficiencyModeTestWindow creates a window designed to trigger efficiency mode issues +func (w *WindowTestService) CreateEfficiencyModeTestWindow() string { + log.Println("Creating efficiency mode test window...") + + // Create content that might trigger efficiency mode or WebView2 delays + heavyHTML := ` + + + Efficiency Mode Test + + + +

        ⚡ Efficiency Mode Test Window

        +

        This window tests the fix for Windows 10 Pro efficiency mode issue #2861

        + +
        +

        Window Container Status

        +

        ✅ Window container is visible (this text proves it)

        +
        + +
        +

        WebView2 Status

        +

        ⏳ WebView2 navigation in progress...

        + +
        + +
        +

        Heavy Content (simulates Vue.js app)

        +
        + Loading heavy content... +
        +
        + + + + ` + + w.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Efficiency Mode Test - Issue #2861 Reproduction", + Width: 700, + Height: 500, + X: 300, + Y: 300, + HTML: heavyHTML, + }) + + return "Efficiency mode test window created" +} + +// GetWindowCount returns the current number of windows +func (w *WindowTestService) GetWindowCount() int { + // This would need to be implemented based on the app's window tracking + // For now, return a placeholder + return 1 // Main window +} + +//go:embed assets/* +var assets embed.FS + +func main() { + // Create the service + service := NewWindowTestService() + + // Create application with menu + app := application.New(application.Options{ + Name: "Window Visibility Test", + Description: "Test application for window visibility robustness (Issue #2861)", + Services: []application.Service{ + application.NewService(service), + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + }) + + // Set app reference in service + service.setApp(app) + + // Create application menu + menu := app.NewMenu() + + // File menu + fileMenu := menu.AddSubmenu("File") + fileMenu.Add("New Normal Window").OnClick(func(ctx *application.Context) { + service.CreateNormalWindow() + }) + fileMenu.Add("New Delayed Content Window").OnClick(func(ctx *application.Context) { + service.CreateDelayedContentWindow() + }) + fileMenu.AddSeparator() + fileMenu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + + // Test menu + testMenu := menu.AddSubmenu("Tests") + testMenu.Add("Hidden Then Show Window").OnClick(func(ctx *application.Context) { + service.CreateHiddenThenShowWindow() + }) + testMenu.Add("Multiple Windows Stress Test").OnClick(func(ctx *application.Context) { + service.CreateMultipleWindows() + }) + testMenu.Add("Efficiency Mode Test").OnClick(func(ctx *application.Context) { + service.CreateEfficiencyModeTestWindow() + }) + + // Help menu + helpMenu := menu.AddSubmenu("Help") + helpMenu.Add("About").OnClick(func(ctx *application.Context) { + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "About Window Visibility Test", + Width: 500, + Height: 400, + X: 400, + Y: 300, + HTML: "About

        Window Visibility Test

        This application tests the fixes for Wails v3 issue #2861

        Windows 10 Pro Efficiency Mode Fix

        Tests window container vs WebView content visibility


        Created for testing robust window visibility patterns

        ", + }) + }) + + app.Menu.Set(menu) + + // Create main window + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window Visibility Test - Issue #2861", + Width: 800, + Height: 600, + X: 50, + Y: 50, + URL: "/index.html", + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/verify-ios-setup.sh b/v3/verify-ios-setup.sh new file mode 100644 index 000000000..1a6e345c2 --- /dev/null +++ b/v3/verify-ios-setup.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +echo "=== iOS Build System Verification ===" +echo +echo "Checking iOS build assets..." +echo + +# Check if files exist +echo "1. Checking build_assets/ios directory:" +if [ -d "internal/commands/build_assets/ios" ]; then + echo " ✅ iOS build_assets directory exists" + ls -la internal/commands/build_assets/ios/ +else + echo " ❌ iOS build_assets directory missing" +fi +echo + +echo "2. Checking updatable_build_assets/ios directory:" +if [ -d "internal/commands/updatable_build_assets/ios" ]; then + echo " ✅ iOS updatable_build_assets directory exists" + ls -la internal/commands/updatable_build_assets/ios/ +else + echo " ❌ iOS updatable_build_assets directory missing" +fi +echo + +echo "3. Checking iOS implementation files:" +for file in pkg/application/application_ios.go pkg/application/application_ios.h pkg/application/application_ios.m; do + if [ -f "$file" ]; then + echo " ✅ $file exists" + else + echo " ❌ $file missing" + fi +done +echo + +echo "4. Checking iOS example:" +if [ -d "examples/ios-poc" ]; then + echo " ✅ ios-poc example exists" + ls -la examples/ios-poc/ +else + echo " ❌ ios-poc example missing" +fi +echo + +echo "5. Checking main Taskfile includes iOS:" +if grep -q "ios:" internal/templates/_common/Taskfile.tmpl.yml 2>/dev/null; then + echo " ✅ iOS included in main Taskfile template" +else + echo " ❌ iOS not included in main Taskfile template" +fi +echo + +echo "6. Checking Xcode tools:" +if command -v xcrun &> /dev/null; then + echo " ✅ xcrun available" + echo " SDK Path: $(xcrun --sdk iphonesimulator --show-sdk-path 2>/dev/null || echo 'Not found')" +else + echo " ❌ xcrun not available" +fi +echo + +echo "7. iOS Build System Summary:" +echo " - Static assets: internal/commands/build_assets/ios/" +echo " - Templates: internal/commands/updatable_build_assets/ios/" +echo " - Implementation: pkg/application/application_ios.*" +echo " - Example: examples/ios-poc/" +echo " - Build script: build_ios.sh" +echo + +echo "=== Verification Complete ===" +echo +echo "The iOS build system structure is in place and ready for:" +echo "1. Creating new iOS projects with 'wails3 init'" +echo "2. Building with 'task ios:build'" +echo "3. Running with 'task ios:run'" +echo +echo "Note: Full compilation requires iOS development environment setup." \ No newline at end of file diff --git a/v3/wep/README.md b/v3/wep/README.md new file mode 100644 index 000000000..63ea57c98 --- /dev/null +++ b/v3/wep/README.md @@ -0,0 +1,69 @@ +# Wails Enhancement Proposal (WEP) Process + +## Introduction + +Welcome to the Wails Enhancement Proposal (WEP) process. This guide outlines the steps for proposing, discussing, and implementing feature enhancements in Wails. The process is divided into two main parts: + +1. **Submission of Proposal**: This part involves documenting your idea, submitting it for review, and discussing it with the community to gather feedback and refine the proposal. +2. **Implementation of Proposal**: Once a proposal is accepted, the implementation phase begins. This involves developing the feature, submitting a PR for the implementation, and iterating based on feedback until the feature is merged and documented. + +Following this structured approach ensures transparency, community involvement, and efficient enhancement of the Wails project. + +**NOTE**: This process is for proposing new functionality. For bug fixes, documentation improvements, and other minor changes, please follow the standard PR process. + +## Submission of Proposal + +### 1. Idea Initiation + +- **Document Your Idea**: + - Create a new directory: `v3/wep/proposals/` with the name of your proposal. + - Copy the WEP template located in `v3/wep/WEP_TEMPLATE.md` into `v3/wep/proposals//proposal.md`. + - Include any additional resources (images, diagrams, etc.) in the proposal directory. + - Fill in the template with the details of your proposal. Do not remove any sections. + +### 2. Submit Proposal + +- **Open a DRAFT PR**: + - Submit a DRAFT Pull Request (PR) for the proposal with the title `[WEP] `. + - It should only contain the proposal file and any additional resources (images, diagrams, etc.). + - Add a summary of the proposal in the PR description. + +### 3. Community Discussion + +- **Share Your Proposal**: Present your proposal to the Wails community. Try to get support for the proposal to increase the chances of acceptance. If you are on the discord server, create a post in the [`#enhancement-proposals`](https://discord.gg/TA8kbQds95) channel. +- **Gather Feedback**: Refine your proposal based on community input. All feedback should be added as comments in the PR. +- **Show Support**: Agreement with the proposal should be indicated by adding a thumbs-up emoji to the PR. The more thumbs-up emojis, the more likely the proposal will be accepted. +- **Iterate**: Make changes to the proposal based on feedback. +- **Agree on an Implementor**: To avoid stagnant proposals, we require someone agree to implement it. This could be the proposer. +- **Ready for Review**: Once the proposal is ready for review, change the PR status to `Ready for Review`. + +A minimum of 2 weeks should be given for community feedback and discussion. + +### 4. Final Decision + +- **Decision**: The Wails maintainers will make a final decision on the proposal based on community feedback and the proposal's merits. + - If accepted, the proposal will be assigned a WEP number and the PR merged. + - If rejected, the reasons will be provided in the PR comments. + +*NOTE*: If a proposal has not met the required support or has been inactive for more than a month, it may be closed. + +## Implementation of Proposal + +Once a proposal has been accepted and an implementation plan has been decided, the focus shifts to bringing the feature to life. This phase encompasses the actual development, review, and integration of the new feature. Here are the steps involved in the implementation process: + +### 1. Develop the Feature + +- **Follow Standards**: Implement the feature following Wails coding standards. +- **Document the Feature**: Ensure the feature is well-documented during the development process. +- **Submit a PR**: Once implemented, submit a PR for the feature. + +### 2. Feedback and Iteration + +- **Gather Feedback**: Collect feedback from the community. +- **Iterate**: Make improvements based on feedback. + +### 3. Merging + +- **Review of PR**: Address any review comments. +- **Merge**: The PR will be merged after satisfactory review. +The WEP process ensures structured and collaborative enhancement of Wails. Adhere to this guide to contribute effectively to the project's growth. \ No newline at end of file diff --git a/v3/wep/WEP_TEMPLATE.md b/v3/wep/WEP_TEMPLATE.md new file mode 100644 index 000000000..c822c2361 --- /dev/null +++ b/v3/wep/WEP_TEMPLATE.md @@ -0,0 +1,50 @@ +# Wails Enhancement Proposal (WEP) + +## Title + +**Author**: [Your Name] +**Created**: [YYYY-MM-DD] + +## Summary + +Provide a concise summary of the proposal. + +## Motivation + +Explain the problem this proposal aims to solve and why it is necessary. + +## Detailed Design + +Provide a detailed description of the proposed feature, including: + +- Technical details +- Implementation steps +- Potential impact on existing functionality + +## Pros/Cons + +List the pros and cons of the proposed solution. + +## Alternatives Considered + +Discuss alternative solutions or approaches that were considered. + +## Backwards Compatibility + +Address any potential backward compatibility issues. + +## Test Plan + +Outline a testing strategy to ensure the feature works as expected. + +## Reference Implementation + +If applicable, include a link to a reference implementation or prototype. + +## Maintenance Plan + +Describe how the feature will be maintained and supported in the long term. + +## Conclusion + +Summarize the benefits of the proposal and any final considerations. diff --git a/v3/wep/proposals/.gitkeep b/v3/wep/proposals/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/v3/wep/proposals/titlebar-buttons/proposal.md b/v3/wep/proposals/titlebar-buttons/proposal.md new file mode 100644 index 000000000..8bd6b8c4f --- /dev/null +++ b/v3/wep/proposals/titlebar-buttons/proposal.md @@ -0,0 +1,137 @@ +# Wails Enhancement Proposal (WEP) + +## Customising Window Controls in Wails + +**Author**: Lea Anthony +**Created**: 2024-05-20 + +## Summary + +This is a proposal for an API to control the appearance and functionality of window controls in Wails. +This will only be available on Windows and macOS. + +## Motivation + +We currently do not fully support the ability to customise window controls. + +## Detailed Design + +### Controlling Button State + +1. A new enum will be added: + +```go + type ButtonState int + + const ( + ButtonEnabled ButtonState = 0 + ButtonDisabled ButtonState = 1 + ButtonHidden ButtonState = 2 + ) +``` + +2. These options will be added to the `WebviewWindowOptions` option struct: + +```go + MinimiseButtonState ButtonState + MaximiseButtonState ButtonState + CloseButtonState ButtonState +``` + +3. These options will be removed from the current Windows/Mac options: + +- DisableMinimiseButton +- DisableMaximiseButton +- DisableCloseButton + +4. These methods will be added to the `Window` interface: + +```go + SetMinimizeButtonState(state ButtonState) + SetMaximizeButtonState(state ButtonState) + SetCloseButtonState(state ButtonState) +``` + +The settings translate to the following functionality on each platform: + +| | Windows | Mac | +|-----------------------|------------------------|------------------------| +| Disable Min/Max/Close | Disables Min/Max/Close | Disables Min/Max/Close | +| Hide Min | Disables Min | Hides Min button | +| Hide Max | Disables Max | Hides Max button | +| Hide Close | Hides all controls | Hides Close | + +Note: On Windows, it is not possible to hide the Min/Max buttons individually. +However, disabling both will hide both of the controls and only show the +close button. + +### Controlling Window Style (Windows) + +As Windows currently does not have much in the way of controlling the style of the +titlebar, a new option will be added to the `WebviewWindowOptions` option struct: + +```go + ExStyle int +``` + +If this is set, then the new Window will use the style specified in the `ExStyle` field. + +Example: +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/w32" +) + +func main() { + app := application.New(application.Options{ + Name: "My Application", + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Windows: application.WindowsWindow{ + ExStyle: w32.WS_EX_TOOLWINDOW | w32.WS_EX_NOREDIRECTIONBITMAP | w32.WS_EX_TOPMOST, + }, + }) + + app.Run() +} +``` + +## Pros/Cons + +### Pros + +- We bring much needed customisation capabilities to both macOS and Windows + +### Cons + +- Windows works slightly different to macOS +- No Linux support (doesn't look like it's possible regardless of the solution) + +## Alternatives Considered + +The alternative is to draw your own titlebar, but this is a lot of work and often doesn't look good. + +## Backwards Compatibility + +This is not backwards compatible as we remove the old "disable button" options. + +## Test Plan + +As part of the implementation, the window example will be updated to test the functionality. + +## Reference Implementation + +There is a reference implementation as part of this proposal. + +## Maintenance Plan + +This feature will be maintained and supported by the Wails developers. + +## Conclusion + +This API would be a leap forward in giving developers greater control over their application window appearances. +