mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-15 07:05:50 +01:00
Merge branch 'v3-alpha' into v3-alpha-proposals/mobile
This commit is contained in:
commit
fc968cbebc
1323 changed files with 63913 additions and 18526 deletions
44
.github/file-labeler.yml
vendored
Normal file
44
.github/file-labeler.yml
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# File path specific labels
|
||||
v2-only:
|
||||
- 'v2/**/*'
|
||||
|
||||
v3-alpha:
|
||||
- 'v3/**/*'
|
||||
|
||||
windows:
|
||||
- '**/*_windows.go'
|
||||
- 'v2/internal/frontend/desktop/windows/**/*'
|
||||
|
||||
macos:
|
||||
- '**/*_darwin.go'
|
||||
- 'v2/internal/frontend/desktop/darwin/**/*'
|
||||
|
||||
linux:
|
||||
- '**/*_linux.go'
|
||||
- 'v2/internal/frontend/desktop/linux/**/*'
|
||||
|
||||
cli:
|
||||
- 'v2/cmd/**/*'
|
||||
- 'v3/cmd/**/*'
|
||||
- '**/cli/**/*'
|
||||
- '**/commands/**/*'
|
||||
|
||||
documentation:
|
||||
- '**/*.md'
|
||||
- 'docs/**/*'
|
||||
- 'website/**/*'
|
||||
- 'mkdocs-website/**/*'
|
||||
|
||||
templates:
|
||||
- '**/templates/**/*'
|
||||
- '**/template/**/*'
|
||||
|
||||
runtime:
|
||||
- '**/runtime/**/*'
|
||||
- 'v2/internal/runtime/**/*'
|
||||
- 'v3/internal/runtime/**/*'
|
||||
|
||||
bindings:
|
||||
- 'v2/internal/binding/**/*'
|
||||
- 'v3/internal/generator/**/*'
|
||||
|
||||
144
.github/issue-labeler.yml
vendored
Normal file
144
.github/issue-labeler.yml
vendored
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
# Version labels
|
||||
v2-only:
|
||||
- '\[v2\]'
|
||||
- '\(v2\)'
|
||||
- 'v2:'
|
||||
- 'version 2'
|
||||
- 'wails v2'
|
||||
- 'using v2'
|
||||
- 'master branch'
|
||||
|
||||
v3-alpha:
|
||||
- '\[v3\]'
|
||||
- '\(v3\)'
|
||||
- 'v3:'
|
||||
- '\[v3-alpha\]'
|
||||
- '\(v3-alpha\)'
|
||||
- 'version 3'
|
||||
- 'wails v3'
|
||||
- 'using v3'
|
||||
- 'v3-alpha branch'
|
||||
|
||||
# Component labels
|
||||
webview2:
|
||||
- 'webview2'
|
||||
- 'windows'
|
||||
- 'microsoft edge'
|
||||
- 'edge browser'
|
||||
- 'IE'
|
||||
- 'Explorer'
|
||||
- 'browser crashes'
|
||||
|
||||
macos:
|
||||
- 'macOS'
|
||||
- 'mac OS'
|
||||
- 'OS X'
|
||||
- 'darwin'
|
||||
- 'cocoa'
|
||||
- 'Safari'
|
||||
- 'Catalyst'
|
||||
- 'Ventura'
|
||||
- 'Sonoma'
|
||||
- 'apple'
|
||||
|
||||
linux:
|
||||
- 'linux'
|
||||
- 'ubuntu'
|
||||
- 'debian'
|
||||
- 'fedora'
|
||||
- 'gtk'
|
||||
- 'webkitgtk'
|
||||
- 'webkit2gtk'
|
||||
- 'gnome'
|
||||
- 'x11'
|
||||
- 'wayland'
|
||||
|
||||
cli:
|
||||
- 'cli'
|
||||
- 'command line'
|
||||
- 'wails doctor'
|
||||
- 'wails init'
|
||||
- 'wails build'
|
||||
- 'wails dev'
|
||||
- 'template'
|
||||
- 'scaffolding'
|
||||
|
||||
# Type labels
|
||||
bug:
|
||||
- 'bug'
|
||||
- 'crash'
|
||||
- 'broken'
|
||||
- 'failure'
|
||||
- 'error'
|
||||
- 'failed'
|
||||
- 'panic'
|
||||
- 'segfault'
|
||||
- 'issue'
|
||||
- 'not working'
|
||||
- 'problem'
|
||||
|
||||
enhancement:
|
||||
- 'feature'
|
||||
- 'enhancement'
|
||||
- 'request'
|
||||
- 'add'
|
||||
- 'new'
|
||||
- 'improve'
|
||||
- 'functionality'
|
||||
- 'support for'
|
||||
- 'please add'
|
||||
- 'would be nice'
|
||||
|
||||
documentation:
|
||||
- 'docs'
|
||||
- 'documentation'
|
||||
- 'readme'
|
||||
- 'example'
|
||||
- 'tutorial'
|
||||
- 'guide'
|
||||
- 'explanation'
|
||||
- 'clarification'
|
||||
- 'instructions'
|
||||
|
||||
security:
|
||||
- 'security'
|
||||
- 'vulnerability'
|
||||
- 'exploit'
|
||||
- 'hack'
|
||||
- 'CVE'
|
||||
- 'secure'
|
||||
- 'encryption'
|
||||
- 'hardening'
|
||||
|
||||
performance:
|
||||
- 'performance'
|
||||
- 'slow'
|
||||
- 'speed'
|
||||
- 'memory leak'
|
||||
- 'cpu usage'
|
||||
- 'high memory'
|
||||
- 'lag'
|
||||
- 'freeze'
|
||||
- 'optimization'
|
||||
|
||||
# Priority labels
|
||||
high-priority:
|
||||
- 'urgent'
|
||||
- 'critical'
|
||||
- 'security'
|
||||
- 'high priority'
|
||||
- 'important'
|
||||
- 'production'
|
||||
- 'blocker'
|
||||
- 'blocking'
|
||||
|
||||
question:
|
||||
- 'how to'
|
||||
- 'how do i'
|
||||
- 'can I'
|
||||
- 'is it possible'
|
||||
- 'question'
|
||||
- 'help me'
|
||||
- 'need help'
|
||||
- 'assistance'
|
||||
- 'confused'
|
||||
3
.github/pull_request_template.md
vendored
3
.github/pull_request_template.md
vendored
|
|
@ -6,6 +6,7 @@
|
|||
* YOUR PR MAY BE REJECTED IF IT DOES NOT FOLLOW THESE STEPS *
|
||||
*********************************************************************
|
||||
|
||||
- *DO NOT* submit bugs for a source install of v3, ONLY tagged versions, e.g. v3.0.0-alpha.11
|
||||
- *DO NOT* submit PRs for v3 alpha enhancements, unless you have opened a post on the discord channel.
|
||||
All enhancements must be discussed first.
|
||||
The feedback guide for v3 is here: https://v3alpha.wails.io/getting-started/feedback/
|
||||
|
|
@ -47,7 +48,7 @@ Please paste the output of `wails doctor`. If you are unable to run this command
|
|||
|
||||
# Checklist:
|
||||
|
||||
- [ ] I have updated `website/src/pages/changelog.mdx` with details of this PR
|
||||
- [ ] I have updated `v3/UNRELEASED_CHANGELOG.md` with details of this PR
|
||||
- [ ] My code follows the general coding style of this project
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
|
|
|
|||
26
.github/stale.yml
vendored
26
.github/stale.yml
vendored
|
|
@ -1,7 +1,7 @@
|
|||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 30
|
||||
daysUntilStale: 45
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
daysUntilClose: 10
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
|
|
@ -9,14 +9,28 @@ exemptLabels:
|
|||
- onhold
|
||||
- inprogress
|
||||
- "Selected For Development"
|
||||
- bug
|
||||
- enhancement
|
||||
- v3-alpha
|
||||
- high-priority
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: "Wont Fix"
|
||||
staleLabel: "stale"
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
recent activity. It will be closed if no further activity occurs within the next 10 days.
|
||||
|
||||
If this issue is still relevant, please add a comment to keep it open.
|
||||
Thank you for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
closeComment: >
|
||||
This issue has been automatically closed due to lack of activity.
|
||||
Please feel free to reopen it if it's still relevant.
|
||||
exemptMilestones: true
|
||||
exemptAssignees: true
|
||||
# Only mark issues (not PRs)
|
||||
only: issues
|
||||
# Exempt issues created before a certain date
|
||||
exemptCreatedBefore: "2024-01-01T00:00:00Z"
|
||||
# Starts checking issues only after the specified date
|
||||
startDate: "2025-06-01T00:00:00Z"
|
||||
|
|
|
|||
33
.github/workflows/auto-label-issues.yml
vendored
Normal file
33
.github/workflows/auto-label-issues.yml
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
name: Auto Label Issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited, reopened]
|
||||
pull_request_target:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
auto-label:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Label issues and PRs by content
|
||||
uses: github/issue-labeler@v3.4
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
configuration-path: .github/issue-labeler.yml
|
||||
enable-versioned-regex: 0
|
||||
include-title: 1
|
||||
|
||||
- name: Label issues and PRs by file paths
|
||||
uses: actions/labeler@v4
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
configuration-path: .github/file-labeler.yml
|
||||
sync-labels: true
|
||||
372
.github/workflows/automated-releases.yml
vendored
Normal file
372
.github/workflows/automated-releases.yml
vendored
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
name: Automated Nightly Releases
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
force_release:
|
||||
description: 'Force release even if no changes detected'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
dry_run:
|
||||
description: 'Run in dry-run mode (no actual releases)'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
# schedule:
|
||||
# Run at 2 AM UTC every day - DISABLED for safety until ready
|
||||
# - cron: '0 2 * * *'
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.24'
|
||||
|
||||
jobs:
|
||||
check-permissions:
|
||||
name: Check Release Permissions
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
authorized: ${{ steps.check.outputs.authorized }}
|
||||
steps:
|
||||
- name: Check if user is authorized for releases
|
||||
id: check
|
||||
run: |
|
||||
# Only allow specific users to trigger releases
|
||||
AUTHORIZED_USERS="leaanthony"
|
||||
|
||||
if [[ "$AUTHORIZED_USERS" == *"${{ github.actor }}"* ]]; then
|
||||
echo "✅ User ${{ github.actor }} is authorized for releases"
|
||||
echo "authorized=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "❌ User ${{ github.actor }} is not authorized for releases"
|
||||
echo "authorized=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
detect-v2-changes:
|
||||
name: Detect v2 Changes
|
||||
runs-on: ubuntu-latest
|
||||
needs: check-permissions
|
||||
if: needs.check-permissions.outputs.authorized == 'true'
|
||||
outputs:
|
||||
has_changes: ${{ steps.changes.outputs.has_changes }}
|
||||
commits_since_last: ${{ steps.changes.outputs.commits_since_last }}
|
||||
last_release_tag: ${{ steps.changes.outputs.last_release_tag }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check for v2 changes since last release
|
||||
id: changes
|
||||
run: |
|
||||
echo "🔍 Checking for v2 changes since last release..."
|
||||
|
||||
# Find the last v2 release tag
|
||||
LAST_TAG=$(git tag -l "v2.*" --sort=-version:refname | head -n 1)
|
||||
if [ -z "$LAST_TAG" ]; then
|
||||
echo "No previous v2 tags found, assuming first release"
|
||||
LAST_TAG=$(git rev-list --max-parents=0 HEAD)
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "commits_since_last=999" >> $GITHUB_OUTPUT
|
||||
echo "last_release_tag=none" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Last v2 release tag: $LAST_TAG"
|
||||
echo "last_release_tag=$LAST_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
# Count commits since last release affecting v2 or root files
|
||||
COMMITS_COUNT=$(git rev-list --count ${LAST_TAG}..HEAD -- v2/ website/ README.md CHANGELOG.md || echo "0")
|
||||
echo "Commits since last v2 release: $COMMITS_COUNT"
|
||||
echo "commits_since_last=$COMMITS_COUNT" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ "$COMMITS_COUNT" -gt 0 ] || [ "${{ github.event.inputs.force_release }}" == "true" ]; then
|
||||
echo "✅ Changes detected or forced release"
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "ℹ️ No changes detected since last release"
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
fi
|
||||
|
||||
detect-v3-changes:
|
||||
name: Detect v3-alpha Changes
|
||||
runs-on: ubuntu-latest
|
||||
needs: check-permissions
|
||||
if: needs.check-permissions.outputs.authorized == 'true'
|
||||
outputs:
|
||||
has_changes: ${{ steps.changes.outputs.has_changes }}
|
||||
commits_since_last: ${{ steps.changes.outputs.commits_since_last }}
|
||||
last_release_tag: ${{ steps.changes.outputs.last_release_tag }}
|
||||
steps:
|
||||
- name: Checkout v3-alpha branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: v3-alpha
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check for v3-alpha changes since last release
|
||||
id: changes
|
||||
run: |
|
||||
echo "🔍 Checking for v3-alpha changes since last release..."
|
||||
|
||||
# Find the last v3 alpha release tag
|
||||
LAST_TAG=$(git tag -l "v3.*-alpha.*" --sort=-version:refname | head -n 1)
|
||||
if [ -z "$LAST_TAG" ]; then
|
||||
echo "No previous v3-alpha tags found, assuming first release"
|
||||
LAST_TAG=$(git rev-list --max-parents=0 HEAD)
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "commits_since_last=999" >> $GITHUB_OUTPUT
|
||||
echo "last_release_tag=none" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Last v3-alpha release tag: $LAST_TAG"
|
||||
echo "last_release_tag=$LAST_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
# Count commits since last release affecting v3 or docs
|
||||
COMMITS_COUNT=$(git rev-list --count ${LAST_TAG}..HEAD -- v3/ docs/ || echo "0")
|
||||
echo "Commits since last v3-alpha release: $COMMITS_COUNT"
|
||||
echo "commits_since_last=$COMMITS_COUNT" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ "$COMMITS_COUNT" -gt 0 ] || [ "${{ github.event.inputs.force_release }}" == "true" ]; then
|
||||
echo "✅ Changes detected or forced release"
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "ℹ️ No changes detected since last release"
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
fi
|
||||
|
||||
release-v2:
|
||||
name: Create v2 Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-permissions, detect-v2-changes]
|
||||
if: |
|
||||
needs.check-permissions.outputs.authorized == 'true' &&
|
||||
needs.detect-v2-changes.outputs.has_changes == 'true'
|
||||
outputs:
|
||||
version: ${{ steps.release.outputs.version }}
|
||||
release_notes: ${{ steps.release.outputs.release_notes }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Run v2 release script and extract notes
|
||||
id: release
|
||||
run: |
|
||||
echo "🚀 Running v2 release script..."
|
||||
cd v2/tools/release
|
||||
|
||||
# Run release script and capture output
|
||||
RELEASE_OUTPUT=$(go run release.go 2>&1)
|
||||
echo "$RELEASE_OUTPUT"
|
||||
|
||||
# Extract version from output or version file
|
||||
NEW_VERSION=$(cat ../../cmd/wails/internal/version.txt)
|
||||
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
# Extract release notes from delimited output
|
||||
RELEASE_NOTES=$(echo "$RELEASE_OUTPUT" | sed -n '/=== RELEASE NOTES FOR/,/=== END RELEASE NOTES ===/p' | sed '1d;$d')
|
||||
|
||||
# Save release notes to file for multiline output
|
||||
echo "$RELEASE_NOTES" > ../../../release_notes_v2.md
|
||||
|
||||
# Set output (escape for GitHub Actions)
|
||||
{
|
||||
echo "release_notes<<EOF"
|
||||
echo "$RELEASE_NOTES"
|
||||
echo "EOF"
|
||||
} >> $GITHUB_OUTPUT
|
||||
|
||||
echo "✅ v2 release script completed - version: $NEW_VERSION"
|
||||
|
||||
- name: Create v2 git tag and release
|
||||
if: github.event.inputs.dry_run != 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
VERSION="${{ steps.release.outputs.version }}"
|
||||
echo "📝 Creating v2 release: $VERSION"
|
||||
|
||||
# Configure git
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Commit the changelog changes
|
||||
git add website/src/pages/changelog.mdx v2/cmd/wails/internal/version.txt
|
||||
git commit -m "chore: release $VERSION
|
||||
|
||||
Automated release created by GitHub Actions
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||
|
||||
# Create and push tag
|
||||
git tag -a "$VERSION" -m "Release $VERSION"
|
||||
git push origin master
|
||||
git push origin "$VERSION"
|
||||
|
||||
# Create GitHub release with notes
|
||||
gh release create "$VERSION" \
|
||||
--title "Release $VERSION" \
|
||||
--notes-file release_notes_v2.md \
|
||||
--target master
|
||||
|
||||
- name: Log dry-run results for v2
|
||||
if: github.event.inputs.dry_run == 'true'
|
||||
run: |
|
||||
echo "🧪 DRY RUN - Would have created v2 release:"
|
||||
echo "Version: ${{ steps.release.outputs.version }}"
|
||||
echo "Release Notes:"
|
||||
cat release_notes_v2.md
|
||||
|
||||
release-v3:
|
||||
name: Create v3-alpha Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-permissions, detect-v3-changes]
|
||||
if: |
|
||||
needs.check-permissions.outputs.authorized == 'true' &&
|
||||
needs.detect-v3-changes.outputs.has_changes == 'true'
|
||||
outputs:
|
||||
version: ${{ steps.release.outputs.version }}
|
||||
release_notes: ${{ steps.release.outputs.release_notes }}
|
||||
steps:
|
||||
- name: Checkout v3-alpha branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: v3-alpha
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Run v3 release script and extract notes
|
||||
id: release
|
||||
run: |
|
||||
echo "🚀 Running v3-alpha release script..."
|
||||
cd v3/tasks/release
|
||||
|
||||
# Run release script and capture output
|
||||
RELEASE_OUTPUT=$(go run release.go 2>&1)
|
||||
echo "$RELEASE_OUTPUT"
|
||||
|
||||
# Extract version from output or version file
|
||||
NEW_VERSION=$(cat ../../internal/version/version.txt)
|
||||
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
# Extract release notes from delimited output
|
||||
RELEASE_NOTES=$(echo "$RELEASE_OUTPUT" | sed -n '/=== RELEASE NOTES FOR/,/=== END RELEASE NOTES ===/p' | sed '1d;$d')
|
||||
|
||||
# Save release notes to file for multiline output
|
||||
echo "$RELEASE_NOTES" > ../../../release_notes_v3.md
|
||||
|
||||
# Set output (escape for GitHub Actions)
|
||||
{
|
||||
echo "release_notes<<EOF"
|
||||
echo "$RELEASE_NOTES"
|
||||
echo "EOF"
|
||||
} >> $GITHUB_OUTPUT
|
||||
|
||||
echo "✅ v3-alpha release script completed - version: $NEW_VERSION"
|
||||
|
||||
- name: Create v3-alpha git tag and release
|
||||
if: github.event.inputs.dry_run != 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
VERSION="${{ steps.release.outputs.version }}"
|
||||
echo "📝 Creating v3-alpha release: $VERSION"
|
||||
|
||||
# Configure git
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Commit the changelog changes
|
||||
git add docs/src/content/docs/changelog.mdx v3/internal/version/version.txt
|
||||
git commit -m "chore: release $VERSION
|
||||
|
||||
Automated v3-alpha release created by GitHub Actions
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||
|
||||
# Create and push tag
|
||||
git tag -a "$VERSION" -m "Release $VERSION"
|
||||
git push origin v3-alpha
|
||||
git push origin "$VERSION"
|
||||
|
||||
# Create GitHub release with notes
|
||||
gh release create "$VERSION" \
|
||||
--title "Release $VERSION" \
|
||||
--notes-file release_notes_v3.md \
|
||||
--target v3-alpha \
|
||||
--prerelease
|
||||
|
||||
- name: Log dry-run results for v3-alpha
|
||||
if: github.event.inputs.dry_run == 'true'
|
||||
run: |
|
||||
echo "🧪 DRY RUN - Would have created v3-alpha release:"
|
||||
echo "Version: ${{ steps.release.outputs.version }}"
|
||||
echo "Release Notes:"
|
||||
cat release_notes_v3.md
|
||||
|
||||
summary:
|
||||
name: Release Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-permissions, detect-v2-changes, detect-v3-changes, release-v2, release-v3]
|
||||
if: always() && needs.check-permissions.outputs.authorized == 'true'
|
||||
steps:
|
||||
- name: Create release summary
|
||||
run: |
|
||||
echo "# 🚀 Automated Release Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Repository**: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Triggered by**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Dry Run Mode**: ${{ github.event.inputs.dry_run || 'false' }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# v2 Summary
|
||||
echo "## v2 Release" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "${{ needs.detect-v2-changes.outputs.has_changes }}" == "true" ]; then
|
||||
if [ "${{ needs.release-v2.result }}" == "success" ]; then
|
||||
echo "✅ **v2 Release**: Created successfully" >> $GITHUB_STEP_SUMMARY
|
||||
echo " - Version: ${{ needs.release-v2.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo " - Commits since last: ${{ needs.detect-v2-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **v2 Release**: Failed" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
else
|
||||
echo "⏭️ **v2 Release**: Skipped (no changes)" >> $GITHUB_STEP_SUMMARY
|
||||
echo " - Commits since last: ${{ needs.detect-v2-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# v3 Summary
|
||||
echo "## v3-alpha Release" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "${{ needs.detect-v3-changes.outputs.has_changes }}" == "true" ]; then
|
||||
if [ "${{ needs.release-v3.result }}" == "success" ]; then
|
||||
echo "✅ **v3-alpha Release**: Created successfully" >> $GITHUB_STEP_SUMMARY
|
||||
echo " - Version: ${{ needs.release-v3.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo " - Commits since last: ${{ needs.detect-v3-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **v3-alpha Release**: Failed" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
else
|
||||
echo "⏭️ **v3-alpha Release**: Skipped (no changes)" >> $GITHUB_STEP_SUMMARY
|
||||
echo " - Commits since last: ${{ needs.detect-v3-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||
echo "🤖 **Automated Release System** | Generated with [Claude Code](https://claude.ai/code)" >> $GITHUB_STEP_SUMMARY
|
||||
269
.github/workflows/build-and-test-v3.yml
vendored
269
.github/workflows/build-and-test-v3.yml
vendored
|
|
@ -3,13 +3,20 @@ name: Build + Test v3
|
|||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- v3-alpha
|
||||
paths:
|
||||
- 'v3/**'
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
branches:
|
||||
- v3-alpha
|
||||
|
||||
jobs:
|
||||
check_approval:
|
||||
name: Check PR Approval
|
||||
runs-on: ubuntu-latest
|
||||
if: github.base_ref == 'v3-alpha'
|
||||
outputs:
|
||||
approved: ${{ steps.check.outputs.approved }}
|
||||
steps:
|
||||
|
|
@ -22,59 +29,11 @@ jobs:
|
|||
echo "approved=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
test_go:
|
||||
name: Run Go Tests
|
||||
needs: check_approval
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest, ubuntu-latest]
|
||||
go-version: [1.23]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install linux dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
with:
|
||||
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config
|
||||
version: 1.0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
cache-dependency-path: "v3/go.sum"
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v2
|
||||
with:
|
||||
version: 3.x
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Examples
|
||||
working-directory: ./v3
|
||||
run: task test:examples
|
||||
|
||||
- name: Run tests (mac)
|
||||
if: matrix.os == 'macos-latest'
|
||||
env:
|
||||
CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13
|
||||
working-directory: ./v3
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Run tests (!mac)
|
||||
if: matrix.os != 'macos-latest'
|
||||
working-directory: ./v3
|
||||
run: go test -v ./...
|
||||
|
||||
test_js:
|
||||
name: Run JS Tests
|
||||
needs: check_approval
|
||||
runs-on: ubuntu-latest
|
||||
if: github.base_ref == 'v3-alpha'
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
|
|
@ -88,57 +47,158 @@ jobs:
|
|||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v2
|
||||
with:
|
||||
version: 3.x
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
working-directory: v2/internal/frontend/runtime
|
||||
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
|
||||
run: |
|
||||
npm ci
|
||||
npx --yes esbuild@latest --version
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
working-directory: v2/internal/frontend/runtime
|
||||
- name: Clean build artifacts
|
||||
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
|
||||
run: npm run clean
|
||||
|
||||
test_templates:
|
||||
name: Test Templates
|
||||
needs: test_go
|
||||
- name: Type-check runtime
|
||||
working-directory: v3
|
||||
run: task runtime:check
|
||||
|
||||
- name: Test runtime
|
||||
working-directory: v3
|
||||
run: task runtime:test
|
||||
|
||||
- name: Check that the bundled runtime builds
|
||||
working-directory: v3
|
||||
run: task runtime:build
|
||||
|
||||
- name: Check that the npm package builds
|
||||
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
|
||||
run: npm run build
|
||||
|
||||
- name: Store runtime build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: runtime-build-artifacts
|
||||
path: |
|
||||
v3/internal/runtime/desktop/@wailsio/runtime/dist/
|
||||
v3/internal/runtime/desktop/@wailsio/runtime/types/
|
||||
v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.tsbuildinfo
|
||||
|
||||
test_go:
|
||||
name: Run Go Tests v3
|
||||
needs: [check_approval, test_js]
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: github.base_ref == 'v3-alpha'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
template:
|
||||
[
|
||||
svelte,
|
||||
svelte-ts,
|
||||
vue,
|
||||
vue-ts,
|
||||
react,
|
||||
react-ts,
|
||||
preact,
|
||||
preact-ts,
|
||||
lit,
|
||||
lit-ts,
|
||||
vanilla,
|
||||
vanilla-ts,
|
||||
]
|
||||
go-version: [1.23]
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
go-version: [1.24]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install linux dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
with:
|
||||
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config xvfb x11-xserver-utils at-spi2-core xdg-desktop-portal-gtk
|
||||
version: 1.0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
cache: true
|
||||
cache-dependency-path: "v3/go.sum"
|
||||
|
||||
- name: Setup Golang caches
|
||||
uses: actions/cache@v4
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-golang-
|
||||
version: 3.x
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Retrieve runtime build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runtime-build-artifacts
|
||||
path: v3/internal/runtime/desktop/@wailsio/runtime/
|
||||
|
||||
- name: Build Examples
|
||||
working-directory: v3
|
||||
run: |
|
||||
echo "Starting example compilation tests..."
|
||||
task test:examples
|
||||
echo "Example compilation tests completed successfully"
|
||||
|
||||
- name: Run tests (mac)
|
||||
if: matrix.os == 'macos-latest'
|
||||
env:
|
||||
CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13
|
||||
working-directory: v3
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Run tests (windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
working-directory: v3
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Run tests (ubuntu)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
working-directory: v3
|
||||
run: >
|
||||
xvfb-run --auto-servernum
|
||||
sh -c '
|
||||
dbus-update-activation-environment --systemd --all &&
|
||||
go test -v ./...
|
||||
'
|
||||
|
||||
- name: Typecheck binding generator output
|
||||
working-directory: v3
|
||||
run: task generator:test:check
|
||||
|
||||
cleanup:
|
||||
name: Cleanup build artifacts
|
||||
if: always()
|
||||
needs: [test_js, test_go]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: geekyeggo/delete-artifact@v5
|
||||
with:
|
||||
name: runtime-build-artifacts
|
||||
failOnError: false
|
||||
|
||||
test_templates:
|
||||
name: Test Templates
|
||||
needs: test_go
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: github.base_ref == 'v3-alpha'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
template:
|
||||
- svelte
|
||||
- svelte-ts
|
||||
- vue
|
||||
- vue-ts
|
||||
- react
|
||||
- react-ts
|
||||
- preact
|
||||
- preact-ts
|
||||
- lit
|
||||
- lit-ts
|
||||
- vanilla
|
||||
- vanilla-ts
|
||||
go-version: [1.24]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install linux dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
|
|
@ -147,17 +207,50 @@ jobs:
|
|||
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config
|
||||
version: 1.0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
cache: true
|
||||
cache-dependency-path: "v3/go.sum"
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v2
|
||||
with:
|
||||
version: 3.x
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Wails3 CLI
|
||||
working-directory: v3
|
||||
run: |
|
||||
cd ./v3/cmd/wails3
|
||||
go install
|
||||
wails3 -help
|
||||
task install
|
||||
wails3 doctor
|
||||
|
||||
- name: Generate template '${{ matrix.template }}'
|
||||
run: |
|
||||
go install github.com/go-task/task/v3/cmd/task@latest
|
||||
mkdir -p ./test-${{ matrix.template }}
|
||||
cd ./test-${{ matrix.template }}
|
||||
wails3 init -n ${{ matrix.template }} -t ${{ matrix.template }}
|
||||
cd ${{ matrix.template }}
|
||||
wails3 build
|
||||
wails3 build
|
||||
|
||||
build_results:
|
||||
if: ${{ always() }}
|
||||
runs-on: ubuntu-latest
|
||||
name: v3 Build Results
|
||||
needs: [test_go, test_js, test_templates]
|
||||
steps:
|
||||
- run: |
|
||||
go_result="${{ needs.test_go.result }}"
|
||||
js_result="${{ needs.test_js.result }}"
|
||||
templates_result="${{ needs.test_templates.result }}"
|
||||
|
||||
if [[ $go_result == "success" || $go_result == "skipped" ]] && \
|
||||
[[ $js_result == "success" || $js_result == "skipped" ]] && \
|
||||
[[ $templates_result == "success" || $templates_result == "skipped" ]]; then
|
||||
echo "All required jobs succeeded or were skipped"
|
||||
exit 0
|
||||
else
|
||||
echo "One or more required jobs failed"
|
||||
exit 1
|
||||
fi
|
||||
74
.github/workflows/changelog-validation-v3.yml
vendored
Normal file
74
.github/workflows/changelog-validation-v3.yml
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
name: Changelog Validation (v3)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ v3-alpha ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'PR number to validate (for manual testing)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
validate-changelog:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request' || github.event.inputs.pr_number
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || format('refs/pull/{0}/head', github.event.inputs.pr_number) }}
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.23'
|
||||
|
||||
- name: Get PR information
|
||||
id: pr_info
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
echo "pr_number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
|
||||
echo "base_ref=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "pr_number=${{ github.event.inputs.pr_number }}" >> $GITHUB_OUTPUT
|
||||
echo "base_ref=v3-alpha" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Check if changelog was modified
|
||||
id: changelog_check
|
||||
run: |
|
||||
git fetch origin ${{ steps.pr_info.outputs.base_ref }}
|
||||
if git diff --name-only origin/${{ steps.pr_info.outputs.base_ref }}..HEAD | grep -q "v3/UNRELEASED_CHANGELOG.md"; then
|
||||
echo "changelog_modified=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ UNRELEASED_CHANGELOG.md was modified in this PR"
|
||||
else
|
||||
echo "changelog_modified=false" >> $GITHUB_OUTPUT
|
||||
echo "⚠️ UNRELEASED_CHANGELOG.md was not modified"
|
||||
fi
|
||||
|
||||
- name: Comment on PR about missing changelog
|
||||
if: steps.changelog_check.outputs.changelog_modified == 'false' && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const author = context.payload.pull_request.user.login;
|
||||
const message = '## ⚠️ Missing Changelog Update\n\n' +
|
||||
`Hi @${author}, please update \`v3/UNRELEASED_CHANGELOG.md\` with a description of your changes.\n\n` +
|
||||
'This helps us keep track of changes for the next release.';
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: ${{ steps.pr_info.outputs.pr_number }},
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: message
|
||||
});
|
||||
|
||||
9
.github/workflows/generate-sponsor-image.yml
vendored
9
.github/workflows/generate-sponsor-image.yml
vendored
|
|
@ -29,7 +29,12 @@ jobs:
|
|||
with:
|
||||
commit-message: "chore: update sponsors.svg"
|
||||
add-paths: "website/static/img/sponsors.svg"
|
||||
title: Update Sponsor Image
|
||||
body: Generated new image
|
||||
title: "chore: update sponsors.svg"
|
||||
body: |
|
||||
Auto-generated by the sponsor image workflow
|
||||
|
||||
[skip ci] [skip actions]
|
||||
branch: update-sponsors
|
||||
base: master
|
||||
delete-branch: true
|
||||
draft: false
|
||||
|
|
|
|||
77
.github/workflows/issue-triage-automation.yml
vendored
Normal file
77
.github/workflows/issue-triage-automation.yml
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
name: Issue Triage Automation
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, reopened, labeled, unlabeled]
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
contents: read
|
||||
steps:
|
||||
# Request more info for unclear bug reports
|
||||
- name: Request more info
|
||||
uses: actions/github-script@v6
|
||||
if: |
|
||||
contains(github.event.issue.labels.*.name, 'bug') &&
|
||||
!contains(github.event.issue.body, 'wails doctor') &&
|
||||
!contains(github.event.issue.body, 'reproduction')
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `👋 Thanks for reporting this issue! To help us investigate, could you please:
|
||||
|
||||
1. Add the output of \`wails doctor\` if not already included
|
||||
2. Provide clear steps to reproduce the issue
|
||||
3. If possible, create a minimal reproduction of the issue
|
||||
|
||||
This will help us resolve your issue much faster. Thank you!`
|
||||
});
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['awaiting feedback']
|
||||
});
|
||||
|
||||
# Prioritize security issues
|
||||
- name: Prioritize security issues
|
||||
uses: actions/github-script@v6
|
||||
if: contains(github.event.issue.labels.*.name, 'security')
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['high-priority']
|
||||
});
|
||||
|
||||
# Tag version-specific issues for project boards
|
||||
- name: Add to v2 project
|
||||
uses: actions/github-script@v6
|
||||
if: |
|
||||
contains(github.event.issue.labels.*.name, 'v2-only') &&
|
||||
!contains(github.event.issue.labels.*.name, 'v3-alpha')
|
||||
with:
|
||||
script: |
|
||||
// Replace PROJECT_ID with your actual GitHub project ID
|
||||
// This is a placeholder as the actual implementation would require
|
||||
// GraphQL API calls to add to a project board
|
||||
console.log('Would add to v2 project board');
|
||||
|
||||
# Tag version-specific issues for project boards
|
||||
- name: Add to v3 project
|
||||
uses: actions/github-script@v6
|
||||
if: contains(github.event.issue.labels.*.name, 'v3-alpha')
|
||||
with:
|
||||
script: |
|
||||
// Replace PROJECT_ID with your actual GitHub project ID
|
||||
// This is a placeholder as the actual implementation would require
|
||||
// GraphQL API calls to add to a project board
|
||||
console.log('Would add to v3 project board');
|
||||
723
.github/workflows/nightly-release-v3.yml
vendored
Normal file
723
.github/workflows/nightly-release-v3.yml
vendored
Normal file
|
|
@ -0,0 +1,723 @@
|
|||
name: Nightly Release v3-alpha
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # 2 AM UTC daily
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
force_release:
|
||||
description: 'Force release even if no changes detected'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
dry_run:
|
||||
description: 'Run in dry-run mode (no actual release)'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
nightly-release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: read
|
||||
actions: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: v3-alpha
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.WAILS_REPO_TOKEN || github.token }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.24'
|
||||
cache: true
|
||||
cache-dependency-path: 'v3/go.sum'
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v2
|
||||
with:
|
||||
version: 3.x
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Git
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Configure git to use the token for authentication
|
||||
git config --global url."https://x-access-token:${{ secrets.WAILS_REPO_TOKEN || github.token }}@github.com/".insteadOf "https://github.com/"
|
||||
|
||||
- name: Check for existing release tag
|
||||
id: check_tag
|
||||
run: |
|
||||
if git describe --tags --exact-match HEAD 2>/dev/null; then
|
||||
echo "has_tag=true" >> $GITHUB_OUTPUT
|
||||
echo "tag=$(git describe --tags --exact-match HEAD)" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_tag=false" >> $GITHUB_OUTPUT
|
||||
echo "tag=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Check for unreleased changelog content
|
||||
id: changelog_check
|
||||
run: |
|
||||
echo "🔍 Checking UNRELEASED_CHANGELOG.md for content..."
|
||||
|
||||
# Run the release script in check mode to see if there's content
|
||||
cd v3/tasks/release
|
||||
|
||||
# Use the release script itself to check for content
|
||||
if go run release.go --check-only 2>/dev/null; then
|
||||
echo "has_unreleased_content=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ Found unreleased changelog content"
|
||||
else
|
||||
echo "has_unreleased_content=false" >> $GITHUB_OUTPUT
|
||||
echo "ℹ️ No unreleased changelog content found"
|
||||
fi
|
||||
|
||||
- name: Quick change detection and early exit
|
||||
id: quick_check
|
||||
run: |
|
||||
echo "🔍 Quick check for changes to determine if we should continue..."
|
||||
|
||||
# First check if we have unreleased changelog content
|
||||
if [ "${{ steps.changelog_check.outputs.has_unreleased_content }}" == "true" ]; then
|
||||
echo "✅ Found unreleased changelog content, proceeding with release"
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "should_continue=true" >> $GITHUB_OUTPUT
|
||||
echo "reason=Found unreleased changelog content" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# If no unreleased changelog content, check for git changes as fallback
|
||||
echo "No unreleased changelog content found, checking for git changes..."
|
||||
|
||||
# Check if current commit has a release tag
|
||||
if git describe --tags --exact-match HEAD 2>/dev/null; then
|
||||
CURRENT_TAG=$(git describe --tags --exact-match HEAD)
|
||||
echo "Current commit has release tag: $CURRENT_TAG"
|
||||
|
||||
# For tagged commits, check if there are changes since the tag
|
||||
COMMIT_COUNT=$(git rev-list ${CURRENT_TAG}..HEAD --count)
|
||||
if [ "$COMMIT_COUNT" -eq 0 ]; then
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
echo "should_continue=false" >> $GITHUB_OUTPUT
|
||||
echo "reason=No changes since existing tag $CURRENT_TAG and no unreleased changelog content" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "should_continue=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
else
|
||||
# No current tag, check against latest release
|
||||
LATEST_TAG=$(git tag --list "v3.0.0-alpha.*" | sort -V | tail -1)
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
echo "No previous release found, proceeding with release"
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "should_continue=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
COMMIT_COUNT=$(git rev-list ${LATEST_TAG}..HEAD --count)
|
||||
if [ "$COMMIT_COUNT" -gt 0 ]; then
|
||||
echo "Found $COMMIT_COUNT commits since $LATEST_TAG"
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "should_continue=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
echo "should_continue=false" >> $GITHUB_OUTPUT
|
||||
echo "reason=No changes since latest release $LATEST_TAG and no unreleased changelog content" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Early exit - No changes detected
|
||||
if: |
|
||||
steps.quick_check.outputs.should_continue == 'false' &&
|
||||
github.event.inputs.force_release != 'true'
|
||||
run: |
|
||||
echo "🛑 EARLY EXIT: ${{ steps.quick_check.outputs.reason }}"
|
||||
echo ""
|
||||
echo "ℹ️ No changes detected since last release and force_release is not enabled."
|
||||
echo " Workflow will exit early to save resources."
|
||||
echo ""
|
||||
echo " To force a release anyway, run this workflow with 'force_release=true'"
|
||||
echo ""
|
||||
echo "## 🛑 Early Exit Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Reason:** ${{ steps.quick_check.outputs.reason }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Action:** Workflow exited early to save resources" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Force Release:** Set 'force_release=true' to override this behavior" >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
|
||||
- name: Continue with release process
|
||||
if: |
|
||||
steps.quick_check.outputs.should_continue == 'true' ||
|
||||
github.event.inputs.force_release == 'true'
|
||||
run: |
|
||||
echo "✅ Proceeding with release process..."
|
||||
if [ "${{ github.event.inputs.force_release }}" == "true" ]; then
|
||||
echo "🔨 FORCE RELEASE: Overriding change detection"
|
||||
fi
|
||||
|
||||
- name: Run release script
|
||||
id: release
|
||||
if: |
|
||||
steps.quick_check.outputs.should_continue == 'true' ||
|
||||
github.event.inputs.force_release == 'true'
|
||||
run: |
|
||||
cd v3
|
||||
|
||||
echo "🚀 Running release task..."
|
||||
echo "======================================================="
|
||||
|
||||
# Initialize error tracking
|
||||
RELEASE_ERRORS=""
|
||||
RELEASE_SUCCESS=true
|
||||
|
||||
# Store the original version for comparison
|
||||
ORIGINAL_VERSION=$(cat internal/version/version.txt 2>/dev/null || echo "unknown")
|
||||
echo "📌 Current version: $ORIGINAL_VERSION"
|
||||
|
||||
# Run the release task and capture output with error handling
|
||||
if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then
|
||||
echo "🧪 DRY RUN MODE: Simulating release task execution"
|
||||
# In dry run, we'll simulate the task without making actual changes
|
||||
OUTPUT=$(task release 2>&1 || true)
|
||||
RELEASE_EXIT_CODE=0 # Always succeed in dry run
|
||||
echo "$OUTPUT"
|
||||
else
|
||||
echo "🚀 LIVE MODE: Executing release task"
|
||||
OUTPUT=$(task release 2>&1)
|
||||
RELEASE_EXIT_CODE=$?
|
||||
echo "$OUTPUT"
|
||||
|
||||
if [ $RELEASE_EXIT_CODE -ne 0 ]; then
|
||||
echo "❌ Release task failed with exit code $RELEASE_EXIT_CODE"
|
||||
RELEASE_ERRORS="$RELEASE_ERRORS\n- Release task execution failed: $OUTPUT"
|
||||
RELEASE_SUCCESS=false
|
||||
else
|
||||
echo "✅ Release task completed successfully"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify version file exists and is readable
|
||||
if [ ! -f "internal/version/version.txt" ]; then
|
||||
echo "❌ Version file not found: internal/version/version.txt"
|
||||
RELEASE_ERRORS="$RELEASE_ERRORS\n- Version file not found after release task execution"
|
||||
RELEASE_SUCCESS=false
|
||||
RELEASE_VERSION="unknown"
|
||||
else
|
||||
RELEASE_VERSION=$(cat internal/version/version.txt 2>/dev/null || echo "unknown")
|
||||
if [ "$RELEASE_VERSION" == "unknown" ]; then
|
||||
echo "❌ Failed to read version from file"
|
||||
RELEASE_ERRORS="$RELEASE_ERRORS\n- Failed to read version from version.txt"
|
||||
RELEASE_SUCCESS=false
|
||||
else
|
||||
echo "✅ Successfully read version: $RELEASE_VERSION"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if version changed
|
||||
VERSION_CHANGED="false"
|
||||
if [ "$ORIGINAL_VERSION" != "$RELEASE_VERSION" ] && [ "$RELEASE_VERSION" != "unknown" ]; then
|
||||
echo "✅ Version changed from $ORIGINAL_VERSION to $RELEASE_VERSION"
|
||||
VERSION_CHANGED="true"
|
||||
else
|
||||
echo "ℹ️ Version unchanged: $RELEASE_VERSION"
|
||||
fi
|
||||
|
||||
RELEASE_TAG="${RELEASE_VERSION}"
|
||||
RELEASE_TITLE="Wails ${RELEASE_VERSION}"
|
||||
|
||||
# Set outputs for next steps
|
||||
echo "version=$RELEASE_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "tag=$RELEASE_TAG" >> $GITHUB_OUTPUT
|
||||
echo "title=$RELEASE_TITLE" >> $GITHUB_OUTPUT
|
||||
echo "is_prerelease=true" >> $GITHUB_OUTPUT
|
||||
echo "is_latest=false" >> $GITHUB_OUTPUT
|
||||
echo "has_changes=${{ steps.changelog_check.outputs.has_unreleased_content }}" >> $GITHUB_OUTPUT
|
||||
echo "success=$RELEASE_SUCCESS" >> $GITHUB_OUTPUT
|
||||
echo "version_changed=$VERSION_CHANGED" >> $GITHUB_OUTPUT
|
||||
|
||||
# Generate release notes from UNRELEASED_CHANGELOG.md if it has content
|
||||
if [ "${{ steps.changelog_check.outputs.has_unreleased_content }}" == "true" ] && [ "$RELEASE_SUCCESS" == "true" ]; then
|
||||
echo "📝 Generating release notes from UNRELEASED_CHANGELOG.md..."
|
||||
|
||||
# Use the release script to extract changelog content
|
||||
cd tasks/release
|
||||
if CHANGELOG_CONTENT=$(go run release.go --extract-changelog 2>&1); then
|
||||
if [ -n "$CHANGELOG_CONTENT" ] && [ "$CHANGELOG_CONTENT" != "No changelog content found." ]; then
|
||||
echo "### Changes in this release:" > ../../release-notes.txt
|
||||
echo "" >> ../../release-notes.txt
|
||||
echo "$CHANGELOG_CONTENT" >> ../../release-notes.txt
|
||||
echo "✅ Successfully extracted changelog content"
|
||||
echo "release_notes_file=release-notes.txt" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "ℹ️ No changelog content to extract"
|
||||
echo "No detailed changelog available for this release." > ../../release-notes.txt
|
||||
echo "release_notes_file=release-notes.txt" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
else
|
||||
echo "⚠️ Failed to extract changelog content: $CHANGELOG_CONTENT"
|
||||
echo "No detailed changelog available for this release." > ../../release-notes.txt
|
||||
RELEASE_ERRORS="$RELEASE_ERRORS\n- Failed to extract changelog content for release notes"
|
||||
echo "release_notes_file=release-notes.txt" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
cd ../..
|
||||
else
|
||||
echo "release_notes_file=" >> $GITHUB_OUTPUT
|
||||
if [ "$RELEASE_SUCCESS" != "true" ]; then
|
||||
echo "⚠️ Skipping release notes generation due to release task failure"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set error output for later steps
|
||||
if [ -n "$RELEASE_ERRORS" ]; then
|
||||
echo "release_errors<<EOF" >> $GITHUB_OUTPUT
|
||||
echo -e "$RELEASE_ERRORS" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
echo "has_release_errors=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_release_errors=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create and push git tag
|
||||
id: git_tag
|
||||
if: |
|
||||
(steps.quick_check.outputs.should_continue == 'true' || github.event.inputs.force_release == 'true') &&
|
||||
steps.check_tag.outputs.has_tag == 'false' &&
|
||||
github.event.inputs.dry_run != 'true' &&
|
||||
steps.release.outputs.success == 'true' &&
|
||||
steps.release.outputs.version_changed == 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.WAILS_REPO_TOKEN || github.token }}
|
||||
run: |
|
||||
echo "🏷️ Creating and pushing git tag: ${{ steps.release.outputs.tag }}"
|
||||
|
||||
# Initialize error tracking
|
||||
GIT_ERRORS=""
|
||||
GIT_SUCCESS=true
|
||||
|
||||
# Create git tag with error handling
|
||||
if git tag -a "${{ steps.release.outputs.tag }}" -m "Release ${{ steps.release.outputs.version }}" 2>&1; then
|
||||
echo "✅ Successfully created git tag: ${{ steps.release.outputs.tag }}"
|
||||
else
|
||||
echo "❌ Failed to create git tag"
|
||||
GIT_ERRORS="$GIT_ERRORS\n- Failed to create git tag: ${{ steps.release.outputs.tag }}"
|
||||
GIT_SUCCESS=false
|
||||
fi
|
||||
|
||||
# Push tag with retry logic and error handling
|
||||
if [ "$GIT_SUCCESS" == "true" ]; then
|
||||
RETRY_COUNT=0
|
||||
MAX_RETRIES=3
|
||||
PUSH_SUCCESS=false
|
||||
|
||||
while [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ "$PUSH_SUCCESS" == "false" ]; do
|
||||
RETRY_COUNT=$((RETRY_COUNT + 1))
|
||||
echo "🔄 Attempting to push tag (attempt $RETRY_COUNT/$MAX_RETRIES)..."
|
||||
|
||||
if git push "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" "${{ steps.release.outputs.tag }}" 2>&1; then
|
||||
echo "✅ Successfully pushed git tag to origin"
|
||||
PUSH_SUCCESS=true
|
||||
else
|
||||
echo "❌ Failed to push git tag (attempt $RETRY_COUNT/$MAX_RETRIES)"
|
||||
if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then
|
||||
echo "⏳ Waiting 5 seconds before retry..."
|
||||
sleep 5
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$PUSH_SUCCESS" == "false" ]; then
|
||||
echo "❌ Failed to push git tag after $MAX_RETRIES attempts"
|
||||
GIT_ERRORS="$GIT_ERRORS\n- Failed to push git tag after $MAX_RETRIES attempts"
|
||||
GIT_SUCCESS=false
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set outputs for later steps
|
||||
echo "success=$GIT_SUCCESS" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ -n "$GIT_ERRORS" ]; then
|
||||
echo "git_tag_errors<<EOF" >> $GITHUB_OUTPUT
|
||||
echo -e "$GIT_ERRORS" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
echo "has_git_errors=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_git_errors=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Commit and push changes
|
||||
id: git_commit
|
||||
if: |
|
||||
(steps.quick_check.outputs.should_continue == 'true' || github.event.inputs.force_release == 'true') &&
|
||||
github.event.inputs.dry_run != 'true' &&
|
||||
steps.release.outputs.success == 'true' &&
|
||||
steps.release.outputs.version_changed == 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.WAILS_REPO_TOKEN || github.token }}
|
||||
run: |
|
||||
echo "📝 Committing and pushing changes..."
|
||||
|
||||
# Initialize error tracking
|
||||
COMMIT_ERRORS=""
|
||||
COMMIT_SUCCESS=true
|
||||
|
||||
# Add any changes made by the release script with error handling
|
||||
if git add . 2>&1; then
|
||||
echo "✅ Successfully staged changes"
|
||||
else
|
||||
echo "❌ Failed to stage changes"
|
||||
COMMIT_ERRORS="$COMMIT_ERRORS\n- Failed to stage changes with git add"
|
||||
COMMIT_SUCCESS=false
|
||||
fi
|
||||
|
||||
# Check if there are changes to commit
|
||||
if [ "$COMMIT_SUCCESS" == "true" ]; then
|
||||
if ! git diff --cached --quiet; then
|
||||
echo "📝 Changes detected, creating commit..."
|
||||
|
||||
# Create commit with error handling
|
||||
if git commit -m "${{ steps.release.outputs.version }}" 2>&1; then
|
||||
echo "✅ Successfully created commit"
|
||||
|
||||
# Push changes with retry logic
|
||||
RETRY_COUNT=0
|
||||
MAX_RETRIES=3
|
||||
PUSH_SUCCESS=false
|
||||
|
||||
while [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ "$PUSH_SUCCESS" == "false" ]; do
|
||||
RETRY_COUNT=$((RETRY_COUNT + 1))
|
||||
echo "🔄 Attempting to push changes (attempt $RETRY_COUNT/$MAX_RETRIES)..."
|
||||
|
||||
if git push "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" v3-alpha 2>&1; then
|
||||
echo "✅ Successfully pushed changes to v3-alpha branch"
|
||||
PUSH_SUCCESS=true
|
||||
else
|
||||
echo "❌ Failed to push changes (attempt $RETRY_COUNT/$MAX_RETRIES)"
|
||||
if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then
|
||||
echo "⏳ Waiting 5 seconds before retry..."
|
||||
sleep 5
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$PUSH_SUCCESS" == "false" ]; then
|
||||
echo "❌ Failed to push changes after $MAX_RETRIES attempts"
|
||||
COMMIT_ERRORS="$COMMIT_ERRORS\n- Failed to push changes after $MAX_RETRIES attempts"
|
||||
COMMIT_SUCCESS=false
|
||||
fi
|
||||
else
|
||||
echo "❌ Failed to create commit"
|
||||
COMMIT_ERRORS="$COMMIT_ERRORS\n- Failed to create git commit"
|
||||
COMMIT_SUCCESS=false
|
||||
fi
|
||||
else
|
||||
echo "ℹ️ No changes to commit"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set outputs for later steps
|
||||
echo "success=$COMMIT_SUCCESS" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ -n "$COMMIT_ERRORS" ]; then
|
||||
echo "commit_errors<<EOF" >> $GITHUB_OUTPUT
|
||||
echo -e "$COMMIT_ERRORS" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
echo "has_commit_errors=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_commit_errors=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Read release notes
|
||||
id: read_notes
|
||||
if: |
|
||||
(steps.quick_check.outputs.should_continue == 'true' || github.event.inputs.force_release == 'true') &&
|
||||
steps.release.outputs.release_notes_file != '' &&
|
||||
steps.release.outputs.version_changed == 'true'
|
||||
run: |
|
||||
cd v3
|
||||
if [ -f "release-notes.txt" ]; then
|
||||
# Read the release notes and handle multiline content
|
||||
RELEASE_NOTES=$(cat release-notes.txt)
|
||||
echo "release_notes<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "release_notes=No release notes available" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Test GitHub Release Creation (DRY RUN)
|
||||
if: |
|
||||
(steps.quick_check.outputs.should_continue == 'true' || github.event.inputs.force_release == 'true') &&
|
||||
github.event.inputs.dry_run == 'true' &&
|
||||
steps.release.outputs.version_changed == 'true'
|
||||
run: |
|
||||
echo "🧪 DRY RUN: Would create GitHub release with the following parameters:"
|
||||
echo "======================================================================="
|
||||
echo "Tag Name: ${{ steps.release.outputs.tag }}"
|
||||
echo "Release Name: ${{ steps.release.outputs.title }}"
|
||||
echo "Is Prerelease: ${{ steps.release.outputs.is_prerelease }}"
|
||||
echo "Is Latest: ${{ steps.release.outputs.is_latest }}"
|
||||
echo "Has Changes: ${{ steps.release.outputs.has_changes }}"
|
||||
echo ""
|
||||
echo "Release Body Preview:"
|
||||
echo "## Wails v3 Alpha Release - ${{ steps.release.outputs.version }}"
|
||||
echo ""
|
||||
cat << 'RELEASE_NOTES_EOF'
|
||||
${{ steps.read_notes.outputs.release_notes }}
|
||||
RELEASE_NOTES_EOF
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
echo "🤖 This is an automated nightly release generated from the latest changes in the v3-alpha branch."
|
||||
echo ""
|
||||
echo "**Installation:**"
|
||||
echo "\`\`\`bash"
|
||||
echo "go install github.com/wailsapp/wails/v3/cmd/wails@${{ steps.release.outputs.tag }}"
|
||||
echo "\`\`\`"
|
||||
echo ""
|
||||
echo "**⚠️ Alpha Warning:** This is pre-release software and may contain bugs or incomplete features."
|
||||
echo ""
|
||||
echo "✅ DRY RUN: GitHub release creation test completed successfully!"
|
||||
|
||||
- name: Create GitHub Release (LIVE)
|
||||
id: github_release
|
||||
if: |
|
||||
(steps.quick_check.outputs.should_continue == 'true' || github.event.inputs.force_release == 'true') &&
|
||||
github.event.inputs.dry_run != 'true' &&
|
||||
steps.release.outputs.success == 'true' &&
|
||||
steps.release.outputs.version_changed == 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
echo "🚀 Creating GitHub release using gh CLI..."
|
||||
|
||||
# Create release notes in a temporary file
|
||||
cat > release_notes.md << 'EOF'
|
||||
## Wails v3 Alpha Release - ${{ steps.release.outputs.version }}
|
||||
|
||||
${{ steps.read_notes.outputs.release_notes }}
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
🤖 This is an automated nightly release generated from the latest changes in the v3-alpha branch.
|
||||
|
||||
**Installation:**
|
||||
```bash
|
||||
go install github.com/wailsapp/wails/v3/cmd/wails@${{ steps.release.outputs.tag }}
|
||||
```
|
||||
|
||||
**⚠️ Alpha Warning:** This is pre-release software and may contain bugs or incomplete features.
|
||||
EOF
|
||||
|
||||
# Create the release
|
||||
if gh release create "${{ steps.release.outputs.tag }}" \
|
||||
--title "${{ steps.release.outputs.title }}" \
|
||||
--notes-file release_notes.md \
|
||||
--target v3-alpha \
|
||||
--prerelease; then
|
||||
echo "✅ Successfully created GitHub release"
|
||||
echo "outcome=success" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "❌ Failed to create GitHub release"
|
||||
echo "outcome=failure" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.WAILS_REPO_TOKEN || github.token }}
|
||||
|
||||
- name: Handle GitHub Release Creation Result
|
||||
id: release_result
|
||||
if: |
|
||||
(steps.quick_check.outputs.should_continue == 'true' || github.event.inputs.force_release == 'true') &&
|
||||
github.event.inputs.dry_run != 'true' &&
|
||||
steps.release.outputs.success == 'true' &&
|
||||
steps.release.outputs.version_changed == 'true'
|
||||
run: |
|
||||
echo "📋 Checking GitHub release creation result..."
|
||||
|
||||
# Initialize error tracking
|
||||
GITHUB_ERRORS=""
|
||||
GITHUB_SUCCESS=true
|
||||
|
||||
# Check if GitHub release creation succeeded
|
||||
if [ "${{ steps.github_release.outcome }}" == "success" ]; then
|
||||
echo "✅ GitHub release created successfully"
|
||||
echo "🔗 Release URL: https://github.com/${{ github.repository }}/releases/tag/${{ steps.release.outputs.tag }}"
|
||||
else
|
||||
echo "❌ GitHub release creation failed"
|
||||
GITHUB_ERRORS="$GITHUB_ERRORS\n- GitHub release creation failed with outcome: ${{ steps.github_release.outcome }}"
|
||||
GITHUB_SUCCESS=false
|
||||
fi
|
||||
|
||||
# Set outputs for summary
|
||||
echo "success=$GITHUB_SUCCESS" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ -n "$GITHUB_ERRORS" ]; then
|
||||
echo "github_errors<<EOF" >> $GITHUB_OUTPUT
|
||||
echo -e "$GITHUB_ERRORS" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
echo "has_github_errors=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_github_errors=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Error Summary and Reporting
|
||||
id: error_summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "📊 Generating comprehensive error summary..."
|
||||
|
||||
# Initialize error tracking
|
||||
TOTAL_ERRORS=0
|
||||
ERROR_SUMMARY=""
|
||||
OVERALL_SUCCESS=true
|
||||
|
||||
# Check for changelog errors
|
||||
if [ "${{ steps.changelog_check.outputs.has_errors }}" == "true" ]; then
|
||||
echo "❌ Changelog processing errors detected"
|
||||
ERROR_SUMMARY="$ERROR_SUMMARY\n### 📄 Changelog Processing Errors\n${{ steps.changelog_check.outputs.changelog_errors }}\n"
|
||||
TOTAL_ERRORS=$((TOTAL_ERRORS + 1))
|
||||
OVERALL_SUCCESS=false
|
||||
fi
|
||||
|
||||
# Check for release script errors
|
||||
if [ "${{ steps.release.outputs.has_release_errors }}" == "true" ]; then
|
||||
echo "❌ Release script errors detected"
|
||||
ERROR_SUMMARY="$ERROR_SUMMARY\n### 🚀 Release Script Errors\n${{ steps.release.outputs.release_errors }}\n"
|
||||
TOTAL_ERRORS=$((TOTAL_ERRORS + 1))
|
||||
OVERALL_SUCCESS=false
|
||||
fi
|
||||
|
||||
# Check for git tag errors
|
||||
if [ "${{ steps.git_tag.outputs.has_git_errors }}" == "true" ]; then
|
||||
echo "❌ Git tag errors detected"
|
||||
ERROR_SUMMARY="$ERROR_SUMMARY\n### 🏷️ Git Tag Errors\n${{ steps.git_tag.outputs.git_tag_errors }}\n"
|
||||
TOTAL_ERRORS=$((TOTAL_ERRORS + 1))
|
||||
OVERALL_SUCCESS=false
|
||||
fi
|
||||
|
||||
# Check for git commit errors
|
||||
if [ "${{ steps.git_commit.outputs.has_commit_errors }}" == "true" ]; then
|
||||
echo "❌ Git commit errors detected"
|
||||
ERROR_SUMMARY="$ERROR_SUMMARY\n### 📝 Git Commit Errors\n${{ steps.git_commit.outputs.commit_errors }}\n"
|
||||
TOTAL_ERRORS=$((TOTAL_ERRORS + 1))
|
||||
OVERALL_SUCCESS=false
|
||||
fi
|
||||
|
||||
# Check for GitHub release errors
|
||||
if [ "${{ steps.release_result.outputs.has_github_errors }}" == "true" ]; then
|
||||
echo "❌ GitHub release errors detected"
|
||||
ERROR_SUMMARY="$ERROR_SUMMARY\n### 🐙 GitHub Release Errors\n${{ steps.release_result.outputs.github_errors }}\n"
|
||||
TOTAL_ERRORS=$((TOTAL_ERRORS + 1))
|
||||
OVERALL_SUCCESS=false
|
||||
fi
|
||||
|
||||
# Set outputs for final summary
|
||||
echo "total_errors=$TOTAL_ERRORS" >> $GITHUB_OUTPUT
|
||||
echo "overall_success=$OVERALL_SUCCESS" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ -n "$ERROR_SUMMARY" ]; then
|
||||
echo "error_summary<<EOF" >> $GITHUB_OUTPUT
|
||||
echo -e "$ERROR_SUMMARY" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# Log summary
|
||||
if [ "$OVERALL_SUCCESS" == "true" ]; then
|
||||
echo "✅ Workflow completed successfully with no errors"
|
||||
else
|
||||
echo "⚠️ Workflow completed with $TOTAL_ERRORS error categories"
|
||||
fi
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then
|
||||
echo "## 🧪 DRY RUN Release Test Summary" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "## 🚀 Nightly Release Summary" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "================================" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Version:** ${{ steps.release.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Tag:** ${{ steps.release.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Version Changed:** ${{ steps.release.outputs.version_changed }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Has existing tag:** ${{ steps.check_tag.outputs.has_tag }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Has unreleased changelog content:** ${{ steps.changelog_check.outputs.has_unreleased_content }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Has changes:** ${{ steps.release.outputs.has_changes }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Is prerelease:** ${{ steps.release.outputs.is_prerelease }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Is latest:** ${{ steps.release.outputs.is_latest }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Overall status
|
||||
if [ "${{ steps.error_summary.outputs.overall_success }}" == "true" ]; then
|
||||
if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then
|
||||
echo "- **Mode:** 🧪 DRY RUN (no actual release created)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Status:** ✅ Test completed successfully" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "- **Mode:** 🚀 Live release" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Status:** ✅ Release created successfully" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
else
|
||||
echo "- **Mode:** ${{ github.event.inputs.dry_run == 'true' && '🧪 DRY RUN' || '🚀 Live release' }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Status:** ⚠️ Completed with ${{ steps.error_summary.outputs.total_errors }} error(s)" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Release Processing" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "${{ steps.release.outputs.version_changed }}" == "true" ]; then
|
||||
echo "✅ **Version was incremented** and release created" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "ℹ️ **Version was not changed** - no release created" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Changelog Processing" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "${{ steps.changelog_check.outputs.has_unreleased_content }}" == "true" ]; then
|
||||
echo "✅ **UNRELEASED_CHANGELOG.md** had content and was processed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Content moved to main changelog" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- UNRELEASED_CHANGELOG.md reset with template" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "ℹ️ **UNRELEASED_CHANGELOG.md** had no content to process" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# Error reporting section
|
||||
if [ "${{ steps.error_summary.outputs.total_errors }}" -gt 0 ]; then
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## ⚠️ Error Report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Total Error Categories:** ${{ steps.error_summary.outputs.total_errors }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "${{ steps.error_summary.outputs.error_summary }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### 🔧 Troubleshooting Tips" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Check the individual step logs above for detailed error messages" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Verify GitHub token permissions (contents: write, pull-requests: read)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Ensure UNRELEASED_CHANGELOG.md follows the expected format" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Check for network connectivity issues if git/GitHub operations failed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Re-run the workflow with 'force_release=true' if needed" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Release Notes Preview" >> $GITHUB_STEP_SUMMARY
|
||||
if [ -n "${{ steps.read_notes.outputs.release_notes }}" ]; then
|
||||
echo "${{ steps.read_notes.outputs.release_notes }}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "No specific release notes generated" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||
echo "*Generated by automated nightly release workflow with enhanced error handling and changelog integration*" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Set final workflow status
|
||||
if [ "${{ steps.error_summary.outputs.overall_success }}" != "true" ]; then
|
||||
echo "⚠️ Workflow completed with errors. Check the summary above for details."
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -1,20 +1,23 @@
|
|||
name: PR Checks
|
||||
name: PR Checks (master)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
check_docs:
|
||||
name: Check Docs
|
||||
if: ${{github.repository == 'wailsapp/wails' && contains(github.head_ref,'feature/')}}
|
||||
if: ${{github.repository == 'wailsapp/wails' && github.base_ref == 'master'}}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Verify Changed files
|
||||
uses: tj-actions/verify-changed-files@v17
|
||||
uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
|
||||
id: verify-changed-files
|
||||
with:
|
||||
files: |
|
||||
|
|
@ -26,47 +29,28 @@ jobs:
|
|||
run: |
|
||||
echo "::warning::Feature branch does not contain any changes to the website."
|
||||
|
||||
# lint_go:
|
||||
# name: Run Go Linters
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Checkout code
|
||||
# uses: actions/checkout@v4
|
||||
#
|
||||
# - name: Setup Go
|
||||
# uses: actions/setup-go@v4
|
||||
# with:
|
||||
# go-version: "1.21"
|
||||
#
|
||||
# - name: Update go modules
|
||||
# working-directory: ./v2
|
||||
# run: go mod tidy
|
||||
#
|
||||
# - name: Run Linter
|
||||
# uses: golangci/golangci-lint-action@v3
|
||||
# with:
|
||||
# version: v1.54
|
||||
# working-directory: ./v2
|
||||
# args: --timeout=10m0s --config ./.golangci.yml
|
||||
|
||||
test_go:
|
||||
name: Run Go Tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: github.event.review.state == 'approved'
|
||||
if: >
|
||||
github.event.review.state == 'approved' &&
|
||||
github.repository == 'wailsapp/wails' &&
|
||||
github.base_ref == 'master' &&
|
||||
github.event.pull_request.head.ref != 'update-sponsors'
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-22.04, windows-latest, macos-latest, ubuntu-24.04]
|
||||
go-version: ['1.21']
|
||||
go-version: ['1.23']
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install linux dependencies ( 22.04 )
|
||||
- name: Install linux dependencies (22.04)
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev build-essential pkg-config
|
||||
|
||||
- name: Install linux dependencies ( 24.04 )
|
||||
- name: Install linux dependencies (24.04)
|
||||
if: matrix.os == 'ubuntu-24.04'
|
||||
run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config
|
||||
|
||||
111
.github/workflows/publish-npm.yml
vendored
111
.github/workflows/publish-npm.yml
vendored
|
|
@ -1,63 +1,114 @@
|
|||
on:
|
||||
push:
|
||||
branches: ['v3-alpha']
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: publish-npm-v3
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
detect:
|
||||
name: Detect committed changes
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
outputs:
|
||||
changed: ${{ steps.package-json-changes.outputs.any_modified == 'true' || steps.source-changes.outputs.any_modified == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
persist-credentials: 'true'
|
||||
|
||||
- name: Detect committed package.json changes
|
||||
id: package-json-changes
|
||||
uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
|
||||
with:
|
||||
files: |
|
||||
v3/internal/runtime/desktop/@wailsio/runtime/package.json
|
||||
|
||||
- name: Detect committed source changes
|
||||
if: >-
|
||||
steps.package-json-changes.outputs.any_modified != 'true'
|
||||
id: source-changes
|
||||
uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
|
||||
with:
|
||||
files: |
|
||||
v3/internal/runtime/Taskfile.yaml
|
||||
v3/internal/runtime/desktop/@wailsio/compiled/main.js
|
||||
v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.json
|
||||
v3/internal/runtime/desktop/@wailsio/runtime/src/**
|
||||
v3/pkg/events/events.txt
|
||||
v3/tasks/events/**
|
||||
|
||||
rebuild_and_publish:
|
||||
name: Rebuild and publish
|
||||
needs: [detect]
|
||||
if: >-
|
||||
!failure() && !cancelled()
|
||||
&& (github.event_name == 'workflow_dispatch' || needs.detect.outputs.changed == 'true')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: 'v3-alpha'
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }}
|
||||
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config --local user.email "github-actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
|
||||
- name: Setup go-task
|
||||
uses: pnorton5432/setup-task@v1
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v2
|
||||
with:
|
||||
task-version: 3.29.1
|
||||
version: 3.x
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
- run: |
|
||||
npm ci
|
||||
npm run build:types
|
||||
npm run build:docs
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
|
||||
run: |
|
||||
npm ci
|
||||
npx --yes esbuild@latest --version
|
||||
|
||||
- name: Verify Changed files
|
||||
uses: tj-actions/verify-changed-files@v20
|
||||
id: verify-changed-files
|
||||
with:
|
||||
files: |
|
||||
v3/internal/runtime/desktop/@wailsio/runtime/src/*.js
|
||||
v3/internal/runtime/desktop/@wailsio/runtime/types/*.d.ts
|
||||
v3/internal/runtime/desktop/@wailsio/runtime/docs/*.*
|
||||
- name: Clean build artifacts
|
||||
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
|
||||
run: npm run clean
|
||||
|
||||
- name: test action
|
||||
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
||||
id: get-version
|
||||
uses: beaconbrigade/package-json-version@v0.3.2
|
||||
with:
|
||||
path: v3/internal/runtime/desktop/@wailsio/runtime
|
||||
- name: Build bundled runtime
|
||||
working-directory: v3
|
||||
run: task runtime:build
|
||||
|
||||
- name: Test+Build npm package
|
||||
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
|
||||
run: |
|
||||
npm test
|
||||
npm run build
|
||||
|
||||
- name: Bump version
|
||||
id: bump-version
|
||||
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
|
||||
run: |
|
||||
echo "version=$(npm --no-git-tag-version --force version prerelease)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Commit changes
|
||||
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
||||
run: |
|
||||
git add .
|
||||
git commit -m "[skip ci] Publish @wailsio/runtime ${{ steps.get-version.outputs.version }}"
|
||||
git commit -m "[skip ci] Publish @wailsio/runtime ${{ steps.bump-version.outputs.version }}"
|
||||
git push
|
||||
fi
|
||||
|
||||
- uses: JS-DevTools/npm-publish@v3
|
||||
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
||||
- name: Publish npm package
|
||||
uses: JS-DevTools/npm-publish@v3
|
||||
with:
|
||||
package: v3/internal/runtime/desktop/@wailsio/runtime
|
||||
access: public
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
|
|
|
|||
3
.github/workflows/semgrep.yml
vendored
3
.github/workflows/semgrep.yml
vendored
|
|
@ -5,6 +5,7 @@ on:
|
|||
branches:
|
||||
- main
|
||||
- master
|
||||
- v3-alpha
|
||||
paths:
|
||||
- .github/workflows/semgrep.yml
|
||||
schedule:
|
||||
|
|
@ -14,7 +15,7 @@ name: Semgrep
|
|||
jobs:
|
||||
semgrep:
|
||||
name: semgrep/ci
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
|
||||
container:
|
||||
|
|
|
|||
57
.github/workflows/stale-issues.yml
vendored
Normal file
57
.github/workflows/stale-issues.yml
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
name: Mark and Close Stale Issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * *' # Run at 1 AM UTC every day
|
||||
workflow_dispatch: # Allow manual triggering
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
# General settings
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 45
|
||||
days-before-close: 10
|
||||
stale-issue-label: 'stale'
|
||||
operations-per-run: 250 # Increased from 50 to 250
|
||||
|
||||
# Issue specific settings
|
||||
stale-issue-message: |
|
||||
This issue has been automatically marked as stale because it has not had recent activity.
|
||||
It will be closed if no further activity occurs within the next 10 days.
|
||||
|
||||
If this issue is still relevant, please add a comment to keep it open.
|
||||
Thank you for your contributions.
|
||||
|
||||
close-issue-message: |
|
||||
This issue has been automatically closed due to lack of activity.
|
||||
Please feel free to reopen it if it's still relevant.
|
||||
|
||||
# PR specific settings - We will not mark PRs as stale
|
||||
days-before-pr-stale: -1 # Disable PR staling
|
||||
days-before-pr-close: -1 # Disable PR closing
|
||||
|
||||
# Exemptions
|
||||
exempt-issue-labels: 'pinned,security,onhold,inprogress,Selected For Development,bug,enhancement,v3-alpha,high-priority'
|
||||
exempt-all-issue-milestones: true
|
||||
exempt-all-issue-assignees: true
|
||||
|
||||
# Protection for existing issues
|
||||
exempt-issue-created-before: '2024-01-01T00:00:00Z'
|
||||
start-date: '2025-06-01T00:00:00Z' # Don't start checking until June 1, 2025
|
||||
|
||||
# Only process issues, not PRs
|
||||
only-labels: ''
|
||||
any-of-labels: ''
|
||||
remove-stale-when-updated: true
|
||||
|
||||
# Debug options
|
||||
debug-only: false # Set to true to test without actually marking issues
|
||||
ascending: true # Process older issues first
|
||||
216
.github/workflows/test-nightly-releases.yml
vendored
Normal file
216
.github/workflows/test-nightly-releases.yml
vendored
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
name: Test Nightly Releases (Dry Run)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: 'Run in dry-run mode (no actual releases)'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
test_branch:
|
||||
description: 'Branch to test against'
|
||||
required: false
|
||||
default: 'master'
|
||||
type: string
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.24'
|
||||
|
||||
jobs:
|
||||
test-permissions:
|
||||
name: Test Release Permissions
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
authorized: ${{ steps.check.outputs.authorized }}
|
||||
steps:
|
||||
- name: Check if user is authorized
|
||||
id: check
|
||||
run: |
|
||||
# Test authorization logic
|
||||
AUTHORIZED_USERS="leaanthony"
|
||||
|
||||
if [[ "$AUTHORIZED_USERS" == *"${{ github.actor }}"* ]]; then
|
||||
echo "✅ User ${{ github.actor }} is authorized"
|
||||
echo "authorized=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "❌ User ${{ github.actor }} is not authorized"
|
||||
echo "authorized=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
test-changelog-extraction:
|
||||
name: Test Changelog Extraction
|
||||
runs-on: ubuntu-latest
|
||||
needs: test-permissions
|
||||
if: needs.test-permissions.outputs.authorized == 'true'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.test_branch }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Test v2 changelog extraction
|
||||
run: |
|
||||
echo "🧪 Testing v2 changelog extraction..."
|
||||
CHANGELOG_FILE="website/src/pages/changelog.mdx"
|
||||
|
||||
if [ ! -f "$CHANGELOG_FILE" ]; then
|
||||
echo "❌ v2 changelog file not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract unreleased section
|
||||
awk '
|
||||
/^## \[Unreleased\]/ { found=1; next }
|
||||
found && /^## / { exit }
|
||||
found && !/^$/ { print }
|
||||
' $CHANGELOG_FILE > v2_release_notes.md
|
||||
|
||||
echo "📝 v2 changelog content (first 10 lines):"
|
||||
head -10 v2_release_notes.md || echo "No content found"
|
||||
echo "Total lines: $(wc -l < v2_release_notes.md)"
|
||||
|
||||
- name: Test v3 changelog extraction (if accessible)
|
||||
run: |
|
||||
echo "🧪 Testing v3 changelog extraction..."
|
||||
|
||||
if git show v3-alpha:docs/src/content/docs/changelog.mdx > /dev/null 2>&1; then
|
||||
echo "✅ v3 changelog accessible"
|
||||
|
||||
git show v3-alpha:docs/src/content/docs/changelog.mdx | awk '
|
||||
/^## \[Unreleased\]/ { found=1; next }
|
||||
found && /^## / { exit }
|
||||
found && !/^$/ { print }
|
||||
' > v3_release_notes.md
|
||||
|
||||
echo "📝 v3 changelog content (first 10 lines):"
|
||||
head -10 v3_release_notes.md || echo "No content found"
|
||||
echo "Total lines: $(wc -l < v3_release_notes.md)"
|
||||
else
|
||||
echo "⚠️ v3 changelog not accessible from current context"
|
||||
fi
|
||||
|
||||
test-version-detection:
|
||||
name: Test Version Detection
|
||||
runs-on: ubuntu-latest
|
||||
needs: test-permissions
|
||||
if: needs.test-permissions.outputs.authorized == 'true'
|
||||
outputs:
|
||||
v2_current_version: ${{ steps.versions.outputs.v2_current }}
|
||||
v2_next_patch: ${{ steps.versions.outputs.v2_next_patch }}
|
||||
v2_next_minor: ${{ steps.versions.outputs.v2_next_minor }}
|
||||
v2_next_major: ${{ steps.versions.outputs.v2_next_major }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Test version detection logic
|
||||
id: versions
|
||||
run: |
|
||||
echo "🧪 Testing version detection..."
|
||||
|
||||
# Test v2 version parsing
|
||||
if [ -f "v2/cmd/wails/internal/version.txt" ]; then
|
||||
CURRENT_V2=$(cat v2/cmd/wails/internal/version.txt | sed 's/^v//')
|
||||
echo "Current v2 version: v$CURRENT_V2"
|
||||
echo "v2_current=v$CURRENT_V2" >> $GITHUB_OUTPUT
|
||||
|
||||
# Parse and increment
|
||||
IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_V2"
|
||||
MAJOR=${VERSION_PARTS[0]}
|
||||
MINOR=${VERSION_PARTS[1]}
|
||||
PATCH=${VERSION_PARTS[2]}
|
||||
|
||||
PATCH_VERSION="v$MAJOR.$MINOR.$((PATCH + 1))"
|
||||
MINOR_VERSION="v$MAJOR.$((MINOR + 1)).0"
|
||||
MAJOR_VERSION="v$((MAJOR + 1)).0.0"
|
||||
|
||||
echo "v2_next_patch=$PATCH_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "v2_next_minor=$MINOR_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "v2_next_major=$MAJOR_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "✅ Patch: v$CURRENT_V2 → $PATCH_VERSION"
|
||||
echo "✅ Minor: v$CURRENT_V2 → $MINOR_VERSION"
|
||||
echo "✅ Major: v$CURRENT_V2 → $MAJOR_VERSION"
|
||||
else
|
||||
echo "❌ v2 version file not found"
|
||||
fi
|
||||
|
||||
test-commit-analysis:
|
||||
name: Test Commit Analysis
|
||||
runs-on: ubuntu-latest
|
||||
needs: test-permissions
|
||||
if: needs.test-permissions.outputs.authorized == 'true'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Test commit analysis
|
||||
run: |
|
||||
echo "🧪 Testing commit analysis..."
|
||||
|
||||
# Get recent commits for testing
|
||||
echo "Recent commits:"
|
||||
git log --oneline -10
|
||||
|
||||
# Test conventional commit detection
|
||||
RECENT_COMMITS=$(git log --oneline --since="7 days ago")
|
||||
echo "Commits from last 7 days:"
|
||||
echo "$RECENT_COMMITS"
|
||||
|
||||
# Analyze for release type
|
||||
RELEASE_TYPE="patch"
|
||||
if echo "$RECENT_COMMITS" | grep -q "feat!\|fix!\|BREAKING CHANGE:"; then
|
||||
RELEASE_TYPE="major"
|
||||
elif echo "$RECENT_COMMITS" | grep -q "feat\|BREAKING CHANGE"; then
|
||||
RELEASE_TYPE="minor"
|
||||
fi
|
||||
|
||||
echo "✅ Detected release type: $RELEASE_TYPE"
|
||||
|
||||
test-summary:
|
||||
name: Test Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test-permissions, test-changelog-extraction, test-version-detection, test-commit-analysis]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Print test results
|
||||
run: |
|
||||
echo "# 🧪 Nightly Release Workflow Test Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "${{ needs.test-permissions.result }}" == "success" ]; then
|
||||
echo "✅ **Permissions Test**: Passed" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **Permissions Test**: Failed" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "${{ needs.test-changelog-extraction.result }}" == "success" ]; then
|
||||
echo "✅ **Changelog Extraction**: Passed" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **Changelog Extraction**: Failed" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "${{ needs.test-version-detection.result }}" == "success" ]; then
|
||||
echo "✅ **Version Detection**: Passed" >> $GITHUB_STEP_SUMMARY
|
||||
echo " - Current v2: ${{ needs.test-version-detection.outputs.v2_current_version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo " - Next patch: ${{ needs.test-version-detection.outputs.v2_next_patch }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo " - Next minor: ${{ needs.test-version-detection.outputs.v2_next_minor }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo " - Next major: ${{ needs.test-version-detection.outputs.v2_next_major }}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **Version Detection**: Failed" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "${{ needs.test-commit-analysis.result }}" == "success" ]; then
|
||||
echo "✅ **Commit Analysis**: Passed" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **Commit Analysis**: Failed" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Note**: This was a dry-run test. No actual releases were created." >> $GITHUB_STEP_SUMMARY
|
||||
11
.github/workflows/test-simple.yml
vendored
Normal file
11
.github/workflows/test-simple.yml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
name: Test Simple
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Test
|
||||
run: echo "Hello World"
|
||||
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
|
||||
- name: Verify Changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v41
|
||||
uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
|
||||
with:
|
||||
files: |
|
||||
website/**/*.mdx
|
||||
|
|
|
|||
49
.gitignore
vendored
49
.gitignore
vendored
|
|
@ -37,6 +37,55 @@ v2/cmd/wails/internal/commands/initialise/templates/testtemplates/
|
|||
/websitev3/site/
|
||||
/v3/examples/plugins/bin/testapp
|
||||
|
||||
# V3 Example binaries - ignore executables that match directory names
|
||||
/v3/examples/badge-custom/badge-custom
|
||||
/v3/examples/badge/badge
|
||||
/v3/examples/binding/binding
|
||||
/v3/examples/cancel-async/cancel-async
|
||||
/v3/examples/cancel-chaining/cancel-chaining
|
||||
/v3/examples/clipboard/clipboard
|
||||
/v3/examples/contextmenus/contextmenus
|
||||
/v3/examples/dev/dev
|
||||
/v3/examples/dialogs-basic/dialogs-basic
|
||||
/v3/examples/dialogs/dialogs
|
||||
/v3/examples/drag-n-drop/drag-n-drop
|
||||
/v3/examples/environment/environment
|
||||
/v3/examples/events-bug/events-bug
|
||||
/v3/examples/events/events
|
||||
/v3/examples/file-association/file-association
|
||||
/v3/examples/frameless/frameless
|
||||
/v3/examples/gin-example/gin-example
|
||||
/v3/examples/gin-routing/gin-routing
|
||||
/v3/examples/gin-service/gin-service
|
||||
/v3/examples/hide-window/hide-window
|
||||
/v3/examples/html-dnd-api/html-dnd-api
|
||||
/v3/examples/ignore-mouse/ignore-mouse
|
||||
/v3/examples/keybindings/keybindings
|
||||
/v3/examples/menu/menu
|
||||
/v3/examples/notifications/notifications
|
||||
/v3/examples/panic-handling/panic-handling
|
||||
/v3/examples/plain/plain
|
||||
/v3/examples/raw-message/raw-message
|
||||
/v3/examples/screen/screen
|
||||
/v3/examples/services/services
|
||||
/v3/examples/show-macos-toolbar/show-macos-toolbar
|
||||
/v3/examples/single-instance/single-instance
|
||||
/v3/examples/systray-basic/systray-basic
|
||||
/v3/examples/systray-custom/systray-custom
|
||||
/v3/examples/systray-menu/systray-menu
|
||||
/v3/examples/video/video
|
||||
/v3/examples/window-api/window-api
|
||||
/v3/examples/window-call/window-call
|
||||
/v3/examples/window-menu/window-menu
|
||||
/v3/examples/window/window
|
||||
/v3/examples/wml/wml
|
||||
|
||||
# Common binary names in examples
|
||||
/v3/examples/*/main
|
||||
/v3/examples/*/app
|
||||
/v3/examples/*/changeme
|
||||
/v3/examples/*/testbuild-*
|
||||
|
||||
# Temporary called mkdocs, should be renamed to more standard -website or similar
|
||||
/docs/site
|
||||
.aider*
|
||||
|
|
|
|||
1240
docs/package-lock.json
generated
1240
docs/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -17,6 +17,7 @@
|
|||
"@types/react-dom": "19.0.2",
|
||||
"astro": "4.16.17",
|
||||
"framer-motion": "11.14.4",
|
||||
"mermaid": "^10.9.3",
|
||||
"motion": "11.14.4",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
|
|
|
|||
59
docs/src/components/Mermaid.astro
Normal file
59
docs/src/components/Mermaid.astro
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
export interface Props {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const { title = "" } = Astro.props;
|
||||
---
|
||||
|
||||
<script>
|
||||
import mermaid from "mermaid";
|
||||
|
||||
// Postpone mermaid initialization
|
||||
mermaid.initialize({ startOnLoad: false });
|
||||
|
||||
function extractMermaidCode() {
|
||||
// Find all mermaid components
|
||||
const mermaidElements = document.querySelectorAll("figure.expandable-diagram");
|
||||
|
||||
mermaidElements.forEach((element) => {
|
||||
// Find the code content in the details section
|
||||
const codeElement = element.querySelector("details pre code");
|
||||
|
||||
if (!codeElement) return;
|
||||
|
||||
// Extract the text content
|
||||
let code = codeElement.textContent || "";
|
||||
|
||||
// Clean up the code
|
||||
code = code.trim();
|
||||
|
||||
// Construct the `pre` element for the diagram code
|
||||
const preElement = document.createElement("pre");
|
||||
preElement.className = "mermaid not-prose";
|
||||
preElement.innerHTML = code;
|
||||
|
||||
// Find the diagram content container and override its content
|
||||
const diagramContainer = element.querySelector(".diagram-content");
|
||||
if (diagramContainer) {
|
||||
diagramContainer.innerHTML = "";
|
||||
diagramContainer.appendChild(preElement);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for the DOM to be fully loaded
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
extractMermaidCode();
|
||||
mermaid.initialize({ startOnLoad: true });
|
||||
});
|
||||
</script>
|
||||
|
||||
<figure class="expandable-diagram">
|
||||
<figcaption>{title}</figcaption>
|
||||
<div class="diagram-content">Loading diagram...</div>
|
||||
<details>
|
||||
<summary>Source</summary>
|
||||
<pre><code><slot /></code></pre>
|
||||
</details>
|
||||
</figure>
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
title: Alpha 10 Released - A New Chapter for Wails
|
||||
description: Announcing Wails v3 Alpha 10 and our new daily release strategy
|
||||
date: 2024-12-03
|
||||
author: The Wails Team
|
||||
---
|
||||
|
||||
# Alpha 10 Released - A New Chapter for Wails
|
||||
|
||||
We're thrilled to announce the release of **Wails v3 Alpha 10** - and it's big! While our release cadence may have seemed slow recently, there's been an incredible amount of work happening behind the scenes. Today marks not just another release, but a shift in how we approach the development of Wails.
|
||||
|
||||
## Why the Wait?
|
||||
|
||||
Like many development teams, we fell into the trap of trying to achieve perfection before each release. We wanted to squash every bug, polish every feature, and ensure everything was just right. But here's the thing - software is never truly bug-free, and waiting for that mythical state only delays getting improvements into your hands.
|
||||
|
||||
## Our New Release Strategy
|
||||
|
||||
Starting today, we're adopting a **daily release strategy**. Any new changes merged during the day will be released at the end of that day. This approach will:
|
||||
|
||||
- Get bug fixes and features to you faster
|
||||
- Provide more frequent feedback loops
|
||||
- Speed up our journey to Beta
|
||||
- Make the development process more transparent
|
||||
|
||||
This means you might see more frequent, smaller releases rather than occasional large ones. We believe this will benefit everyone in the Wails community.
|
||||
|
||||
## How You Can Help
|
||||
|
||||
We've been overwhelmed by the number of people wanting to contribute to Wails! To make it easier for contributors to get involved, we're implementing a new system:
|
||||
|
||||
- Firstly, we are opening up bug reporting for alpha releases. The frequent releases allow us to do this.
|
||||
- Issues marked with **"Ready for Work"** are open for community contributions
|
||||
- These issues have clear requirements and are ready to be tackled
|
||||
- Most importantly, **we need people testing PRs** - this is one of the most valuable contributions you can make
|
||||
|
||||
Testing doesn't require deep knowledge of the codebase, but it provides immense value by ensuring changes work across different environments and use cases.
|
||||
|
||||
## What's Next?
|
||||
|
||||
With our new daily release strategy, expect to see rapid progress toward Beta. We're committed to maintaining momentum and getting Wails v3 to a stable release as quickly as possible without sacrificing quality.
|
||||
|
||||
And who knows? There might be a few surprises along the way... 😉
|
||||
|
||||
## Thank You
|
||||
|
||||
To everyone who has contributed code, tested releases, reported bugs, or simply used Wails - thank you. Your support and feedback drive this project forward.
|
||||
|
||||
Here's to more frequent releases, faster iteration, and an exciting journey ahead!
|
||||
|
||||
---
|
||||
|
||||
*Want to get involved? Check out our [GitHub repository](https://github.com/wailsapp/wails) for issues marked "ready for work", or join our community to help test the latest changes.*
|
||||
|
|
@ -25,14 +25,81 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Breaking Changes
|
||||
## v3.0.0-alpha.17 - 2025-07-31
|
||||
|
||||
## Fixed
|
||||
- Fixed notification parsing on Windows @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4450)
|
||||
|
||||
## v3.0.0-alpha.16 - 2025-07-25
|
||||
|
||||
## Added
|
||||
- Add Windows `getAccentColor` implementation by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/4427)
|
||||
|
||||
## v3.0.0-alpha.15 - 2025-07-25
|
||||
|
||||
## Added
|
||||
- Add Windows `getAccentColor` implementation by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/4427)
|
||||
|
||||
## v3.0.0-alpha.14 - 2025-07-25
|
||||
|
||||
## Added
|
||||
- Windows dark theme menus + menubar. By @leaanthony in [a29b4f0861b1d0a700e9eb213c6f1076ec40efd5](https://github.com/wailsapp/wails/commit/a29b4f0861b1d0a700e9eb213c6f1076ec40efd5)
|
||||
- Rename built-in services for clearer JS/TS bindings by @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4405)
|
||||
|
||||
## v3.0.0-alpha.12 - 2025-07-15
|
||||
|
||||
### Added
|
||||
- `app.Env.GetAccentColor` to get the accent color of a user's system. Works on MacOS. by [@etesam913](https://github.com/etesam913)
|
||||
- Add `window.ToggleFrameless()` api by [@atterpac](https://github.com/atterpac) in [#4137](https://github.com/wailsapp/wails/pull/4137)
|
||||
|
||||
### Fixed
|
||||
- Fixed doctor command to check for Windows SDK dependencies by [@kodumulo](https://github.com/kodumulo) in [#4390](https://github.com/wailsapp/wails/issues/4390)
|
||||
|
||||
|
||||
## v3.0.0-alpha.11 - 2025-07-12
|
||||
|
||||
## Added
|
||||
- Add distribution-specific build dependencies for Linux by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/4345)
|
||||
- Added bindings guide by @atterpac in [PR](https://github.com/wailsapp/wails/pull/4404)
|
||||
|
||||
## v3.0.0-alpha.10 - 2025-07-06
|
||||
|
||||
### Breaking Changes
|
||||
- **Manager API Refactoring**: Reorganized application API from flat structure to organized managers for better code organization and discoverability by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359)
|
||||
- `app.NewWebviewWindow()` → `app.Window.New()`
|
||||
- `app.CurrentWindow()` → `app.Window.Current()`
|
||||
- `app.GetAllWindows()` → `app.Window.GetAll()`
|
||||
- `app.WindowByName()` → `app.Window.GetByName()`
|
||||
- `app.EmitEvent()` → `app.Event.Emit()`
|
||||
- `app.OnApplicationEvent()` → `app.Event.OnApplicationEvent()`
|
||||
- `app.OnWindowEvent()` → `app.Event.OnWindowEvent()`
|
||||
- `app.SetApplicationMenu()` → `app.Menu.SetApplicationMenu()`
|
||||
- `app.OpenFileDialog()` → `app.Dialog.OpenFile()`
|
||||
- `app.SaveFileDialog()` → `app.Dialog.SaveFile()`
|
||||
- `app.MessageDialog()` → `app.Dialog.Message()`
|
||||
- `app.InfoDialog()` → `app.Dialog.Info()`
|
||||
- `app.WarningDialog()` → `app.Dialog.Warning()`
|
||||
- `app.ErrorDialog()` → `app.Dialog.Error()`
|
||||
- `app.QuestionDialog()` → `app.Dialog.Question()`
|
||||
- `app.NewSystemTray()` → `app.SystemTray.New()`
|
||||
- `app.GetSystemTray()` → `app.SystemTray.Get()`
|
||||
- `app.ShowContextMenu()` → `app.ContextMenu.Show()`
|
||||
- `app.RegisterKeybinding()` → `app.KeyBinding.Register()`
|
||||
- `app.UnregisterKeybinding()` → `app.KeyBinding.Unregister()`
|
||||
- `app.GetPrimaryScreen()` → `app.Screen.GetPrimary()`
|
||||
- `app.GetAllScreens()` → `app.Screen.GetAll()`
|
||||
- `app.BrowserOpenURL()` → `app.Browser.OpenURL()`
|
||||
- `app.Environment()` → `app.Env.GetAll()`
|
||||
- `app.ClipboardGetText()` → `app.Clipboard.Text()`
|
||||
- `app.ClipboardSetText()` → `app.Clipboard.SetText()`
|
||||
- Renamed Service methods: `Name` -> `ServiceName`, `OnStartup` -> `ServiceStartup`, `OnShutdown` -> `ServiceShutdown` by [@leaanthony](https://github.com/leaanthony)
|
||||
- Moved `Path` and `Paths` methods to `application` package by [@leaanthony](https://github.com/leaanthony)
|
||||
- The application menu is now macOS only by [@leaanthony](https://github.com/leaanthony)
|
||||
|
||||
### Added
|
||||
|
||||
- **Organized Testing Infrastructure**: Moved Docker test files to dedicated `test/docker/` directory with optimized images and enhanced build reliability by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359)
|
||||
- **Improved Resource Management Patterns**: Added proper event handler cleanup and context-aware goroutine management in examples by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359)
|
||||
- Support aarch64 AppImage builds by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981)
|
||||
- Add diagnostics section to `wails doctor` by [@leaanthony](https://github.com/leaanthony)
|
||||
- Add window to context when calling a service method by [@leaanthony](https://github.com/leaanthony)
|
||||
|
|
@ -53,10 +120,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Add `//wails:ignore` directive to prevent binding generation for chosen service methods by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Add `//wails:internal` directive on services and models to allow for types that are exported in Go but not in JS/TS by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Add binding generator support for constants of alias type to allow for weakly typed enums by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Add binding generator tests for Go 1.24 features by [@fbbdev](https://github.com/fbbdev) in [#4068](https://github.com/wailsapp/wails/pull/4068)
|
||||
- Add support for macOS 15 "Sequoia" to `OSInfo.Branding` for improved OS version detection in [#4065](https://github.com/wailsapp/wails/pull/4065)
|
||||
- Add `PostShutdown` hook for running custom code after the shutdown process completes by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Add `FatalError` struct to support detection of fatal errors in custom error handlers by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Standardise and document service startup and shutdown order by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Add test harness for application startup/shutdown sequence and service startup/shutdown tests by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Add `RegisterService` method for registering services after the application has been created by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Add `MarshalError` field in application and service options for custom error handling in binding calls by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Add cancellable promise wrapper that propagates cancellation requests through promise chains by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- Add the ability to tie binding call cancellation to an `AbortSignal` by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- Support `data-wml-*` attributes for WML alongside the usual `wml-*` attributes by [@leaanthony](https://github.com/leaanthony)
|
||||
- Add `Configure` method on all services for late configuration/dynamic reconfiguration by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- `fileserver` service sends a 503 Service Unavailable response when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- `kvstore` service provides an in-memory key-value store by default when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Add `Load` method on `kvstore` service to reload data from file after config changes by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Add `Clear` method on `kvstore` service to delete all keys by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Add type `Level` in `log` service to provide JS-side log-level constants by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Add `Log` method on `log` service to specify log-level dynamically by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- `sqlite` service provides an in-memory DB by default when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Add method `Close` on `sqlite` service to close the DB manually by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Add cancellation support for query methods on `sqlite` service by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Add prepared statement support to `sqlite` service with JS bindings by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Gin support by [Lea Anthony](https://github.com/leaanthony) in [PR](https://github.com/wailsapp/wails/pull/3537) based on the original work of [@AnalogJ](https://github.com/AnalogJ) in PR[https://github.com/wailsapp/wails/pull/3537]
|
||||
- Fix auto save and password auto save always enabled by [@oSethoum](https://github.com/osethoum) in [#4134](https://github.com/wailsapp/wails/pull/4134)
|
||||
- Add `SetMenu()` on window to allow for setting a menu on a window by [@leaanthony](https://github.com/leaanthony)
|
||||
- Add Notification support by [@popaprozac](https://github.com/popaprozac) in [#4098](https://github.com/wailsapp/wails/pull/4098)
|
||||
- Add File Association support for mac by [@wimaha](https://github.com/wimaha) in [#4177](https://github.com/wailsapp/wails/pull/4177)
|
||||
- Add `wails3 tool version` for semantic version bumping by [@leaanthony](https://github.com/leaanthony)
|
||||
- Add badging support for macOS and Windows by [@popaprozac](https://github.com/popaprozac) in [#](https://github.com/wailsapp/wails/pull/4234)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed nil pointer dereference in processURLRequest for Mac by [@etesam913](https://github.com/etesam913) in [#4366](https://github.com/wailsapp/wails/pull/4366)
|
||||
- Fixed a linux bug preventing filtered dialogs by [@bh90210](https://github.com/bh90210) in [#4287](https://github.com/wailsapp/wails/pull/4287)
|
||||
- Fixed Windows+Linux Edit Menu issues by [@leaanthony](https://github.com/leaanthony) in [#3f78a3a](https://github.com/wailsapp/wails/commit/3f78a3a8ce7837e8b32242c8edbbed431c68c062)
|
||||
- Updated the minimum system version in macOS .plist files from 10.13.0 to 10.15.0 by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981)
|
||||
- Window ID skip issue by [@leaanthony](https://github.com/leaanthony)
|
||||
|
|
@ -81,6 +178,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Suppressed warnings for services that define lifecycle or http methods but no other bound methods by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Fixed non-React templates failing to display Hello World footer when using light system colour scheme by [@marcus-crane](https://github.com/marcus-crane) in [#4056](https://github.com/wailsapp/wails/pull/4056)
|
||||
- Fixed hidden menu items on macOS by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fixed handling and formatting of errors in message processors by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Fixed skipped service shutdown when quitting application by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Ensure menu updates occur on the main thread by [@leaanthony](https://github.com/leaanthony)
|
||||
- The dragging and resizing mechanism is now more robust and matches expected platform behaviour more closely by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- Fixed [#4097](https://github.com/wailsapp/wails/issues/4097) Webpack/angular discards runtime init code by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- Fixed initially-hidden menu items by [@IanVS](https://github.com/IanVS) in [#4116](https://github.com/wailsapp/wails/pull/4116)
|
||||
- Fixed assetFileServer not serving `.html` files when non-extension request when `[request]` doesn't exist but `[request].html` does
|
||||
- Fixed icon generation paths by [@robin-samuel](https://github.com/robin-samuel) in [#4125](https://github.com/wailsapp/wails/pull/4125)
|
||||
- Fixed `fullscreen`, `unfullscreen`, `unminimise` and `unmaximise` events not being emitted by [@oSethoum](https://github.com/osethoum) in [#4130](https://github.com/wailsapp/wails/pull/4130)
|
||||
- Fixed NSIS Error because of incorrect prefix on default version in config by [@robin-samuel](https://github.com/robin-samuel) in [#4126](https://github.com/wailsapp/wails/pull/4126)
|
||||
- Fixed Dialogs runtime function returning escaped paths on Windows by [TheGB0077](https://github.com/TheGB0077) in [#4188](https://github.com/wailsapp/wails/pull/4188)
|
||||
- Fixed Webview2 detection path in HKCU by [@leaanthony](https://github.com/leaanthony).
|
||||
- Fixed input issue with macOS by [@leaanthony](https://github.com/leaanthony).
|
||||
- Fixed Windows icon generation task file name by [@yulesxoxo](https://github.com/yulesxoxo) in [#4219](https://github.com/wailsapp/wails/pull/4219).
|
||||
- Fixed transparency issue for frameless windows by [@leaanthony](https://github.com/leaanthony) based on work by @kron.
|
||||
- Fixed focus calls when window is disabled or minimised by [@leaanthony](https://github.com/leaanthony) based on work by @kron.
|
||||
- Fixed system trays not showing after taskbar restarts by [@leaanthony](https://github.com/leaanthony) based on work by @kron.
|
||||
- Fixed fallbackResponseWriter not implementing Flush() in [#4245](https://github.com/wailsapp/wails/pull/4245)
|
||||
- Fixed fallbackResponseWriter not implementing Flush() by [@superDingda] in [#4236](https://github.com/wailsapp/wails/issues/4236)
|
||||
- Fixed macOS window close with pending async Go-bound function call crashes by [@joshhardy](https://github.com/joshhardy) in [#4354](https://github.com/wailsapp/wails/pull/4354)
|
||||
- Fixed Windows Efficiency mode startup race condition by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fixed Windows icon handle cleanup by [@leaanthony](https://github.com/leaanthony).
|
||||
- Fixed `OpenFileManager` on Windows by [@PPTGamer](https://github.com/PPTGamer) in [#4375](https://github.com/wailsapp/wails/pull/4375).
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
@ -94,9 +214,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- In JS/TS bindings, `internal.js/ts` model files have been removed; all models can now be found in `models.js/ts` by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- In JS/TS bindings, named types are never rendered as aliases for other named types; the old behaviour is now restricted to aliases by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- In JS/TS bindings, in class mode, struct fields whose type is a type parameter are marked optional and never initialised automatically by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Remove ESLint from templates by by [@IanVS](https://github.com/IanVS) in [#4059](https://github.com/wailsapp/wails/pull/4059)
|
||||
- Update copyright date to 2025 by [@IanVS](https://github.com/IanVS) in [#4037](https://github.com/wailsapp/wails/pull/4037)
|
||||
- Add docs for event.Sender by [@IanVS](https://github.com/IanVS) in [#4075](https://github.com/wailsapp/wails/pull/4075)
|
||||
- Go 1.24 support by [@leaanthony](https://github.com/leaanthony)
|
||||
- `ServiceStartup` hooks are now invoked when `App.Run` is called, not in `application.New` by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- `ServiceStartup` errors are now returned from `App.Run` instead of terminating the process by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Binding and dialog calls from JS now reject with error objects instead of strings by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Improved systray menu positioning on Windows by [@leaanthony](https://github.com/leaanthony)
|
||||
- The JS runtime has been ported to TypeScript by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- The runtime initialises as soon as it is imported, no need to wait for the window to load by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- The runtime does not export an init method anymore. A side effects import can be used to initialise it by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- Bound methods now return a `CancellablePromise` that rejects with a `CancelError` if cancelled. The actual result of the call is discarded by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- Built-in service types are now consistently called `Service` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Built-in service creation functions with options are now consistently called `NewWithConfig` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- `Select` method on `sqlite` service is now named `Query` for consistency with Go APIs by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Templates: moved runtime to "dependencies", organized package.json files by [@IanVS](https://github.com/IanVS) in [#4133](https://github.com/wailsapp/wails/pull/4133)
|
||||
- Creates and ad-hoc signs app bundles in dev to enable certain macOS APIs by [@popaprozac](https://github.com/popaprozac) in [#4171](https://github.com/wailsapp/wails/pull/4171)
|
||||
|
||||
## v3.0.0-alpha.9 - 2025-01-13
|
||||
|
||||
|
|
@ -362,9 +496,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- 🐧 add task `run:linux` by
|
||||
[@marcus-crane](https://github.com/marcus-crane) in
|
||||
[#3146](https://github.com/wailsapp/wails/pull/3146)
|
||||
- Export `SetIcon` method by @almas1992 in
|
||||
- Export `SetIcon` method by [@almas-x](https://github.com/almas-x) in
|
||||
[PR](https://github.com/wailsapp/wails/pull/3147)
|
||||
- Improve `OnShutdown` by @almas1992 in
|
||||
- Improve `OnShutdown` by [@almas-x](https://github.com/almas-x) in
|
||||
[PR](https://github.com/wailsapp/wails/pull/3189)
|
||||
- Restore `ToggleMaximise` method in `Window` interface by
|
||||
[@fbbdev](https://github.com/fbbdev) in
|
||||
|
|
@ -435,7 +569,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Fixed bug for linux in doctor in the event user doesn't have proper drivers
|
||||
installed. Added by [@pylotlight](https://github.com/pylotlight) in
|
||||
[PR](https://github.com/wailsapp/wails/pull/3032)
|
||||
- Fix dpi scaling on start up (windows). Changed by @almas1992 in
|
||||
- Fix dpi scaling on start up (windows). Changed by [@almas-x](https://github.com/almas-x) in
|
||||
[PR](https://github.com/wailsapp/wails/pull/3145)
|
||||
- Fix replace line in `go.mod` to use relative paths. Fixes Windows paths with
|
||||
spaces - @leaanthony.
|
||||
|
|
|
|||
9
docs/src/content/docs/contributing/_category_.json
Normal file
9
docs/src/content/docs/contributing/_category_.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"label": "Technical Documentation",
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Deep dive into Wails v3 internals for developers who want to understand or contribute to the codebase."
|
||||
},
|
||||
"order": 50,
|
||||
"collapsed": false
|
||||
}
|
||||
165
docs/src/content/docs/contributing/architecture.mdx
Normal file
165
docs/src/content/docs/contributing/architecture.mdx
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
---
|
||||
title: Wails v3 Architecture
|
||||
description: Deep-dive diagrams and explanations of every moving part inside Wails v3
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import Mermaid from "../../components/Mermaid.astro";
|
||||
|
||||
Wails v3 is a **full-stack desktop framework** consisting of a Go runtime,
|
||||
a JavaScript bridge, a task-driven tool-chain and a collection of templates that
|
||||
let you ship native applications powered by modern web tech.
|
||||
|
||||
This page presents the *big picture* in four diagrams:
|
||||
|
||||
1. **Overall Architecture** – how every subsystem connects
|
||||
2. **Runtime Flow** – what happens when JS calls Go and vice-versa
|
||||
3. **Development vs Production** – two modes of the asset server
|
||||
4. **Platform Implementations** – where OS-specific code lives
|
||||
|
||||
---
|
||||
|
||||
## 1 · Overall Architecture
|
||||
|
||||
<Mermaid title="Wails v3 – High-Level Stack">
|
||||
flowchart TD
|
||||
subgraph Developer
|
||||
CLI[wails3 CLI]
|
||||
end
|
||||
subgraph Build["Build-time Tool-chain"]
|
||||
GEN["Binding Generator\n(static analysis)"]
|
||||
TMP["Template Engine"]
|
||||
ASSETDEV["Asset Server (dev)"]
|
||||
PKG["Cross-compilation & Packaging"]
|
||||
end
|
||||
subgraph Runtime["Native Runtime"]
|
||||
RT["Desktop Runtime\n(window, dialogs, tray, …)"]
|
||||
BRIDGE["Message Bridge\n(JSON channel)"]
|
||||
end
|
||||
subgraph App["Your Application"]
|
||||
BACKEND["Go Backend"]
|
||||
FRONTEND["Web Frontend\n(React/Vue/…)"]
|
||||
end
|
||||
|
||||
CLI -->|init| TMP
|
||||
CLI -->|generate| GEN
|
||||
CLI -->|dev| ASSETDEV
|
||||
CLI -->|build| PKG
|
||||
|
||||
GEN -->|Go & TS stubs| BACKEND
|
||||
GEN -->|bindings.json| FRONTEND
|
||||
|
||||
ASSETDEV <-->|HTTP| FRONTEND
|
||||
|
||||
BACKEND <--> BRIDGE <--> FRONTEND
|
||||
BRIDGE <--> RT
|
||||
RT <-->|serve assets| ASSETDEV
|
||||
</Mermaid>
|
||||
|
||||
---
|
||||
|
||||
## 2 · Runtime Call Flow
|
||||
|
||||
<Mermaid title="Runtime – JavaScript ⇄ Go Calling Path">
|
||||
sequenceDiagram
|
||||
participant JS as JavaScript (frontend)
|
||||
participant Bridge as Bridge (WebView callback)
|
||||
participant MP as Message Processor (Go)
|
||||
participant Go as Bound Go Function
|
||||
|
||||
JS->>Bridge: invoke("Greet","Alice")
|
||||
Bridge->>MP: JSON {t:c,id:42,...}
|
||||
MP->>Go: call Greet("Alice")
|
||||
Go-->>MP: "Hello Alice"
|
||||
MP-->>Bridge: JSON {t:r,id:42,result:"Hello Alice"}
|
||||
Bridge-->>JS: Promise.resolve("Hello Alice")
|
||||
</Mermaid>
|
||||
|
||||
Key points:
|
||||
|
||||
* **No HTTP / IPC** – the bridge uses the native WebView’s in-memory channel
|
||||
* **Method IDs** – deterministic FNV-hash enables O(1) lookup in Go
|
||||
* **Promises** – errors propagate as rejections with stack & code
|
||||
|
||||
---
|
||||
|
||||
## 3 · Development vs Production Asset Flow
|
||||
|
||||
<Mermaid title="Dev ↔ Prod Asset Server">
|
||||
flowchart LR
|
||||
subgraph Dev["`wails3 dev`"]
|
||||
VITE["Framework Dev Server\n(port 5173)"]
|
||||
ASDEV["Asset Server (dev)\n(proxy + disk)"]
|
||||
FRONTENDDEV[Browser]
|
||||
end
|
||||
subgraph Prod["`wails3 build`"]
|
||||
EMBED["Embedded FS\n(go:embed)"]
|
||||
ASPROD["Asset Server (prod)\n(read-only)"]
|
||||
FRONTENDPROD[WebView Window]
|
||||
end
|
||||
|
||||
VITE <-->|proxy / HMR| ASDEV
|
||||
ASDEV <-->|http| FRONTENDDEV
|
||||
|
||||
EMBED --> ASPROD
|
||||
ASPROD <-->|in-memory| FRONTENDPROD
|
||||
</Mermaid>
|
||||
|
||||
* In **dev** the server proxies unknown paths to the framework’s live-reload
|
||||
server and serves static assets from disk.
|
||||
* In **prod** the same API is backed by `go:embed`, producing a zero-dependency
|
||||
binary.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Platform-Specific Runtime Split
|
||||
|
||||
<Mermaid title="Per-OS Runtime Files">
|
||||
classDiagram
|
||||
class runtime::Window {
|
||||
+Show()
|
||||
+Hide()
|
||||
+Center()
|
||||
}
|
||||
|
||||
runtime::Window <|-- Window_darwin
|
||||
runtime::Window <|-- Window_linux
|
||||
runtime::Window <|-- Window_windows
|
||||
|
||||
class Window_darwin {
|
||||
//go:build darwin
|
||||
+NSWindow* ptr
|
||||
}
|
||||
class Window_linux {
|
||||
//go:build linux
|
||||
+GtkWindow* ptr
|
||||
}
|
||||
class Window_windows {
|
||||
//go:build windows
|
||||
+HWND ptr
|
||||
}
|
||||
|
||||
note for runtime::Window "Shared interface\nin pkg/application"
|
||||
note for Window_darwin "Objective-C (Cgo)"
|
||||
note for Window_linux "Pure Go GTK calls"
|
||||
note for Window_windows "Win32 API via syscall"
|
||||
</Mermaid>
|
||||
|
||||
Every feature follows this pattern:
|
||||
|
||||
1. **Common interface** in `pkg/application`
|
||||
2. **Message processor** entry in `pkg/application/messageprocessor_*.go`
|
||||
3. **Implementation** per OS under `internal/runtime/*.go` guarded by build tags
|
||||
|
||||
Missing functionality on an OS should return `ErrCapability` and register
|
||||
availability via `internal/capabilities`.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
These diagrams outline **where the code lives**, **how data moves**, and
|
||||
**which layers own which responsibilities**.
|
||||
Keep them handy while exploring the detailed pages that follow – they are your
|
||||
map to the Wails v3 source tree.
|
||||
203
docs/src/content/docs/contributing/asset-server.mdx
Normal file
203
docs/src/content/docs/contributing/asset-server.mdx
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
---
|
||||
title: Asset Server
|
||||
description: How Wails v3 serves and embeds your web assets in development and production
|
||||
sidebar:
|
||||
order: 4
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Every Wails application ships a **single native executable** that combines:
|
||||
|
||||
1. Your *Go* backend
|
||||
2. A *Web* frontend (HTML + JS + CSS)
|
||||
|
||||
The **Asset Server** is the glue that makes this possible.
|
||||
It has **two operating modes** that are selected at compile-time via Go build
|
||||
tags:
|
||||
|
||||
| Mode | Tag | Purpose |
|
||||
|------|-----|---------|
|
||||
| **Development** | `//go:build dev` | Fast iteration with hot-reload |
|
||||
| **Production** | `//go:build !dev` | Zero-dependency, embedded assets |
|
||||
|
||||
The implementation lives in
|
||||
`v3/internal/assetserver/` with clear file splits:
|
||||
|
||||
```
|
||||
assetserver_dev.go # ⬅️ runtime dev server
|
||||
assetserver_production.go # ⬅️ embedded server
|
||||
assetserver_darwin.go # OS-specific helpers (same for linux/windows)
|
||||
asset_fileserver.go # Shared static file logic
|
||||
content_type_sniffer.go # MIME type detection
|
||||
ringqueue.go # Tiny LRU for mime cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development Mode
|
||||
|
||||
### Lifecycle
|
||||
|
||||
1. `wails3 dev` boots and **spawns your frontend dev server**
|
||||
(Vite, SvelteKit, React-SWC …) by running the task defined in
|
||||
`frontend/Taskfile.yml` (usually `npm run dev`).
|
||||
2. Wails starts the **Dev Asset Server** listening on `localhost:<random>` and
|
||||
tells the Go runtime to load `http://<host>:<port>` as the window URL.
|
||||
3. Incoming requests are handled by `assetserver_dev.go`:
|
||||
|
||||
```
|
||||
┌─────────┐ /runtime/... ┌─────────────┐
|
||||
│ Browser │ ── native bridge ───▶ │ Runtime │
|
||||
├─────────┤ └─────────────┘
|
||||
│ JS │ / (index.html) proxy / -> Vite
|
||||
└─────────┘ ◀─────────────┐
|
||||
AssetServer │
|
||||
▼
|
||||
┌────────────┐
|
||||
│ Vite Dev │
|
||||
│ Server │
|
||||
└────────────┘
|
||||
```
|
||||
|
||||
4. Static files (`/assets/logo.svg`) are **served directly from disk** via
|
||||
`asset_fileserver.go` (for speed) while anything unknown is **proxied** to
|
||||
the framework dev server, giving you *instant* hot-module replacement.
|
||||
|
||||
### Features
|
||||
|
||||
* **Live Reload** – Vite/Snowpack/… injects HMR WebSocket; Wails only has to
|
||||
proxy it.
|
||||
* **Source Map Support** – because assets are not bundled, your browser devtools
|
||||
map errors back to original source.
|
||||
* **No Go Re-compile** – Only the frontend rebuilds; Go code stays running until
|
||||
you change `.go` files.
|
||||
|
||||
### Switching Frameworks
|
||||
|
||||
The dev proxy is **framework-agnostic**:
|
||||
|
||||
* The `wails.json` template injects two env vars:
|
||||
`FRONTEND_DEV_HOST` & `FRONTEND_DEV_PORT`.
|
||||
* Taskfiles for each template emit those vars before running their dev servers.
|
||||
* `assetserver_dev.go` simply proxies to that target.
|
||||
|
||||
Add a new template → define its dev task → Asset Server just works.
|
||||
|
||||
---
|
||||
|
||||
## Production Mode
|
||||
|
||||
When you run `wails3 build` the pipeline:
|
||||
|
||||
1. Runs the frontend **production build** (`npm run build`) producing
|
||||
`/frontend/dist/**`.
|
||||
2. **Embeds** that folder into `go:embed` FS at compile time (see
|
||||
`bundled_assetserver.go` generated file).
|
||||
3. Compiles the Go binary with `-tags production` (implicit).
|
||||
|
||||
### Request Handling
|
||||
|
||||
```go
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// 1. Try embedded static assets (exact path)
|
||||
// 2. Fallback to index.html for SPA routing
|
||||
// 3. Sniff content-type if extension unknown
|
||||
// 4. Set strong cache headers
|
||||
}
|
||||
```
|
||||
|
||||
* **MIME Detection** – If the build tool produced extension-less files (e.g.
|
||||
`/assets/manifest`) `content_type_sniffer.go` inspects the first 512 bytes and
|
||||
caches the result in a tiny lock-free LRU.
|
||||
* **Ring Queue Caching** – Frequently accessed assets (logo, CSS) are kept
|
||||
in-memory for the lifetime of the app, removing the need for disk or embed FS
|
||||
lookups.
|
||||
* **Security Headers** – Disallows `file://` navigation, enables `nosniff`.
|
||||
|
||||
Because everything is embedded, the shipped binary has **no external
|
||||
dependencies** (even on Windows).
|
||||
|
||||
---
|
||||
|
||||
## Bridging Dev ↔ Prod
|
||||
|
||||
Both modes expose the **same public interface**:
|
||||
|
||||
```go
|
||||
type AssetServer interface {
|
||||
URL() string // dev: http://localhost:34115, prod: wails://app
|
||||
Open() error // start listening
|
||||
Close() // graceful shutdown
|
||||
}
|
||||
```
|
||||
|
||||
`pkg/application` happily uses whichever implementation was compiled in, meaning
|
||||
**your application code does not change** between `dev` and `build`.
|
||||
|
||||
---
|
||||
|
||||
## How Frontend Frameworks Integrate
|
||||
|
||||
### Templates
|
||||
|
||||
Each official template (React, Vue, Svelte, Solid…) contains:
|
||||
|
||||
* `frontend/Taskfile.yml`
|
||||
* `frontend/vite.config.ts` (or equivalent)
|
||||
|
||||
They export two tasks:
|
||||
|
||||
| Task | Purpose |
|
||||
|------|---------|
|
||||
| `dev` | Starts the framework dev server on a **random free port** and prints it to stdout (`PORT=5173`). |
|
||||
| `build` | Produces static assets into `dist/` + manifest for cache-busting. |
|
||||
|
||||
`internal/commands/dev.go` parses that stdout, sets `FRONTEND_DEV_*` env vars
|
||||
and launches the **Dev Asset Server**.
|
||||
|
||||
Frameworks remain fully decoupled from Go:
|
||||
|
||||
* No need to import Wails JS SDK at build time – the runtime injects it at
|
||||
window creation.
|
||||
* Any framework with an HTTP dev server can plug in.
|
||||
|
||||
---
|
||||
|
||||
## Extending / Customising
|
||||
|
||||
Need custom headers, auth, or gzip?
|
||||
|
||||
1. Implement `type Middleware func(http.Handler) http.Handler`
|
||||
2. Register via `internal/assetserver/options.go`
|
||||
3. For prod, remember to add the same middleware in `assetserver_production.go`.
|
||||
|
||||
---
|
||||
|
||||
## Key Source Files
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `assetserver_dev.go` | Reverse proxy + disk file server |
|
||||
| `assetserver_production.go` | Embedded FS handler |
|
||||
| `options.go` | Config struct parsed from `pkg/options/assetserver` |
|
||||
| `build_dev.go` / `build_production.go` | Build-tag wrappers selecting correct implementation |
|
||||
| `bundled_assetserver.go` | Generated embed data (only present in production builds) |
|
||||
|
||||
---
|
||||
|
||||
## Gotchas & Debugging
|
||||
|
||||
* **White Screen in Prod** – usually SPA routing: ensure `History API Fallback`
|
||||
is enabled in dev and `index.html` fallback works in prod.
|
||||
* **404 in Dev** – mis-matched `FRONTEND_DEV_PORT`; run with
|
||||
`WAILSDEV_VERBOSE=1` to print every proxied request.
|
||||
* **Large Assets** – they are embedded; consider
|
||||
[`assetserver.WithExternalDir("/path")`](https://pkg.go.dev) to load from disk.
|
||||
|
||||
---
|
||||
|
||||
You now know how the Wails **Asset Server** feeds your web code to the native
|
||||
window in both **development** and **production**.
|
||||
Master this layer and you can debug loading issues, add middlewares, or even
|
||||
swap in a completely different frontend tool-chain with confidence.
|
||||
240
docs/src/content/docs/contributing/binding-system.mdx
Normal file
240
docs/src/content/docs/contributing/binding-system.mdx
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
---
|
||||
title: Binding System
|
||||
description: How Wails v3 lets Go and JavaScript call each other with zero boilerplate
|
||||
sidebar:
|
||||
order: 5
|
||||
---
|
||||
|
||||
> “Bindings” are the **type-safe contract** that lets you write:
|
||||
|
||||
```go
|
||||
msg, err := chatService.Send("Hello")
|
||||
```
|
||||
|
||||
in Go *and*
|
||||
|
||||
```ts
|
||||
const msg = await window.backend.ChatService.Send("Hello")
|
||||
```
|
||||
|
||||
in TypeScript **without manual glue code**.
|
||||
This document breaks down *how* that magic happens, from **static analysis** at
|
||||
build time, through **code generation**, to the **runtime bridge** that moves
|
||||
bytes over the WebView.
|
||||
|
||||
---
|
||||
|
||||
## 1. 30-Second Overview
|
||||
|
||||
| Stage | Component | Output |
|
||||
|-------|-----------|--------|
|
||||
| **Analysis** | `internal/generator/analyse.go` | In-memory AST of exported Go structs, methods, params, return types |
|
||||
| **Generation** | `internal/generator/render/*.tmpl` | • `pkg/application/bindings.go` (Go)<br>• `frontend/src/wailsjs/**.ts` (TS defs)<br>• `runtime/desktop/@wailsio/runtime/internal/bindings.json` |
|
||||
| **Runtime** | `pkg/application/messageprocessor_call.go` + JS runtime (`invoke.ts`) | JSON messages over WebView native bridge |
|
||||
|
||||
The flow is orchestrated by the `wails3` CLI:
|
||||
|
||||
```
|
||||
wails3 generate ─┬─> generator.Collect() // parse Go packages
|
||||
├─> generator.Analyse() // build semantic model
|
||||
└─> generator.Render() // write files + `go fmt`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Static Analysis
|
||||
|
||||
### Entry Point
|
||||
|
||||
```
|
||||
internal/generator/analyse.go
|
||||
```
|
||||
|
||||
`Analyse(cfg generator.Config)`:
|
||||
|
||||
1. Recursively loads provided Go packages with `go/packages`.
|
||||
2. Walks the *type* and *value* specs of every AST file.
|
||||
3. Filters **exported** symbols (capitalised) outside `internal/` packages.
|
||||
4. Records:
|
||||
* Receiver type (`struct`, `interface`)
|
||||
* Method name
|
||||
* Parameter list (name, type, pointer, variadic)
|
||||
* Return tuple (values + error presence)
|
||||
5. Normalises types to a **serialisable set**
|
||||
(`int`, `float64`, `string`, `[]byte`, slices, maps, structs, pointers).
|
||||
|
||||
Unsupported types produce a **compile-time error** so mistakes are caught early.
|
||||
|
||||
### Model
|
||||
|
||||
```go
|
||||
type Method struct {
|
||||
ID uint32 // stable hash for runtime lookup
|
||||
Name string
|
||||
Params []Param
|
||||
Results []Result
|
||||
Receiver *Struct // nil for package funcs
|
||||
}
|
||||
```
|
||||
|
||||
A deterministic **FNV-1a** hash (`internal/hash/fnv.go`) of
|
||||
`<Receiver>.<Name>(<Signature>)` becomes the *method ID* used at runtime.
|
||||
|
||||
---
|
||||
|
||||
## 3. Code Generation
|
||||
|
||||
### Templates
|
||||
|
||||
`internal/generator/render/` contains text/template files:
|
||||
|
||||
| Template | Target |
|
||||
|----------|--------|
|
||||
| `go_bindings.tmpl` | `pkg/application/bindings.go` |
|
||||
| `ts_bindings.tmpl` | `frontend/wailsjs/go/models.d.ts` |
|
||||
| `ts_index.tmpl` | `frontend/wailsjs/index.ts` |
|
||||
|
||||
Add or adjust templates here to customise generation.
|
||||
|
||||
### Go Output
|
||||
|
||||
`bindings.go` registers a lookup table:
|
||||
|
||||
```go
|
||||
var bindings = []application.BoundMethod{
|
||||
{ID: 0x7A1201D3, Name: "ChatService.Send", Call: chatServiceSend},
|
||||
}
|
||||
|
||||
func chatServiceSend(ctx context.Context, in []byte) ([]byte, error) {
|
||||
var req struct{ Msg string }
|
||||
if err := json.Unmarshal(in, &req); err != nil { return nil, err }
|
||||
res, err := chatService.Send(req.Msg)
|
||||
return json.Marshal(res), err
|
||||
}
|
||||
```
|
||||
|
||||
Key points:
|
||||
|
||||
* **Zero reflection** at runtime → performance.
|
||||
* Marshal/Unmarshal is **per-method** with generated struct wrappers.
|
||||
|
||||
### TypeScript Output
|
||||
|
||||
```ts
|
||||
export namespace backend {
|
||||
export namespace ChatService {
|
||||
function Send(msg: string): Promise<string>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* Emitted as **ES modules** so any bundler can tree-shake.
|
||||
* Method IDs are embedded in an auto-generated `bindings.json` for the JS
|
||||
runtime.
|
||||
|
||||
---
|
||||
|
||||
## 4. Runtime Invocation Protocol
|
||||
|
||||
### JavaScript Side
|
||||
|
||||
```ts
|
||||
import { invoke } from "@wailsio/runtime";
|
||||
|
||||
await invoke(0x7a1201d3 /* ChatService.Send */, ["Hello"]);
|
||||
```
|
||||
|
||||
Implementation: `runtime/desktop/@wailsio/runtime/invoke.ts`
|
||||
|
||||
1. Packs `{t:"c", id:<methodID>, p:[...args]}` into JSON.
|
||||
2. Calls `window.external.invoke(payload)` (WebView2) or equivalent.
|
||||
3. Returns a `Promise` that resolves/rejects based on the reply.
|
||||
|
||||
### Go Side
|
||||
|
||||
1. `messageprocessor_call.go` receives the JSON.
|
||||
2. Looks up `methodID` in the `bindings` slice.
|
||||
3. Executes the generated stub (`chatServiceSend`).
|
||||
4. Serialises `{result, error}` back to JS.
|
||||
|
||||
### Error Mapping
|
||||
|
||||
| Go | JavaScript |
|
||||
|----|------------|
|
||||
| `error == nil` | `Promise` resolves with result |
|
||||
| `errors.New(...)` | `Promise` rejects with `{message, stack, code}` |
|
||||
|
||||
The mapping code lives in `runtime/desktop/@wailsio/runtime/errors.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Calling JavaScript from Go
|
||||
|
||||
*Browser → Go* is covered above.
|
||||
*Go → Browser* uses **Events** or **Eval**:
|
||||
|
||||
```go
|
||||
window.Eval(`alert("Hi")`)
|
||||
app.Publish("chat:new-message", msg)
|
||||
```
|
||||
|
||||
Binding generation is one-way (Go methods).
|
||||
For JS-exposed functions use `runtime.EventsOn` in JS and `application.Publish`
|
||||
from Go.
|
||||
|
||||
---
|
||||
|
||||
## 6. Extending & Troubleshooting
|
||||
|
||||
### Adding Custom Serialisers
|
||||
|
||||
* Implement `generator.TypeConverter` interface.
|
||||
* Register in `generator.Config.Converters`.
|
||||
* Update JS runtime deserialisation if needed.
|
||||
|
||||
### Unsupported Type Error
|
||||
|
||||
```
|
||||
error: field "Client" uses unsupported type: chan struct{}
|
||||
```
|
||||
|
||||
→ wrap the channel in a struct with an exposed API or redesign.
|
||||
|
||||
### Version Skew
|
||||
|
||||
Bindings are regenerated on **every** `wails3 dev` / `wails3 build`.
|
||||
If IDE intellisense shows stale stubs, delete `frontend/wailsjs` and rebuild.
|
||||
|
||||
### Performance Tips
|
||||
|
||||
* Prefer **value** receivers for small structs to reduce allocations.
|
||||
* Avoid large byte slices over the bridge; use `application.FileServer` instead.
|
||||
* Batch multiple quick calls into one method to minimise bridge latency.
|
||||
|
||||
---
|
||||
|
||||
## 7. Key Files Map
|
||||
|
||||
| Concern | File |
|
||||
|---------|------|
|
||||
| Static analysis entry | `internal/generator/analyse.go` |
|
||||
| Render pipeline | `internal/generator/generate.go` |
|
||||
| Template assets | `internal/generator/render/*.tmpl` |
|
||||
| Go binding table | `pkg/application/bindings.go` (generated) |
|
||||
| Call processor | `pkg/application/messageprocessor_call.go` |
|
||||
| JS runtime | `runtime/desktop/@wailsio/runtime/invoke.ts` |
|
||||
| Errors mapping | `runtime/desktop/@wailsio/runtime/errors.ts` |
|
||||
|
||||
Keep this cheat-sheet handy when you trace a bridge bug.
|
||||
|
||||
---
|
||||
|
||||
## 8. Recap
|
||||
|
||||
1. **Generator** scans your Go code → semantic model.
|
||||
2. **Templates** emit **Go stubs** + **TypeScript definitions**.
|
||||
3. **Message Processor** executes stubs at runtime.
|
||||
4. **JS Runtime** wraps it all in idiomatic promises.
|
||||
|
||||
All without reflection, without IPC servers, and without you writing a single
|
||||
line of boilerplate. That’s the Wails v3 binding system. Go forth and bind!
|
||||
211
docs/src/content/docs/contributing/build-packaging.mdx
Normal file
211
docs/src/content/docs/contributing/build-packaging.mdx
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
---
|
||||
title: Build & Packaging Pipeline
|
||||
description: What happens under the hood when you run `wails3 build`, how cross-platform binaries are produced, and how installers are generated for each OS.
|
||||
sidebar:
|
||||
order: 6
|
||||
---
|
||||
|
||||
`wails3 build` is a **single command** that drives a _multi-stage_ pipeline:
|
||||
|
||||
1. **Frontend production build** (Vite / React / …)
|
||||
2. **Asset embedding** into Go sources
|
||||
3. **Native compilation** for the target OS/arch
|
||||
4. **Post-processing** (icon injection, version info, codesign)
|
||||
5. **Packaging** into a distributable artefact (AppImage, DMG, MSI, …)
|
||||
|
||||
This document follows every stage, shows where the code lives, and explains how
|
||||
to customise or debug it.
|
||||
|
||||
---
|
||||
|
||||
## 1. Entry Point: `internal/commands/build-assets.go`
|
||||
|
||||
```
|
||||
wails3 build # cmd/wails3/main.go
|
||||
└─ internal/commands.Build() # build-assets.go
|
||||
```
|
||||
|
||||
High-level tasks are delegated to **Taskfile** targets so the same logic runs in
|
||||
CI or locally.
|
||||
|
||||
| Stage | Taskfile target | Go implementation |
|
||||
|-------|-----------------|-------------------|
|
||||
| Frontend build | `frontend:build` | `internal/commands/task.go` |
|
||||
| Embed assets | `embed:assets` | generated `bundled_assetserver.go` |
|
||||
| Go compile | `build:go` | `tool_buildinfo.go`, `tool_package.go` |
|
||||
| Package | `package:*` | `internal/packager`, `internal/commands/*` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Frontend Production Build
|
||||
|
||||
1. The CLI changes into `frontend/` and executes the `build` task found in the
|
||||
project `Taskfile.yml` (`npm run build` by default).
|
||||
2. Output is written to `frontend/dist/`.
|
||||
3. A **content hash** manifest is produced (`manifest.json`) so cache-busting
|
||||
works out of the box.
|
||||
|
||||
If the task fails the pipeline aborts early, returning the exit code of your
|
||||
build tool.
|
||||
|
||||
---
|
||||
|
||||
## 3. Embedding Assets
|
||||
|
||||
Implemented in `internal/assetserver/build_production.go`:
|
||||
|
||||
* Walks `frontend/dist` and generates a `go:embed` file
|
||||
`bundled_assetserver.go` (ignored by git).
|
||||
* Adds the `production` build tag.
|
||||
|
||||
Result: the final binary contains every HTML/JS/CSS byte, so the executable is
|
||||
self-contained.
|
||||
|
||||
---
|
||||
|
||||
## 4. Native Compilation
|
||||
|
||||
### Build Flags
|
||||
|
||||
| Flag | Purpose |
|
||||
|------|---------|
|
||||
| `-tags production` | Select prod asset server & runtime stubs |
|
||||
| `-ldflags "-s -w"` | Strip symbol table & DWARF (smaller binaries) |
|
||||
| `-X internal/buildinfo.BuildTime=$(date)` | Embed reproducible metadata |
|
||||
|
||||
`internal/commands/tool_buildinfo.go` collects version, git commit, and build
|
||||
time then injects them using `go build -ldflags`.
|
||||
|
||||
### Cross Compilation Matrix
|
||||
|
||||
| OS | Arch | Build Tag | CGO | Notes |
|
||||
|----|------|-----------|-----|-------|
|
||||
| Windows | amd64 / arm64 | `windows` | cgo enabled for WebView2 | Generates `.syso` via `internal/commands/syso.go` |
|
||||
| macOS | amd64 / arm64 | `darwin` | cgo enabled (Objective-C) | Codesign & notarisation tasks available |
|
||||
| Linux | amd64 / arm64 | `linux` | pure Go (webkit option) | GTK/WebKitGTK headers required for CGO build |
|
||||
|
||||
`wails3 build -platform darwin/arm64` overrides GOOS/GOARCH.
|
||||
If CGO is needed on the host that can't compile the target (e.g. building
|
||||
Windows from Linux), the CLI falls back to **Docker** images (`ghcr.io/wailsapp/cross-compiler`).
|
||||
|
||||
---
|
||||
|
||||
## 5. Post-Processing
|
||||
|
||||
### Icons & Resources
|
||||
|
||||
* **Windows** – `syso.go` merges your PNG/ICO into a `.syso` that `go build`
|
||||
links automatically. Also writes `manifest.xml` enabling High-DPI support.
|
||||
* **macOS** – `icons.go` turns `icon.png` → `.icns`, injects into
|
||||
`MyApp.app/Contents/Resources`.
|
||||
* **Linux** – `.desktop` files are generated in `/usr/share/applications`
|
||||
by each packager backend.
|
||||
|
||||
### Code Signing (Optional)
|
||||
|
||||
* macOS: `codesign` + `xcrun altool --notarize-app`
|
||||
* Windows: `signtool.exe`
|
||||
* Linux: Not common (repository GPG handled externally)
|
||||
|
||||
Task targets: `sign:mac`, `sign:windows`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Packaging Back-Ends
|
||||
|
||||
### Linux
|
||||
|
||||
| Artefact | Code Path | Tooling |
|
||||
|----------|-----------|---------|
|
||||
| **AppImage** (default) | `internal/commands/appimage.go` | `linuxdeploy` + `linuxdeploy-plugin-gtk` |
|
||||
| **deb / rpm / pacman** | `internal/packager` | `fpm` (invoked via Go) |
|
||||
|
||||
Flags:
|
||||
|
||||
```
|
||||
wails3 build -package deb # produces myapp_1.0.0_amd64.deb
|
||||
wails3 build -package rpm
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
* **DMG** – `internal/commands/packager.go` calls `appdmg` to generate a
|
||||
drag-&-drop installer.
|
||||
* **ZIP** – fallback if `appdmg` is missing.
|
||||
* Sets CFBundle identifiers and version plist automatically.
|
||||
|
||||
### Windows
|
||||
|
||||
* **MSI** – `internal/commands/packager.go` wraps **WiX** candle & light tools.
|
||||
Heat autogenerates the component tree from the built `.exe`.
|
||||
|
||||
Extra resources:
|
||||
|
||||
* `internal/commands/windows_resources/` contains templated **.wxs** fragments.
|
||||
* Version info injected via `rsrc` utility.
|
||||
|
||||
---
|
||||
|
||||
## 7. Build Artefact Layout
|
||||
|
||||
After a successful build the CLI prints:
|
||||
|
||||
```
|
||||
build/bin/
|
||||
├── myapp-linux-amd64
|
||||
└── myapp-linux-amd64.AppImage
|
||||
|
||||
build/package/
|
||||
└── myapp_1.0.0_amd64.deb
|
||||
```
|
||||
|
||||
The exact files depend on `-platform` and `-package` flags.
|
||||
|
||||
---
|
||||
|
||||
## 8. Customising the Pipeline
|
||||
|
||||
| Need | Approach |
|
||||
|------|----------|
|
||||
| Run a linter before build | Add a `lint` task in **Taskfile.yml** and make `build` depend on it. |
|
||||
| Extra linker flags | `wails3 build -ldflags "-H windowsgui"` |
|
||||
| Skip packaging | `wails3 build -skip-package` (keeps raw binary). |
|
||||
| Bring your own packager | Implement `internal/packager/<name>.go`, register in `packager.go`. |
|
||||
|
||||
All Taskfile targets use environment variables exported by the CLI, so you can
|
||||
reference values like `$WAILS_VERSION` or `$WAILS_PLATFORM` inside custom tasks.
|
||||
|
||||
---
|
||||
|
||||
## 9. Troubleshooting
|
||||
|
||||
| Symptom | Likely Cause | Fix |
|
||||
|---------|--------------|-----|
|
||||
| **`ld: framework not found WebKit` (mac)** | Xcode CLI tools missing | `xcode-select --install` |
|
||||
| **Blank window in prod build** | Frontend build failed or SPA routing | Check `frontend/dist/index.html` exists and `History API Fallback` is set. |
|
||||
| **`wixl` not found** (Linux MSI cross-build) | WiX toolset not installed in Docker image | Use `--docker` build or install WiX manually. |
|
||||
| **Duplicated symbol `_WinMain`** | Mixed `windowsgui` flag and syso | Remove custom `-ldflags` or let Wails manage resources. |
|
||||
|
||||
Verbose mode: `wails3 build -verbose` dumps every command executed.
|
||||
|
||||
---
|
||||
|
||||
## 10. Key Source Map
|
||||
|
||||
| Concern | File |
|
||||
|---------|------|
|
||||
| Task runner glue | `internal/commands/task_wrapper.go` |
|
||||
| Build dispatcher | `internal/commands/build-assets.go` |
|
||||
| AppImage builder | `internal/commands/appimage.go` |
|
||||
| Generic packager interface | `internal/packager/packager.go` |
|
||||
| Windows resource generator | `internal/commands/syso.go` |
|
||||
| Build info injector | `internal/commands/tool_buildinfo.go` |
|
||||
| Version constants | `internal/version/version.go` |
|
||||
|
||||
Keep this table handy when you trace a build failure.
|
||||
|
||||
---
|
||||
|
||||
You now have the full picture from **source code** to **installer**. Armed with
|
||||
this knowledge you can tweak the pipeline, add new packaging targets, or debug
|
||||
cross-compilation issues without fear. Happy shipping!
|
||||
210
docs/src/content/docs/contributing/codebase-layout.mdx
Normal file
210
docs/src/content/docs/contributing/codebase-layout.mdx
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
---
|
||||
title: Codebase Layout
|
||||
description: How the Wails v3 repository is organised and how the pieces fit together
|
||||
sidebar:
|
||||
order: 2
|
||||
---
|
||||
|
||||
Wails v3 lives in a **monorepo** that contains the framework runtime, CLI,
|
||||
examples, documentation, and build tool-chain.
|
||||
This page walks through the *directory structure* that matters to anyone digging
|
||||
into the internals.
|
||||
|
||||
## Top-Level View
|
||||
|
||||
```
|
||||
wails/
|
||||
├── v3/ # ⬅️ Everything specific to Wails v3 lives here
|
||||
├── v2/ # Legacy v2 implementation (can be ignored for v3 work)
|
||||
├── docs/ # Astro-powered v3 docs site (this page!)
|
||||
├── website/ # Docusaurus v2 site and marketing pages (main site)
|
||||
├── scripts/ # Misc helper scripts (e.g. sponsor image generator)
|
||||
├── mkdocs-website/ # Deprecated v3 docs prototype
|
||||
└── *.md # Project-wide meta files (CHANGELOG, LICENSE, …)
|
||||
```
|
||||
|
||||
From here on, we zoom into the **`v3/`** tree.
|
||||
|
||||
## `v3/` Root
|
||||
|
||||
```
|
||||
v3/
|
||||
├── cmd/ # Compilable commands (currently only wails3 CLI)
|
||||
├── examples/ # Hands-on sample apps testing every feature
|
||||
├── internal/ # Framework implementation (not public API)
|
||||
├── pkg/ # Public Go packages ‑ the API surface
|
||||
├── tasks/ # Taskfile-based release utilities
|
||||
├── templates/ # RFC-style proposals (WEP) + common assets
|
||||
├── go.mod
|
||||
└── go.sum
|
||||
```
|
||||
|
||||
### Mental Model
|
||||
|
||||
1. **`pkg/`** exposes *what application developers import*
|
||||
2. **`internal/`** contains *how the magic is implemented*
|
||||
3. **`cmd/wails3`** drives *project lifecycle & builds*
|
||||
4. **`examples/`** double as *living tests* and *reference code*
|
||||
|
||||
Everything else supports those three pillars.
|
||||
|
||||
---
|
||||
|
||||
## `cmd/` – Commands
|
||||
|
||||
| Path | Notes |
|
||||
|------|-------|
|
||||
| `v3/cmd/wails3` | The **CLI entrypoint**. A tiny `main.go` delegates all logic to packages in `internal/commands`. |
|
||||
| `internal/commands/*` | Sub-commands (init, dev, build, doctor, …). Each lives in its own file for easy discoverability. |
|
||||
| `internal/commands/task_wrapper.go` | Bridges between CLI flags and the Taskfile build pipeline. |
|
||||
|
||||
The CLI owns:
|
||||
|
||||
* **Project scaffolding** (`init`, template generation)
|
||||
* **Dev server orchestration** (`dev`, live-reload)
|
||||
* **Production builds & packaging** (`build`, `package`, platform wrappers)
|
||||
* **Diagnostics** (`doctor`)
|
||||
|
||||
---
|
||||
|
||||
## `internal/` – The Engine Room
|
||||
|
||||
```
|
||||
internal/
|
||||
├── assetserver/ # Serving & embedding web assets
|
||||
├── buildinfo/ # Reproducible build metadata
|
||||
├── commands/ # CLI mechanics (see above)
|
||||
├── runtime/ # Desktop runtime (per-platform)
|
||||
├── generator/ # Static analysis & binding generator
|
||||
├── templates/ # Project templates (frontend stacks)
|
||||
├── packager/ # Linux AppImage/deb/rpm, Windows MSI, macOS DMG
|
||||
├── capabilities/ # Host OS capability probing
|
||||
├── dbus/ # Linux system tray integration
|
||||
├── service/ # IPC micro-services framework
|
||||
└── ... # [other helper sub-packages]
|
||||
```
|
||||
|
||||
### Key Sub-Packages
|
||||
|
||||
| Package | Responsibility | Where It Connects |
|
||||
|---------|----------------|-------------------|
|
||||
| `runtime` | Window/event loop, clipboard, dialogs, system tray. One file per OS with build-tags (`*_darwin.go`, `*_linux.go`, …). | Called by `pkg/application` and message processor. |
|
||||
| `assetserver` | Dual-mode file server:<br>• Dev: serves from disk & proxies Vite<br>• Prod: embeds assets via `go:embed` | Initialized by `pkg/application` during startup. |
|
||||
| `generator` | Parses Go source to build **binding metadata** which later produces TypeScript stub files and Go marshal/unmarshal code. | Triggered by `wails3 init` / `wails3 generate`. |
|
||||
| `packager` | Wraps platform-specific packaging CLIs (eg. `electron-builder` equivalents) into Go for cross-platform automation. | Invoked by `wails3 package`. |
|
||||
|
||||
Supporting utilities (eg. `s/`, `hash/`, `flags/`) keep internal concerns decoupled.
|
||||
|
||||
---
|
||||
|
||||
## `pkg/` – Public API
|
||||
|
||||
```
|
||||
pkg/
|
||||
├── application/ # Core API: windows, menus, dialogs, events, …
|
||||
├── runtime/ # JS-side helpers (bridge, events, screen, ...)
|
||||
├── options/ # Strongly-typed configuration structs
|
||||
├── menu/ # Declarative menu DSL
|
||||
└── ... # Platform helpers (mac/, windows/, assetserver/, …)
|
||||
```
|
||||
|
||||
`pkg/application` bootstraps a Wails program:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "MyApp",
|
||||
Assets: application.NewAssetsFS(assetsFS),
|
||||
})
|
||||
window := app.NewWebviewWindow(&application.WebviewWindowOptions{
|
||||
Title: "Hello",
|
||||
Width: 1024,
|
||||
Height: 768,
|
||||
})
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
Under the hood it:
|
||||
|
||||
1. Spins up `internal.runtime.*`
|
||||
2. Sets up an `assetserver`
|
||||
3. Registers bindings generated by `internal.generator`
|
||||
4. Enters the OS main thread
|
||||
|
||||
---
|
||||
|
||||
## `examples/` – Living Specs
|
||||
|
||||
Each sub-folder is a **self-contained application** exercising one feature.
|
||||
Patterns you’ll see mirrored in real code:
|
||||
|
||||
* Binding services (`examples/binding/`)
|
||||
* Multi-window (`examples/window/`)
|
||||
* System tray (`examples/systray-*`)
|
||||
* Packaging (`examples/file-association/`)
|
||||
|
||||
When in doubt, start here and drill into implementation.
|
||||
|
||||
---
|
||||
|
||||
## `templates/` – Scaffolding Blueprints
|
||||
|
||||
`internal/templates` ships **base templates** (Go layout) and **frontend skins**
|
||||
(React, Svelte, Vue, …).
|
||||
At `wails3 init -t react`, the CLI:
|
||||
|
||||
1. Copies `_common` Go files
|
||||
2. Merges desired frontend pack
|
||||
3. Runs `npm install` + `task deps`
|
||||
|
||||
Editing templates **does not** affect existing apps, only future `init`s.
|
||||
|
||||
---
|
||||
|
||||
## `tasks/` – Release Automation
|
||||
|
||||
Taskfiles wrap complex cross-compilation, version bumping, and changelog
|
||||
generation. They are consumed programmatically by `internal/commands/task.go`
|
||||
so the same logic powers **CLI** and **CI**.
|
||||
|
||||
---
|
||||
|
||||
## How the Pieces Interact
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[wails3 CLI] -- build/generate --> B[internal.generator]
|
||||
A -- dev --> C[assetserver (dev)]
|
||||
A -- package --> P[internal.packager]
|
||||
|
||||
subgraph App runtime
|
||||
E[pkg.application] --> F[internal.runtime]
|
||||
F --> G[OS APIs]
|
||||
E --> C
|
||||
end
|
||||
|
||||
B --> E %% generated bindings registered at init
|
||||
```
|
||||
|
||||
*CLI → generator → runtime* forms the core path from **source** to **running
|
||||
desktop app**.
|
||||
|
||||
---
|
||||
|
||||
## Orientation Tips
|
||||
|
||||
| Need to understand… | Look at… |
|
||||
|---------------------|----------|
|
||||
| Platform shims | `internal/runtime/*_DARWIN.go`, `*_windows.go`, … |
|
||||
| Bridge protocol | `pkg/application/messageprocessor_*.go` |
|
||||
| Asset workflow | `internal/assetserver`, `v3/templates/base/assets` |
|
||||
| Packaging flow | `internal/commands/appimage.go`, `internal/packager` |
|
||||
| Template engine | `internal/templates/templates.go` |
|
||||
| Static analysis | `internal/generator/*` |
|
||||
|
||||
---
|
||||
|
||||
You now have a **mental map** of the repository.
|
||||
Use it with `ripgrep`, your IDE’s “Go to File/Symbol”, and the example apps to
|
||||
navigate deeper into any feature. Happy hacking!
|
||||
246
docs/src/content/docs/contributing/extending-wails.mdx
Normal file
246
docs/src/content/docs/contributing/extending-wails.mdx
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
---
|
||||
title: Extending Wails
|
||||
description: Practical guide to adding new features and platforms to Wails v3
|
||||
sidebar:
|
||||
order: 8
|
||||
---
|
||||
|
||||
> Wails is designed to be **hackable**.
|
||||
> Every major subsystem lives in Go code you can read, modify, and ship.
|
||||
> This page shows _where_ to start and _how_ to stay cross-platform when you:
|
||||
|
||||
* Add a **Service** (file server, KV store, custom IPC…)
|
||||
* Create a **new CLI command** (`wails3 <foo>`)
|
||||
* Extend the **Runtime** (window API, dialogs, events)
|
||||
* Introduce a **Platform capability** (Wayland, Apple Vision OS…)
|
||||
* Keep **cross-platform compatibility** without drowning in `//go:build` tags
|
||||
|
||||
---
|
||||
|
||||
## 1. Adding a Service
|
||||
|
||||
Services are background Go components exposed to apps via the `application.Context`.
|
||||
|
||||
```
|
||||
internal/service/
|
||||
├── template/ # Boilerplate for new services
|
||||
│ ├── template.go
|
||||
│ └── README.md
|
||||
└── service.go # Registration + lifecycle interfaces
|
||||
```
|
||||
|
||||
### 1.1 Define the Service
|
||||
|
||||
```go
|
||||
package chat
|
||||
|
||||
type Service struct {
|
||||
messages []string
|
||||
}
|
||||
|
||||
func New() *Service { return &Service{} }
|
||||
|
||||
func (s *Service) Send(msg string) string {
|
||||
s.messages = append(s.messages, msg)
|
||||
return "ok"
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 Implement `service.Service`
|
||||
|
||||
```go
|
||||
func (s *Service) Init(ctx *application.Context) error { return nil }
|
||||
func (s *Service) Shutdown() error { return nil }
|
||||
```
|
||||
|
||||
### 1.3 Register Generator
|
||||
|
||||
*Add to* `internal/generator/collect/services.go`
|
||||
|
||||
```go
|
||||
services.Register("chat", chat.New)
|
||||
```
|
||||
|
||||
> Now `wails3 generate` emits bindings so JS can call
|
||||
> `window.backend.chat.Send("hi")`.
|
||||
|
||||
### 1.4 Ship an Example
|
||||
|
||||
Copy `v3/examples/services/` and adjust.
|
||||
|
||||
---
|
||||
|
||||
## 2. Writing a New CLI Command
|
||||
|
||||
CLI logic lives under `internal/commands/`.
|
||||
Each command is **one file** that mounts itself in `init()`.
|
||||
|
||||
### 2.1 Create the File
|
||||
|
||||
`v3/internal/commands/hello.go`
|
||||
|
||||
```go
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Add(&cobra.Command{
|
||||
Use: "hello",
|
||||
Short: "Prints Hello World",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cmd.Println("Hello Wails!")
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
`Add()` registers with the root in `cmd/wails3/main.go`.
|
||||
|
||||
### 2.2 Re-compile CLI
|
||||
|
||||
```
|
||||
go install ./v3/cmd/wails3
|
||||
wails3 hello
|
||||
```
|
||||
|
||||
If your command needs **Taskfile** integration, reuse helpers in
|
||||
`internal/commands/task_wrapper.go`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Modifying the Runtime
|
||||
|
||||
Common reasons:
|
||||
|
||||
* New window feature (`SetOpacity`, `Shake`, …)
|
||||
* Extra dialog (`ColorPicker`)
|
||||
* System-level API (screen brightness)
|
||||
|
||||
### 3.1 Public API
|
||||
|
||||
Add to `pkg/application/window.go`:
|
||||
|
||||
```go
|
||||
func (w *WebviewWindow) SetOpacity(o float32) {
|
||||
w.ctx.Call("window:setOpacity", o)
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Message Processor
|
||||
|
||||
Create `messageprocessor_window_opacity.go`:
|
||||
|
||||
```go
|
||||
const MsgSetOpacity = "window:setOpacity"
|
||||
|
||||
func init() { register(MsgSetOpacity, handleSetOpacity) }
|
||||
|
||||
func handleSetOpacity(ctx *Context, params json.RawMessage) ([]byte, error) {
|
||||
var req struct {
|
||||
ID uint64 `json:"id"`
|
||||
Opacity float32 `json:"o"`
|
||||
}
|
||||
json.Unmarshal(params, &req)
|
||||
return nil, runtime.SetOpacity(req.ID, req.Opacity)
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Platform Implementation
|
||||
|
||||
```
|
||||
internal/runtime/
|
||||
webview_window_darwin.go
|
||||
webview_window_linux.go
|
||||
webview_window_windows.go
|
||||
```
|
||||
|
||||
Add `SetOpacity()` to each, guarded by build tags.
|
||||
If a platform can’t support it, return `ErrCapability`.
|
||||
|
||||
### 3.4 Capability Flag
|
||||
|
||||
Expose via `internal/capabilities`.
|
||||
|
||||
```go
|
||||
const CapOpacity = "window:opacity"
|
||||
```
|
||||
|
||||
Runtime files `*_darwin.go` etc. should `registerCapability(CapOpacity)` when
|
||||
successfully initialised.
|
||||
|
||||
Apps can query:
|
||||
|
||||
```go
|
||||
if application.HasCapability(application.CapOpacity) { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Adding New Platform Capabilities
|
||||
|
||||
Example: **Wayland** clipboard on Linux.
|
||||
|
||||
1. Fork `internal/runtime/clipboard_linux.go`.
|
||||
2. Split file:
|
||||
* `clipboard_linux_x11.go` → `//go:build linux && !wayland`
|
||||
* `clipboard_linux_wayland.go` → `//go:build linux && wayland`
|
||||
3. Add CLI flag `--tags wayland` in `internal/commands/dev.go`
|
||||
(`goEnv.BuildTags += ",wayland"`).
|
||||
4. Document in **Asset Server** & README.
|
||||
|
||||
> Keep default build tags minimal; reserve opt-in tags for niche features.
|
||||
|
||||
---
|
||||
|
||||
## 5. Cross-Platform Compatibility Checklist
|
||||
|
||||
| ✅ Step | Why |
|
||||
|---------|-----|
|
||||
| Provide **every** public method in all platform files (even if stub) | Keeps build green on all OS/arch |
|
||||
| Gate platform specifics behind **capabilities** | Let apps degrade gracefully |
|
||||
| Use **pure Go** first, CGO only when needed | Simplifies cross-compiles |
|
||||
| Test with `task matrix:test` | Runs `go test ./...` on linux/mac/windows in Docker |
|
||||
| Document new build tags in `docs/getting-started/installation.mdx` | Users must know flags |
|
||||
|
||||
---
|
||||
|
||||
## 6. Debug Builds & Iteration Speed
|
||||
|
||||
* `wails3 dev -tags debug` adds extra log hooks (`logger_dev*.go`).
|
||||
* Use `task reload` to rebuild runtime without restarting the whole app.
|
||||
* Race detector: `wails3 dev -race` (see `pkg/application/RACE.md`).
|
||||
|
||||
---
|
||||
|
||||
## 7. Upstream Contributions
|
||||
|
||||
1. Open an **issue** to discuss the idea & design.
|
||||
2. Follow the techniques above to implement.
|
||||
3. Add:
|
||||
* Unit tests (`*_test.go`)
|
||||
* Example (`v3/examples/<feat>/`)
|
||||
* Docs (this file or API reference)
|
||||
4. Ensure `go vet`, `golangci-lint`, and CI matrix pass.
|
||||
|
||||
---
|
||||
|
||||
### Quick Links
|
||||
|
||||
| Area | Location |
|
||||
|------|----------|
|
||||
| Services framework | `internal/service/` |
|
||||
| CLI core | `internal/commands/` |
|
||||
| Runtime per-OS | `internal/runtime/` |
|
||||
| Capability helpers| `internal/capabilities/` |
|
||||
| Taskfile DSL | `tasks/Taskfile.yml` |
|
||||
| Dev matrix tests | `tasks/events/generate.go` |
|
||||
|
||||
---
|
||||
|
||||
You now have a **roadmap** for bending Wails to your will—add services, sprinkle
|
||||
CLI magic, hack the runtime, or bring entirely new OS features.
|
||||
Happy extending!
|
||||
128
docs/src/content/docs/contributing/index.mdx
Normal file
128
docs/src/content/docs/contributing/index.mdx
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
---
|
||||
title: Technical Overview
|
||||
description: High-level architecture and roadmap to the Wails v3 codebase
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
import Mermaid from "../../components/Mermaid.astro";
|
||||
|
||||
## Welcome to the Wails v3 Technical Documentation
|
||||
|
||||
This section is **not** about community guidelines or how to open a pull-request.
|
||||
Instead, it dives into **how Wails v3 is built** so that you can quickly orient
|
||||
yourself in the codebase and start hacking with confidence.
|
||||
|
||||
Whether you plan to patch the runtime, extend the CLI, craft new templates, or
|
||||
simply understand the internals, the pages that follow provide the technical
|
||||
context you need.
|
||||
|
||||
---
|
||||
|
||||
## High-Level Architecture
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Go Backend" icon="seti:go">
|
||||
The heart of every Wails app is Go code compiled into a native executable.
|
||||
It owns application logic, system integration and performance-critical
|
||||
operations.
|
||||
</Card>
|
||||
|
||||
<Card title="Web Frontend" icon="seti:html">
|
||||
UI is written with standard web tech (React, Vue, Svelte, Vanilla, …)
|
||||
rendered by a lightweight system WebView (WebKit on Linux/macOS, WebView2 on
|
||||
Windows).
|
||||
</Card>
|
||||
|
||||
<Card title="Bridging Layer" icon="puzzle">
|
||||
A zero-copy, in-memory bridge enables **Go⇄JavaScript** calls with automatic
|
||||
type conversion, event propagation and error forwarding.
|
||||
</Card>
|
||||
|
||||
<Card title="CLI & Tooling" icon="terminal">
|
||||
`wails3` orchestrates project creation, live-reload dev server, asset
|
||||
bundling, cross-compilation and packaging (deb, rpm, AppImage, msi, dmg…).
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
## Architectural Overview
|
||||
|
||||
<Mermaid title="Wails v3 – End-to-End Flow">
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Developer Environment
|
||||
CLI[wails3 CLI<br/>Init · Dev · Build · Package]
|
||||
end
|
||||
|
||||
subgraph Build-Time
|
||||
GEN[Binding System<br/>(Static Analysis & Codegen)]
|
||||
ASSET[Asset Server<br/>(Dev Proxy · Embed FS)]
|
||||
PKG[Build & Packaging<br/>Pipeline]
|
||||
end
|
||||
|
||||
subgraph Runtime
|
||||
RUNTIME[Desktop Runtime<br/>(Window · Events · Dialogs)]
|
||||
BIND[Bridge<br/>(Message Processor)]
|
||||
end
|
||||
|
||||
subgraph Application
|
||||
GO[Go Backend<br/>(App Logic)]
|
||||
WEB[Web Frontend<br/>(React/Vue/...)]
|
||||
end
|
||||
|
||||
%% Relationships
|
||||
CLI --> |"generate"| GEN
|
||||
CLI --> |"dev / build"| ASSET
|
||||
CLI --> |"compile & package"| PKG
|
||||
|
||||
GEN --> |"Code Stubs + TS"| GO
|
||||
GEN --> |"Bindings JSON"| WEB
|
||||
|
||||
PKG --> |"Final Binary + Installer"| GO
|
||||
|
||||
GO --> |"Function Calls"| BIND
|
||||
WEB --> |"Invoke / Events"| BIND
|
||||
|
||||
RUNTIME <-->|native messages| BIND
|
||||
RUNTIME --> |"Display Assets"| ASSET
|
||||
WEB <-->|HTTP / In-Memory| ASSET
|
||||
```
|
||||
</Mermaid>
|
||||
|
||||
The diagram shows the **end-to-end flow**:
|
||||
|
||||
1. **CLI** drives generation, dev server, compilation, and packaging.
|
||||
2. **Binding System** produces glue code that lets the **Web Frontend** call into the **Go Backend**.
|
||||
3. During development the **Asset Server** proxies to the framework dev server; in production it serves embedded files.
|
||||
4. At runtime the **Desktop Runtime** manages windows and OS APIs, while the **Bridge** shuttles messages between Go and JavaScript.
|
||||
|
||||
---
|
||||
|
||||
## What This Documentation Covers
|
||||
|
||||
| Topic | Why It Matters |
|
||||
| ----- | -------------- |
|
||||
| **Codebase Layout** | Map of `/v3` directories and how modules interact. |
|
||||
| **Runtime Internals** | Window management, system APIs, message processor, and platform shims. |
|
||||
| **Asset & Dev Server** | How web assets are served in dev and embedded in production. |
|
||||
| **Build & Packaging Pipeline** | Taskfile-based workflow, cross-platform compilation, and installer generation. |
|
||||
| **Binding System** | Static analysis pipeline that generates type-safe Go⇄TS bindings. |
|
||||
| **Template System** | Generator architecture that powers `wails init -t <framework>`. |
|
||||
| **Testing & CI** | Unit/integration test harness, GitHub Actions, race detector guidance. |
|
||||
| **Extending Wails** | Adding services, templates, or CLI sub-commands. |
|
||||
|
||||
Each subsequent page drills into these areas with concrete code samples,
|
||||
diagrams, and references to the relevant source files.
|
||||
|
||||
---
|
||||
|
||||
:::note
|
||||
Prerequisites: You should be comfortable with **Go 1.23+**, basic TypeScript,
|
||||
and modern frontend build tools. If you are new to Go, consider skimming the
|
||||
official tour first.
|
||||
:::
|
||||
|
||||
Happy exploring — and welcome to the Wails v3 internals!
|
||||
175
docs/src/content/docs/contributing/runtime-internals.mdx
Normal file
175
docs/src/content/docs/contributing/runtime-internals.mdx
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
---
|
||||
title: Runtime Internals
|
||||
description: Deep-dive into how Wails v3 boots, runs, and talks to the OS
|
||||
sidebar:
|
||||
order: 3
|
||||
---
|
||||
|
||||
The **runtime** is the layer that transforms ordinary Go functions into a
|
||||
cross-platform desktop application.
|
||||
This document explains the moving parts you will meet when tracing through the
|
||||
source code.
|
||||
|
||||
---
|
||||
|
||||
## 1. Application Lifecycle
|
||||
|
||||
| Phase | Code Path | What Happens |
|
||||
|-------|-----------|--------------|
|
||||
| **Bootstrap** | `pkg/application/application.go:init()` | Registers build-time data, creates a global `application` singleton. |
|
||||
| **New()** | `application.New(...)` | Validates `Options`, spins up the **AssetServer**, initialises logging. |
|
||||
| **Run()** | `application.(*App).Run()` | 1. Calls platform `mainthread.X()` to enter the OS UI thread.<br>2. Boots the **runtime** (`internal/runtime`).<br>3. Blocks until the last window closes or `Quit()` is called. |
|
||||
| **Shutdown** | `application.(*App).Quit()` | Broadcasts `application:shutdown` event, flushes log, tears down windows & services. |
|
||||
|
||||
The lifecycle is strictly **single-entry**: you may create many windows, but the
|
||||
application object itself is initialised once.
|
||||
|
||||
---
|
||||
|
||||
## 2. Window Management
|
||||
|
||||
### Public API
|
||||
|
||||
```go
|
||||
win := app.NewWebviewWindow(&application.WebviewWindowOptions{
|
||||
Title: "Dashboard",
|
||||
Width: 1280,
|
||||
Height: 720,
|
||||
})
|
||||
win.Show()
|
||||
```
|
||||
|
||||
`NewWebviewWindow` delegates to `internal/runtime/webview_window_*.go` where
|
||||
platform-specific constructors live:
|
||||
|
||||
```
|
||||
internal/runtime/
|
||||
├── webview_window_darwin.go
|
||||
├── webview_window_linux.go
|
||||
└── webview_window_windows.go
|
||||
```
|
||||
|
||||
Each file:
|
||||
|
||||
1. Creates a native webview (WKWebView, WebKitGTK, WebView2).
|
||||
2. Registers a **Message Processor** callback.
|
||||
3. Maps Wails events (`WindowResized`, `Focus`, `DropFile`, …) to runtime
|
||||
event IDs.
|
||||
|
||||
Windows are tracked by `screenmanager.go` which offers **query APIs** (all
|
||||
displays, DPI, active window) and centralises resize / move bookkeeping.
|
||||
|
||||
---
|
||||
|
||||
## 3. Message Processing Pipeline
|
||||
|
||||
The bridge between JavaScript and Go is implemented by the **Message
|
||||
Processor** family in `pkg/application/messageprocessor_*.go`.
|
||||
|
||||
Flow:
|
||||
|
||||
1. **JavaScript** calls `window.runtime.Invoke("Greet", "Alice")`.
|
||||
2. The runtime serialises the request:
|
||||
`{"t":"c","id":"123","m":"Greet","p":["Alice"]}`
|
||||
(`t` = type, `c` = call).
|
||||
3. **Go** receives this JSON via the webview callback.
|
||||
4. `messageprocessor_call.go` looks up the bound method in the
|
||||
generated table (`application.bindings.go`) and executes it.
|
||||
5. The result or error is marshalled back to JS where a `Promise` resolves.
|
||||
|
||||
Specialised processors:
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `messageprocessor_window.go` | Window actions (hide, maximize, …) |
|
||||
| `messageprocessor_dialog.go` | Native dialogs (`OpenFile`, `MessageBox`, …) |
|
||||
| `messageprocessor_clipboard.go` | Clipboard read/write |
|
||||
| `messageprocessor_events.go` | Event subscribe / emit |
|
||||
| `messageprocessor_browser.go` | Browser navigation, devtools |
|
||||
|
||||
Processors are **stateless** – they pull everything they need from the
|
||||
`ApplicationContext` passed with each message.
|
||||
|
||||
---
|
||||
|
||||
## 4. Events System
|
||||
|
||||
Events are namespaced strings dispatched across three layers:
|
||||
|
||||
1. **Application events**: global lifecycle (`application:ready`,
|
||||
`application:shutdown`).
|
||||
2. **Window events**: per-window (`window:focus`, `window:resize`).
|
||||
3. **Custom events**: user-defined (`chat:new-message`).
|
||||
|
||||
Implementation details:
|
||||
|
||||
* Constants are generated from `internal/events/defaults.go`.
|
||||
* Go side:
|
||||
`app.On("window:focus", func(e application.WindowEvent) {...})`
|
||||
* JS side:
|
||||
`window.runtime.EventsOn("chat:new-message", cb)`
|
||||
|
||||
Under the hood both map to the same **EventBus** in
|
||||
`pkg/application/events.go`.
|
||||
Subscribers are reference-counted; when a window closes, its callbacks are
|
||||
auto-unregistered to avoid leaks.
|
||||
|
||||
---
|
||||
|
||||
## 5. Platform-Specific Implementations
|
||||
|
||||
Conditional compilation keeps the public API identical while hiding OS wrinkles.
|
||||
|
||||
| Concern | Darwin | Linux | Windows |
|
||||
|---------|--------|-------|---------|
|
||||
| Main Thread | `mainthread_darwin.go` (Cgo to Foundation) | `mainthread_linux.go` (GTK) | `mainthread_windows.go` (Win32 `AttachThreadInput`) |
|
||||
| Dialogs | `dialogs_darwin.*` (NSAlert) | `dialogs_linux.go` (GtkFileChooser) | `dialogs_windows.go` (IFileOpenDialog) |
|
||||
| Clipboard | `clipboard_darwin.go` | `clipboard_linux.go` | `clipboard_windows.go` |
|
||||
| Tray Icons | `systemtray_darwin.*` | `systemtray_linux.go` (DBus) | `systemtray_windows.go` (Shell_NotifyIcon) |
|
||||
|
||||
Key principles:
|
||||
|
||||
* **No Cgo on Windows/Linux** unless unavoidable (performance, portability).
|
||||
* Use **build tags** (`//go:build darwin && !production`) to keep files readable.
|
||||
* Expose **capabilities** via `internal/capabilities` so higher layers can
|
||||
degrade gracefully.
|
||||
|
||||
---
|
||||
|
||||
## 6. File Guide
|
||||
|
||||
| File | Why You’d Touch It |
|
||||
|------|--------------------|
|
||||
| `internal/runtime/runtime_*.go` | Change global startup logic, add debug hooks. |
|
||||
| `internal/runtime/webview_window_*.go` | Implement a new window hint or behaviour. |
|
||||
| `pkg/application/messageprocessor_*.go` | Add a new bridge command callable from JS. |
|
||||
| `pkg/application/events_*.go` | Extend built-in event definitions. |
|
||||
| `internal/assetserver/*` | Tweak dev/production asset handling. |
|
||||
|
||||
---
|
||||
|
||||
## 7. Debugging Tips
|
||||
|
||||
* Launch with `WAILS_LOG_LEVEL=debug` to print every message crossing the bridge.
|
||||
* Use `wails3 dev -verbose` to see live reload & asset requests.
|
||||
* On macOS run under `lldb --` to catch Objective-C exceptions early.
|
||||
* For Windows Chromium issues, enable WebView2 debug logs:
|
||||
`set WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=9222`
|
||||
|
||||
---
|
||||
|
||||
## 8. Extending the Runtime
|
||||
|
||||
1. Define a **capability flag** in `internal/capabilities`.
|
||||
2. Implement the feature in each platform file using build tags.
|
||||
3. Add public API in `pkg/application`.
|
||||
4. Register a new message type or event if JS needs to call it.
|
||||
5. Update at least one example in `v3/examples/` exercising the feature.
|
||||
|
||||
Follow this checklist and you'll keep the cross-platform contract intact.
|
||||
|
||||
---
|
||||
|
||||
You now have a guided tour of the runtime internals. Combine this knowledge with
|
||||
the **Codebase Layout** map and the **Asset Server** docs to navigate confidently
|
||||
and make impactful contributions. Happy coding!
|
||||
222
docs/src/content/docs/contributing/template-system.mdx
Normal file
222
docs/src/content/docs/contributing/template-system.mdx
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
---
|
||||
title: Template System
|
||||
description: How Wails v3 scaffolds new projects, how templates are organised, and how to build your own.
|
||||
sidebar:
|
||||
order: 7
|
||||
---
|
||||
|
||||
Wails ships with a **template system** that lets `wails3 init` produce a ready-to-run
|
||||
project for **any** modern frontend stack (React, Svelte, Vue, Solid, Vanilla
|
||||
JS …).
|
||||
|
||||
This page covers:
|
||||
|
||||
1. Template directory layout
|
||||
2. How the CLI chooses & renders templates
|
||||
3. Creating a new template step-by-step
|
||||
4. Updating or overriding existing templates
|
||||
5. Troubleshooting and best practices
|
||||
|
||||
---
|
||||
|
||||
## 1. Where Templates Live
|
||||
|
||||
```
|
||||
v3/
|
||||
└── internal/templates/
|
||||
├── _common/ # Files copied into EVERY project (main.go, .gitignore)
|
||||
├── base/ # Backend-only “plain Go” template
|
||||
├── react/ # React + Vite
|
||||
├── react-ts/
|
||||
├── svelte/
|
||||
├── vue/
|
||||
├── ... (others)
|
||||
└── templates.go # Registry + helper functions
|
||||
```
|
||||
|
||||
* **`_common/`** – universal boilerplate (Go modules, Taskfile, README stub,
|
||||
VS Code settings).
|
||||
* **Framework folders** – contain **frontend/** and any stack-specific config.
|
||||
* Folder names match the **template ID** you pass to the CLI
|
||||
(`wails3 init -t react-ts`).
|
||||
|
||||
> The whole directory is compiled into the CLI binary via `go:embed`, so users
|
||||
> can scaffold projects offline.
|
||||
|
||||
---
|
||||
|
||||
## 2. How `wails3 init` Uses Templates
|
||||
|
||||
Call chain:
|
||||
|
||||
```
|
||||
cmd/wails3/init.go
|
||||
│
|
||||
▼
|
||||
internal/commands/init.go ← parses flags, target dir
|
||||
│
|
||||
▼
|
||||
internal/templates.Load() ← templates.go
|
||||
│
|
||||
▼
|
||||
Template.CopyTo(dest) ← filesystem copy + text substitutions
|
||||
```
|
||||
|
||||
### Substitutions
|
||||
|
||||
Placeholders wrapped in `{{ }}` are replaced at copy time:
|
||||
|
||||
| Placeholder | Example | Source |
|
||||
|-------------|---------|--------|
|
||||
| `{{ProjectName}}` | `myapp` | CLI flag/dir name |
|
||||
| `{{ModulePath}}` | `github.com/me/myapp` | if `--module` provided |
|
||||
| `{{WailsVersion}}`| `v3.0.0` | compiled constant |
|
||||
|
||||
You can add **any** placeholder; just ensure it gets a value in
|
||||
`internal/commands/init.go`.
|
||||
|
||||
### Post-Copy Hooks
|
||||
|
||||
Each template may ship a **Taskfile** with a `deps` task.
|
||||
After copy, the CLI runs:
|
||||
|
||||
```
|
||||
task --taskfile Taskfile.yml deps
|
||||
```
|
||||
|
||||
Typical work:
|
||||
|
||||
* `go mod tidy`
|
||||
* `npm install`
|
||||
* git init & initial commit (optional)
|
||||
|
||||
Hook behaviour defined in `_common/Taskfile.yml` but override-able per template.
|
||||
|
||||
---
|
||||
|
||||
## 3. Creating a New Template
|
||||
|
||||
> Example: Add **Qwik-TS** template
|
||||
|
||||
### 3.1 Folder & ID
|
||||
|
||||
```
|
||||
internal/templates/qwik-ts/
|
||||
```
|
||||
|
||||
The folder name = template ID. Keep it **kebab-case**.
|
||||
|
||||
### 3.2 Minimal File Set
|
||||
|
||||
```
|
||||
qwik-ts/
|
||||
├── README.md # Shown in CLI with -list
|
||||
├── frontend/ # Your web project
|
||||
│ ├── src/
|
||||
│ ├── package.json
|
||||
│ └── vite.config.ts
|
||||
├── Taskfile.yml # deps & dev tasks
|
||||
└── go/ # Optional extra Go files
|
||||
```
|
||||
|
||||
Start by copying `react-ts` and pruning files.
|
||||
|
||||
### 3.3 Update Placeholders
|
||||
|
||||
Search/replace:
|
||||
|
||||
* `myapp` → `{{ProjectName}}`
|
||||
* `github.com/you/myapp` → `{{ModulePath}}`
|
||||
|
||||
### 3.4 Register (optional)
|
||||
|
||||
If the folder exists, **templates.go auto-detects it** via `embed`.
|
||||
Only add explicit code if you need **custom validation**:
|
||||
|
||||
```go
|
||||
func (t *Template) Validate() error {
|
||||
// check Node ≥ 20 for this template
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 Test
|
||||
|
||||
```bash
|
||||
wails3 init -n demo -t qwik-ts
|
||||
cd demo
|
||||
wails3 dev
|
||||
```
|
||||
|
||||
Ensure:
|
||||
|
||||
* Dev server starts
|
||||
* `wailsjs` bindings generate
|
||||
* Hot-reload works
|
||||
|
||||
---
|
||||
|
||||
## 4. Modifying Existing Templates
|
||||
|
||||
1. Edit files under `internal/templates/<id>/`.
|
||||
2. Run `go generate ./v3/...` **or** just `go run ./v3/cmd/wails3 –help`; the
|
||||
embed directive recompiles automatically.
|
||||
3. Bump any **dependency versions** in `frontend/package.json` & `Taskfile.yml`.
|
||||
4. Update `README.md` inside the template — users see it via
|
||||
`wails3 init -t react --help`.
|
||||
|
||||
### Common Tweaks
|
||||
|
||||
| Task | Where |
|
||||
|------|-------|
|
||||
| Change dev server port | `frontend/Taskfile.yml` (`vite dev --port {{DevPort}}`) |
|
||||
| Add environment variables | same Taskfile or `.env` |
|
||||
| Replace JS package manager | Switch `npm` → `pnpm` in Taskfile |
|
||||
|
||||
---
|
||||
|
||||
## 5. Template Authoring Tips
|
||||
|
||||
* **Keep frontend generic** – avoid referencing Wails-specific globals; runtime
|
||||
is injected automatically.
|
||||
* **No compiled artefacts** – exclude `node_modules`, `dist`, `.DS_Store` via
|
||||
`.tmplignore`.
|
||||
* **Document prerequisites** – Node version, extra CLI tools, etc.
|
||||
* **Avoid breaking changes** – create a *new* template ID if overhaul is big.
|
||||
|
||||
---
|
||||
|
||||
## 6. Troubleshooting
|
||||
|
||||
| Symptom | Cause | Fix |
|
||||
|---------|-------|-----|
|
||||
| `unknown template id` | Folder name mismatch | Check `wails3 init -list` |
|
||||
| Placeholders not replaced | Missing entry in `Data` map | Edit `internal/commands/init.go` |
|
||||
| Dev server opens blank page | Wrong `DevPort` env | Ensure Taskfile echoes port to stdout |
|
||||
| Frontend fails to build in prod | Forgotten vite `base` path | Set `base: "./"` in `vite.config.ts` |
|
||||
|
||||
Add `--verbose` to `wails3 init` to print every copied file and substitution.
|
||||
|
||||
---
|
||||
|
||||
## 7. Key Source File Map
|
||||
|
||||
| File | Responsibility |
|
||||
|------|----------------|
|
||||
| `internal/templates/templates.go` | Embeds template FS, returns `[]Template` |
|
||||
| `internal/templates/<id>/**` | Actual template content |
|
||||
| `internal/commands/init.go` | CLI glue: picks template, fills placeholders |
|
||||
| `internal/commands/generate_template.go` | Utility to *export* a live project back into a template (handy for updates) |
|
||||
|
||||
---
|
||||
|
||||
## 8. Recap
|
||||
|
||||
* Templates live in **`internal/templates/`** and are baked into the CLI.
|
||||
* `wails3 init -t <id>` copies `_common` + selected template, performs
|
||||
substitutions, then runs a **deps** Taskfile hook.
|
||||
* Creating a template is as simple as **making a folder**, adding files, and
|
||||
using placeholders.
|
||||
* The system is **extensible** and **self-contained** — perfect for sharing
|
||||
custom stacks with your team or the community.
|
||||
|
||||
Happy templating!
|
||||
216
docs/src/content/docs/contributing/testing-ci.mdx
Normal file
216
docs/src/content/docs/contributing/testing-ci.mdx
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
---
|
||||
title: Testing & Continuous Integration
|
||||
description: How Wails v3 ensures quality with unit tests, integration suites, race detection, and GitHub Actions CI.
|
||||
sidebar:
|
||||
order: 9
|
||||
---
|
||||
|
||||
Robust desktop frameworks demand rock-solid testing.
|
||||
Wails v3 employs a **layered strategy**:
|
||||
|
||||
| Layer | Goal | Tooling |
|
||||
|-------|------|---------|
|
||||
| Unit tests | Fast feedback for isolated functions | `go test ./...` |
|
||||
| Integration / Example tests | Validate whole features across packages | Example apps + headless assertions |
|
||||
| Race detection | Catch data races in the runtime & bridge | `go test -race`, `wails3 dev -race` |
|
||||
| CI matrix | Cross-OS confidence on every PR | GitHub Actions |
|
||||
|
||||
This document explains **where tests live**, **how to run them**, and **what happens in CI**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Directory Conventions
|
||||
|
||||
```
|
||||
v3/
|
||||
├── internal/.../_test.go # Unit tests for internal packages
|
||||
├── pkg/.../_test.go # Public API tests
|
||||
├── examples/... # Example apps double as integration tests
|
||||
├── tasks/events/... # Generated code tests run in CI
|
||||
└── pkg/application/RACE.md # Race test guidance & known safe suppressions
|
||||
```
|
||||
|
||||
Guidelines:
|
||||
|
||||
* **Keep unit tests next to code** (`foo.go` ↔ `foo_test.go`)
|
||||
* Use the **black-box style** for `pkg/` packages (`package application_test`)
|
||||
* Integration helpers go in `internal/testutil/` (shared mocks, fixtures)
|
||||
|
||||
---
|
||||
|
||||
## 2. Unit Tests
|
||||
|
||||
### Writing Tests
|
||||
|
||||
```go
|
||||
func TestColourParseHex(t *testing.T) {
|
||||
c, err := colour.Parse("#FF00FF")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 255, c.R)
|
||||
}
|
||||
```
|
||||
|
||||
Recommendations:
|
||||
|
||||
* Use [`stretchr/testify`](https://github.com/stretchr/testify) – already vendor-imported.
|
||||
* Follow **table-driven** style for branches.
|
||||
* Stub platform quirks behind build tags (`_test_windows.go` etc.) when needed.
|
||||
|
||||
### Running Locally
|
||||
|
||||
```bash
|
||||
cd v3
|
||||
go test ./... -cover
|
||||
```
|
||||
|
||||
Taskfile shortcut:
|
||||
|
||||
```
|
||||
task test # same as above + vet + lint
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Integration & Example Tests
|
||||
|
||||
Every folder under `v3/examples/` is a **self-contained app**.
|
||||
Three techniques turn them into tests:
|
||||
|
||||
| Technique | File | What it checks |
|
||||
|-----------|------|----------------|
|
||||
| `go test ./...` inside example | `*_test.go` spins up `wails dev` in headless mode | Build & startup |
|
||||
| CLI golden tests | `internal/commands/appimage_test.go` | Generated artefact hashes |
|
||||
| Scripted e2e | `tasks/events/generate.go` | Emits Go that launches example, asserts runtime logs |
|
||||
|
||||
Run all with:
|
||||
|
||||
```
|
||||
task integration
|
||||
```
|
||||
|
||||
> Headless mode uses **Xvfb** on Linux runners; macOS & Windows start minimized.
|
||||
|
||||
---
|
||||
|
||||
## 4. Race Detection
|
||||
|
||||
Data races are fatal in GUI runtimes.
|
||||
|
||||
### Dedicated Doc
|
||||
|
||||
See `v3/pkg/application/RACE.md` for:
|
||||
|
||||
* Known benign races and suppression rationale
|
||||
* How to interpret stack-traces crossing Cgo boundaries
|
||||
|
||||
### Local Race Suite
|
||||
|
||||
```
|
||||
go test -race ./...
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
wails3 dev -race
|
||||
```
|
||||
|
||||
*The latter rebuilds runtime with race flags and launches a demo window.
|
||||
Close the window; any races print after exit.*
|
||||
|
||||
CI runs the race suite on **linux/amd64** (fastest) for every PR.
|
||||
|
||||
---
|
||||
|
||||
## 5. GitHub Actions Workflows
|
||||
|
||||
### 5.1 `build-and-test-v3.yml`
|
||||
|
||||
Location: `.github/workflows/build-and-test-v3.yml`
|
||||
|
||||
Key features:
|
||||
|
||||
* **Matrix** over `os: [ubuntu-latest, windows-latest, macos-latest]`
|
||||
* Caches Go & npm deps for speed
|
||||
* Steps:
|
||||
1. `setup-go` @ 1.23
|
||||
2. `go vet ./...`
|
||||
3. `go test ./... -v -coverprofile=cover.out`
|
||||
4. Build CLI + sample app (`wails3 build -skip-package`)
|
||||
5. Upload coverage to Codecov
|
||||
* Conditional **race** job on Ubuntu:
|
||||
```yaml
|
||||
- name: Race Detector
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: go test -race ./...
|
||||
```
|
||||
|
||||
### 5.2 Static Analysis
|
||||
|
||||
* `semgrep.yml` – security/lint scanning (`.github/workflows/semgrep.yml`)
|
||||
* `qodana.yaml` – JetBrains Qodana cloud inspection (optional in PR)
|
||||
|
||||
### 5.3 Release Pipeline
|
||||
|
||||
`runtime.yml` builds and publishes the JS runtime to npm on tag push; out of testing scope but referenced by build matrix.
|
||||
|
||||
---
|
||||
|
||||
## 6. Local CI Parity
|
||||
|
||||
Run the **exact** CI task set via Taskfile:
|
||||
|
||||
```
|
||||
task ci
|
||||
```
|
||||
|
||||
It executes:
|
||||
|
||||
1. `lint` (golangci-lint, govet)
|
||||
2. `test` (unit + race)
|
||||
3. `integration`
|
||||
4. `build` (for host platform)
|
||||
|
||||
---
|
||||
|
||||
## 7. Troubleshooting Failing Tests
|
||||
|
||||
| Symptom | Likely Cause | Fix |
|
||||
|---------|--------------|-----|
|
||||
| **Race in `runtime_darwin.go`** | Forgot to lock `mainthread.Call` | Use `mainthread.Sync` helper |
|
||||
| **Windows headless hangs** | Console window waiting for input | Pass `-verbose` and ensure no dialogs open |
|
||||
| **CI example build fails** | Template changed without bumping example deps | `task update-examples` regenerates lockfiles |
|
||||
| **Coverpkg errors** | Integration test importing `main` | Switch to build tags `//go:build integration` |
|
||||
|
||||
---
|
||||
|
||||
## 8. Adding New Tests
|
||||
|
||||
1. **Unit** – create `*_test.go`, run `go test ./...`
|
||||
2. **Integration** – update or add example app + test harness
|
||||
3. **CI** – commit; the workflow auto-discovers tests
|
||||
If OS-specific, add skip tags:
|
||||
|
||||
```go
|
||||
//go:build !windows
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Key File Map
|
||||
|
||||
| What | Path |
|
||||
|------|------|
|
||||
| Unit test example | `internal/colour/colour_test.go` |
|
||||
| Integration harness | `internal/commands/appimage_test.go` |
|
||||
| Race guide | `pkg/application/RACE.md` |
|
||||
| Taskfile test targets | `v3/Taskfile.yaml` |
|
||||
| CI workflow | `.github/workflows/build-and-test-v3.yml` |
|
||||
|
||||
---
|
||||
|
||||
Quality isn’t an afterthought in Wails v3.
|
||||
With unit tests, integration suites, race detection, and a cross-platform CI
|
||||
matrix you can contribute confidently, knowing your changes run green on every
|
||||
OS we support.
|
||||
Happy testing!
|
||||
|
|
@ -174,13 +174,13 @@ wails3 generate icons [flags]
|
|||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|------------|------------------------------|-----------------------|
|
||||
| `-input` | Input PNG file | Required |
|
||||
| `-windows` | Windows output filename | |
|
||||
| `-mac` | macOS output filename | |
|
||||
| `-sizes` | Icon sizes (comma-separated) | `256,128,64,48,32,16` |
|
||||
| `-example` | Generate example icon | `false` |
|
||||
| Flag | Description | Default |
|
||||
|--------------------|------------------------------|-----------------------|
|
||||
| `-input` | Input PNG file | Required |
|
||||
| `-windowsfilename` | Windows output filename | |
|
||||
| `-macfilename` | macOS output filename | |
|
||||
| `-sizes` | Icon sizes (comma-separated) | `256,128,64,48,32,16` |
|
||||
| `-example` | Generate example icon | `false` |
|
||||
|
||||
### `generate syso`
|
||||
Generates Windows .syso file.
|
||||
|
|
@ -325,6 +325,32 @@ Shows build information about the application.
|
|||
wails3 tool buildinfo
|
||||
```
|
||||
|
||||
### `tool version`
|
||||
Bumps a semantic version based on the provided flags.
|
||||
|
||||
```bash
|
||||
wails3 tool version [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|---------------|--------------------------------------------------|---------|
|
||||
| `-v` | Current version to bump | |
|
||||
| `-major` | Bump major version | `false` |
|
||||
| `-minor` | Bump minor version | `false` |
|
||||
| `-patch` | Bump patch version | `false` |
|
||||
| `-prerelease` | Bump prerelease version (e.g., alpha.5 to alpha.6) | `false` |
|
||||
|
||||
The command follows the precedence order: major > minor > patch > prerelease. It preserves the "v" prefix if present in the input version, as well as any prerelease and metadata components.
|
||||
|
||||
Example usage:
|
||||
```bash
|
||||
wails3 tool version -v 1.2.3 -major # Output: 2.0.0
|
||||
wails3 tool version -v v1.2.3 -minor # Output: v1.3.0
|
||||
wails3 tool version -v 1.2.3-alpha -patch # Output: 1.2.4-alpha
|
||||
wails3 tool version -v v3.0.0-alpha.5 -prerelease # Output: v3.0.0-alpha.6
|
||||
```
|
||||
|
||||
### `tool package`
|
||||
Generates Linux packages (deb, rpm, archlinux).
|
||||
|
||||
|
|
|
|||
167
docs/src/content/docs/guides/custom-protocol-association.mdx
Normal file
167
docs/src/content/docs/guides/custom-protocol-association.mdx
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
---
|
||||
title: Custom Protocol Schemes (Deep Linking)
|
||||
description: Guide to implementing custom URL schemes for deep linking in Wails applications across macOS, Windows, and Linux.
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
|
||||
# Custom Protocol Schemes (Deep Linking)
|
||||
|
||||
Custom protocol schemes (also known as custom URL schemes or deep linking) allow your Wails application to be launched or brought to the foreground by clicking a URL with a scheme you define (e.g., `myapp://some/data`). This is useful for various purposes, such as:
|
||||
|
||||
- OAuth authentication flows.
|
||||
- Inter-application communication.
|
||||
- Launching your app with a specific context or to perform a particular action.
|
||||
|
||||
Wails provides a unified way to handle these custom URL invocations across macOS, Windows, and Linux through the `events.Common.ApplicationLaunchedWithUrl` event.
|
||||
|
||||
## Defining Your Protocols
|
||||
|
||||
First, you need to define the custom protocol schemes your application will use. This is done in your `wails.json` project configuration file. Wails reads this file during the build process (`wails build`) to configure the necessary platform-specific assets like `Info.plist` for macOS, NSIS installer scripts for Windows, and `.desktop` files for Linux.
|
||||
|
||||
**Example: `wails.json`**
|
||||
|
||||
```json title="wails.json"
|
||||
{
|
||||
"name": "My App",
|
||||
"description": "An amazing Wails app!",
|
||||
"info": {
|
||||
"companyName": "My Company",
|
||||
"productName": "My Product",
|
||||
// ... other info fields ...
|
||||
"protocols": [
|
||||
{
|
||||
"scheme": "myapp",
|
||||
"description": "My Application Custom Protocol"
|
||||
},
|
||||
{
|
||||
"scheme": "anotherprotocol",
|
||||
"description": "Another protocol for specific actions"
|
||||
}
|
||||
]
|
||||
}
|
||||
// ... other wails.json fields ...
|
||||
}
|
||||
```
|
||||
|
||||
This `info.protocols` array is what Wails uses to generate the necessary entries in platform-specific files. For example, in template files, you might access this via a path like `{{.Info.Protocols}}`.
|
||||
|
||||
<Aside type="note">
|
||||
While `application.Options` in your `main.go` is used for runtime application settings, the definition of custom protocols for build-time asset generation (like `Info.plist`, NSIS scripts, `.desktop` files) should be managed in `wails.json`.
|
||||
</Aside>
|
||||
|
||||
## Handling the Event in Your Application
|
||||
|
||||
When your application is launched or activated via a custom URL, Wails emits an `events.Common.ApplicationLaunchedWithUrl` event. You can listen for this event and retrieve the URL that triggered the launch.
|
||||
|
||||
```go title="main.go"
|
||||
import (
|
||||
"log"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "My App", // Ensure this matches relevant info from wails.json if needed
|
||||
Description: "An amazing Wails app!",
|
||||
// ... other runtime options ...
|
||||
})
|
||||
|
||||
app.OnApplicationEvent(events.Common.ApplicationLaunchedWithUrl, func(e *application.ApplicationEvent) {
|
||||
launchedURL := e.Context().URL() // Retrieve the URL from the event context
|
||||
log.Printf("Application launched with URL: %s", launchedURL)
|
||||
|
||||
// TODO: Process the URL (e.g., navigate, perform action, etc.)
|
||||
// Example: app.EmitEvent("frontend:ShowURL", launchedURL)
|
||||
})
|
||||
|
||||
// ... rest of your main function ...
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Aside type="note">
|
||||
The `e.Context().URL()` method returns the full URL string that was used to launch the application (e.g., `myapp://some/data?param=value`).
|
||||
</Aside>
|
||||
|
||||
## Platform-Specific Setup and Behavior
|
||||
|
||||
While Wails aims for a unified event, the underlying mechanism for custom protocol registration and URL delivery varies by operating system.
|
||||
|
||||
### macOS
|
||||
|
||||
- **Setup:** Wails automatically configures your application's `Info.plist` file during the build process. It adds `CFBundleURLTypes` entries based on the `info.protocols` defined in your `wails.json` file.
|
||||
```xml title="Info.plist (excerpt generated by Wails)"
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>My Application Custom Protocol</string> <!-- From Protocol.Description in wails.json -->
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>myapp</string> <!-- From Protocol.Scheme in wails.json -->
|
||||
</array>
|
||||
</dict>
|
||||
<!-- ... other protocols ... -->
|
||||
</array>
|
||||
```
|
||||
- **How it Works:** When a URL like `myapp://` is opened, macOS uses LaunchServices to find the application registered for that scheme and sends it an Apple Event (`kAEGetURL`). Wails intercepts this event and translates it into the common `events.Common.ApplicationLaunchedWithUrl` Wails event, providing the URL via `e.Context().URL()`.
|
||||
|
||||
### Windows
|
||||
|
||||
- **Setup:** Custom protocol schemes on Windows are registered in the Windows Registry. Wails facilitates this through its NSIS installer template.
|
||||
- When you build your application with the `-nsis` flag, Wails uses the `v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl` file.
|
||||
- This template contains macros like `CUSTOM_PROTOCOL_ASSOCIATE` and `wails.associateCustomProtocols` which use the `info.protocols` from your `wails.json` (passed as `{{.Info.Protocols}}` to the template) to create the necessary registry entries during installation.
|
||||
```nsis title="wails_tools.nsh.tmpl (excerpt)"
|
||||
!macro wails.associateCustomProtocols
|
||||
; Create custom protocols associations
|
||||
{{range .Info.Protocols}}
|
||||
!insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\""
|
||||
{{end}}
|
||||
!macroend
|
||||
```
|
||||
- **How it Works:** The installer registers your application executable to be called with the URL as a command-line argument (`%1`). For example, `your_app.exe "myapp://some/data"`.
|
||||
- The Wails runtime for Windows (`v3/pkg/application/application_windows.go`) has been updated to check `os.Args` upon startup. If it detects an argument that looks like a URL (e.g., `os.Args[1]` contains `"://"`), it now emits the `events.Common.ApplicationLaunchedWithUrl` event with this URL.
|
||||
|
||||
<Aside type="important">
|
||||
For Windows, custom protocol schemes are typically only registered when your application is installed via an installer (like the one generated by Wails with NSIS). Running the bare executable might not have the schemes registered system-wide.
|
||||
</Aside>
|
||||
|
||||
### Linux
|
||||
|
||||
- **Setup:** On Linux, custom protocol handling is typically managed via `.desktop` files and the MIME type system.
|
||||
- Wails uses a `.desktop` file template (e.g., `v3/internal/commands/updatable_build_assets/linux/desktop.tmpl`) which is populated during the build using information from `wails.json`.
|
||||
```desktop title="desktop.tmpl (excerpt)"
|
||||
[Desktop Entry]
|
||||
Name={{.ProductName}}
|
||||
Exec=/usr/local/bin/{{.BinaryName}} %u
|
||||
MimeType={{range $index, $protocol := .Info.Protocols}}x-scheme-handler/{{$protocol.Scheme}};{{end}}
|
||||
```
|
||||
The `Exec` line uses `%u` which gets replaced by the URL. The `MimeType` line registers your application as a handler for `x-scheme-handler/your-scheme` for each protocol defined in `wails.json` (via `{{.Info.Protocols}}`).
|
||||
- When packaging for Linux (e.g., using `nfpm`), this `.desktop` file is installed to `/usr/share/applications/`.
|
||||
- A `postinstall.sh` script (e.g., `v3/internal/commands/build_assets/linux/nfpm/scripts/postinstall.sh`) is used to update the system's application and MIME databases:
|
||||
```sh title="postinstall.sh (excerpt)"
|
||||
#!/bin/sh
|
||||
update-desktop-database -q /usr/share/applications
|
||||
update-mime-database -n /usr/share/mime
|
||||
```
|
||||
- **How it Works:** When a URL like `myapp://` is opened, the desktop environment uses the MIME database to find the associated `.desktop` file and executes the command specified in its `Exec` line, substituting `%u` with the URL. Your application receives this URL as a command-line argument.
|
||||
- The Wails runtime for Linux (`v3/pkg/application/application_linux.go`) checks `os.Args` on startup. If it detects an argument that looks like a URL, it emits the `events.Common.ApplicationLaunchedWithUrl` event.
|
||||
|
||||
## Testing Your Custom Protocols
|
||||
|
||||
- **macOS:** Open Terminal and type `open "your-scheme://your/data"`.
|
||||
- **Linux:** Open a terminal and type `xdg-open "your-scheme://your/data"` (requires `xdg-utils` to be installed and the app to be properly packaged and registered).
|
||||
- **Windows:** After installation via NSIS:
|
||||
- You can try running `start your-scheme://your/data` from Command Prompt or PowerShell.
|
||||
- Create a simple HTML file with a link `<a href="your-scheme://your/data">Test Link</a>` and open it in a browser.
|
||||
|
||||
<Aside type="tip">
|
||||
Always ensure your application is properly built and installed (especially for Windows and Linux) for the system to recognize the custom protocol schemes.
|
||||
</Aside>
|
||||
|
||||
By following this guide, you can effectively use custom protocol schemes to enhance your Wails application's interactivity and integration with other applications or web services.
|
||||
400
docs/src/content/docs/guides/events-reference.mdx
Normal file
400
docs/src/content/docs/guides/events-reference.mdx
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
---
|
||||
title: Events Guide
|
||||
description: A practical guide to using events in Wails v3 for application communication and lifecycle management
|
||||
---
|
||||
|
||||
**NOTE: This guide is a work in progress**
|
||||
|
||||
# Events Guide
|
||||
|
||||
Events are the heartbeat of communication in Wails applications. They allow different parts of your application to talk to each other without being tightly coupled. This guide will walk you through everything you need to know about using events effectively in your Wails application.
|
||||
|
||||
## Understanding Wails Events
|
||||
|
||||
Think of events as messages that get broadcast throughout your application. Any part of your application can listen for these messages and react accordingly. This is particularly useful for:
|
||||
|
||||
- **Responding to window changes**: Know when your window is minimized, maximized, or moved
|
||||
- **Handling system events**: React to theme changes or power events
|
||||
- **Custom application logic**: Create your own events for features like data updates or user actions
|
||||
- **Cross-component communication**: Let different parts of your app communicate without direct dependencies
|
||||
|
||||
## Event Naming Convention
|
||||
|
||||
All Wails events follow a namespace pattern to clearly indicate their origin:
|
||||
|
||||
- `common:` - Cross-platform events that work on Windows, macOS, and Linux
|
||||
- `windows:` - Windows-specific events
|
||||
- `mac:` - macOS-specific events
|
||||
- `linux:` - Linux-specific events
|
||||
|
||||
For example:
|
||||
- `common:WindowFocus` - Window gained focus (works everywhere)
|
||||
- `windows:APMSuspend` - System is suspending (Windows only)
|
||||
- `mac:ApplicationDidBecomeActive` - App became active (macOS only)
|
||||
|
||||
## Getting Started with Events
|
||||
|
||||
### Listening to Events (Frontend)
|
||||
|
||||
The most common use case is listening for events in your frontend code:
|
||||
|
||||
```javascript
|
||||
import { Events } from '@wailsio/runtime';
|
||||
|
||||
// Listen for when the window gains focus
|
||||
Events.On('common:WindowFocus', () => {
|
||||
console.log('Window is now focused!');
|
||||
// Maybe refresh some data or resume animations
|
||||
});
|
||||
|
||||
// Listen for theme changes
|
||||
Events.On('common:ThemeChanged', (event) => {
|
||||
console.log('Theme changed:', event.data);
|
||||
// Update your app's theme accordingly
|
||||
});
|
||||
|
||||
// Listen for custom events from your Go backend
|
||||
Events.On('my-app:data-updated', (event) => {
|
||||
console.log('Data updated:', event.data);
|
||||
// Update your UI with the new data
|
||||
});
|
||||
```
|
||||
|
||||
### Emitting Events (Backend)
|
||||
|
||||
From your Go code, you can emit events that your frontend can listen to:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func (a *App) UpdateData() {
|
||||
// Do some data processing...
|
||||
|
||||
// Notify the frontend
|
||||
a.app.EmitEvent("my-app:data-updated", map[string]interface{}{
|
||||
"timestamp": time.Now(),
|
||||
"count": 42,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Removing Event Listeners
|
||||
|
||||
Always clean up your event listeners when they're no longer needed:
|
||||
|
||||
```javascript
|
||||
// Store the handler reference
|
||||
const focusHandler = () => {
|
||||
console.log('Window focused');
|
||||
};
|
||||
|
||||
// Add the listener
|
||||
Events.On('common:WindowFocus', focusHandler);
|
||||
|
||||
// Later, remove it when no longer needed
|
||||
Events.Off('common:WindowFocus', focusHandler);
|
||||
|
||||
// Or remove all listeners for an event
|
||||
Events.Off('common:WindowFocus');
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### 1. Pause/Resume on Window Focus
|
||||
|
||||
Many applications need to pause certain activities when the window loses focus:
|
||||
|
||||
```javascript
|
||||
let animationRunning = true;
|
||||
|
||||
Events.On('common:WindowLostFocus', () => {
|
||||
animationRunning = false;
|
||||
pauseBackgroundTasks();
|
||||
});
|
||||
|
||||
Events.On('common:WindowFocus', () => {
|
||||
animationRunning = true;
|
||||
resumeBackgroundTasks();
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Responding to Theme Changes
|
||||
|
||||
Keep your app in sync with the system theme:
|
||||
|
||||
```javascript
|
||||
Events.On('common:ThemeChanged', (event) => {
|
||||
const isDarkMode = event.data.isDark;
|
||||
|
||||
if (isDarkMode) {
|
||||
document.body.classList.add('dark-theme');
|
||||
document.body.classList.remove('light-theme');
|
||||
} else {
|
||||
document.body.classList.add('light-theme');
|
||||
document.body.classList.remove('dark-theme');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Handling File Drops
|
||||
|
||||
Make your app accept dragged files:
|
||||
|
||||
```javascript
|
||||
Events.On('common:WindowFilesDropped', (event) => {
|
||||
const files = event.data.files;
|
||||
|
||||
files.forEach(file => {
|
||||
console.log('File dropped:', file);
|
||||
// Process the dropped files
|
||||
handleFileUpload(file);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 4. Window Lifecycle Management
|
||||
|
||||
Respond to window state changes:
|
||||
|
||||
```javascript
|
||||
Events.On('common:WindowClosing', () => {
|
||||
// Save user data before closing
|
||||
saveApplicationState();
|
||||
|
||||
// You could also prevent closing by returning false
|
||||
// from a registered window close handler
|
||||
});
|
||||
|
||||
Events.On('common:WindowMaximise', () => {
|
||||
// Adjust UI for maximized view
|
||||
adjustLayoutForMaximized();
|
||||
});
|
||||
|
||||
Events.On('common:WindowRestore', () => {
|
||||
// Return UI to normal state
|
||||
adjustLayoutForNormal();
|
||||
});
|
||||
```
|
||||
|
||||
### 5. Platform-Specific Features
|
||||
|
||||
Handle platform-specific events when needed:
|
||||
|
||||
```javascript
|
||||
// Windows-specific power management
|
||||
Events.On('windows:APMSuspend', () => {
|
||||
console.log('System is going to sleep');
|
||||
saveState();
|
||||
});
|
||||
|
||||
Events.On('windows:APMResumeSuspend', () => {
|
||||
console.log('System woke up');
|
||||
refreshData();
|
||||
});
|
||||
|
||||
// macOS-specific app lifecycle
|
||||
Events.On('mac:ApplicationWillTerminate', () => {
|
||||
console.log('App is about to quit');
|
||||
performCleanup();
|
||||
});
|
||||
```
|
||||
|
||||
## Creating Custom Events
|
||||
|
||||
You can create your own events for application-specific needs:
|
||||
|
||||
### Backend (Go)
|
||||
|
||||
```go
|
||||
// Emit a custom event when data changes
|
||||
func (a *App) ProcessUserData(userData UserData) error {
|
||||
// Process the data...
|
||||
|
||||
// Notify all listeners
|
||||
a.app.EmitEvent("user:data-processed", map[string]interface{}{
|
||||
"userId": userData.ID,
|
||||
"status": "completed",
|
||||
"timestamp": time.Now(),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Emit periodic updates
|
||||
func (a *App) StartMonitoring() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
stats := a.collectStats()
|
||||
a.app.EmitEvent("monitor:stats-updated", stats)
|
||||
}
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
### Frontend (JavaScript)
|
||||
|
||||
```javascript
|
||||
// Listen for your custom events
|
||||
Events.On('user:data-processed', (event) => {
|
||||
const { userId, status, timestamp } = event.data;
|
||||
|
||||
showNotification(`User ${userId} processing ${status}`);
|
||||
updateUIWithNewData();
|
||||
});
|
||||
|
||||
Events.On('monitor:stats-updated', (event) => {
|
||||
updateDashboard(event.data);
|
||||
});
|
||||
```
|
||||
|
||||
## Event Reference
|
||||
|
||||
### Common Events (Cross-platform)
|
||||
|
||||
These events work on all platforms:
|
||||
|
||||
| Event | Description | When to Use |
|
||||
|-------|-------------|-------------|
|
||||
| `common:ApplicationStarted` | Application has fully started | Initialize your app, load saved state |
|
||||
| `common:WindowRuntimeReady` | Wails runtime is ready | Start making Wails API calls |
|
||||
| `common:ThemeChanged` | System theme changed | Update app appearance |
|
||||
| `common:WindowFocus` | Window gained focus | Resume activities, refresh data |
|
||||
| `common:WindowLostFocus` | Window lost focus | Pause activities, save state |
|
||||
| `common:WindowMinimise` | Window was minimized | Pause rendering, reduce resource usage |
|
||||
| `common:WindowMaximise` | Window was maximized | Adjust layout for full screen |
|
||||
| `common:WindowRestore` | Window restored from min/max | Return to normal layout |
|
||||
| `common:WindowClosing` | Window is about to close | Save data, cleanup resources |
|
||||
| `common:WindowFilesDropped` | Files dropped on window | Handle file imports |
|
||||
| `common:WindowDidResize` | Window was resized | Adjust layout, rerender charts |
|
||||
| `common:WindowDidMove` | Window was moved | Update position-dependent features |
|
||||
|
||||
### Platform-Specific Events
|
||||
|
||||
#### Windows Events
|
||||
|
||||
Key events for Windows applications:
|
||||
|
||||
| Event | Description | Use Case |
|
||||
|-------|-------------|----------|
|
||||
| `windows:SystemThemeChanged` | Windows theme changed | Update app colors |
|
||||
| `windows:APMSuspend` | System suspending | Save state, pause operations |
|
||||
| `windows:APMResumeSuspend` | System resumed | Restore state, refresh data |
|
||||
| `windows:APMPowerStatusChange` | Power status changed | Adjust performance settings |
|
||||
|
||||
#### macOS Events
|
||||
|
||||
Important macOS application events:
|
||||
|
||||
| Event | Description | Use Case |
|
||||
|-------|-------------|----------|
|
||||
| `mac:ApplicationDidBecomeActive` | App became active | Resume operations |
|
||||
| `mac:ApplicationDidResignActive` | App became inactive | Pause operations |
|
||||
| `mac:ApplicationWillTerminate` | App will quit | Final cleanup |
|
||||
| `mac:WindowDidEnterFullScreen` | Entered fullscreen | Adjust UI for fullscreen |
|
||||
| `mac:WindowDidExitFullScreen` | Exited fullscreen | Restore normal UI |
|
||||
|
||||
#### Linux Events
|
||||
|
||||
Core Linux window events:
|
||||
|
||||
| Event | Description | Use Case |
|
||||
|-------|-------------|----------|
|
||||
| `linux:SystemThemeChanged` | Desktop theme changed | Update app theme |
|
||||
| `linux:WindowFocusIn` | Window gained focus | Resume activities |
|
||||
| `linux:WindowFocusOut` | Window lost focus | Pause activities |
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use Event Namespaces
|
||||
|
||||
When creating custom events, use namespaces to avoid conflicts:
|
||||
|
||||
```javascript
|
||||
// Good - namespaced events
|
||||
Events.Emit('myapp:user:login');
|
||||
Events.Emit('myapp:data:updated');
|
||||
Events.Emit('myapp:network:connected');
|
||||
|
||||
// Avoid - generic names that might conflict
|
||||
Events.Emit('login');
|
||||
Events.Emit('update');
|
||||
```
|
||||
|
||||
### 2. Clean Up Listeners
|
||||
|
||||
Always remove event listeners when components unmount:
|
||||
|
||||
```javascript
|
||||
// React example
|
||||
useEffect(() => {
|
||||
const handler = (event) => {
|
||||
// Handle event
|
||||
};
|
||||
|
||||
Events.On('common:WindowResize', handler);
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
Events.Off('common:WindowResize', handler);
|
||||
};
|
||||
}, []);
|
||||
```
|
||||
|
||||
### 3. Handle Platform Differences
|
||||
|
||||
Check platform availability when using platform-specific events:
|
||||
|
||||
```javascript
|
||||
import { Platform } from '@wailsio/runtime';
|
||||
|
||||
if (Platform.isWindows) {
|
||||
Events.On('windows:APMSuspend', handleSuspend);
|
||||
} else if (Platform.isMac) {
|
||||
Events.On('mac:ApplicationWillTerminate', handleTerminate);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Don't Overuse Events
|
||||
|
||||
While events are powerful, don't use them for everything:
|
||||
|
||||
- ✅ Use events for: System notifications, lifecycle changes, broadcast updates
|
||||
- ❌ Avoid events for: Direct function returns, single component updates, synchronous operations
|
||||
|
||||
## Debugging Events
|
||||
|
||||
To debug event issues:
|
||||
|
||||
```javascript
|
||||
// Log all events (development only)
|
||||
if (isDevelopment) {
|
||||
const originalOn = Events.On;
|
||||
Events.On = function(eventName, handler) {
|
||||
console.log(`[Event Registered] ${eventName}`);
|
||||
return originalOn.call(this, eventName, function(event) {
|
||||
console.log(`[Event Fired] ${eventName}`, event);
|
||||
return handler(event);
|
||||
});
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Source of Truth
|
||||
|
||||
The complete list of available events can be found in the Wails source code:
|
||||
- Frontend events: [`v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts`](https://github.com/wailsapp/wails/blob/main/v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts)
|
||||
- Backend events: [`v3/pkg/events/events.go`](https://github.com/wailsapp/wails/blob/main/v3/pkg/events/events.go)
|
||||
|
||||
Always refer to these files for the most up-to-date event names and availability.
|
||||
|
||||
## Summary
|
||||
|
||||
Events in Wails provide a powerful, decoupled way to handle communication in your application. By following the patterns and practices in this guide, you can build responsive, platform-aware applications that react smoothly to system changes and user interactions.
|
||||
|
||||
Remember: start with common events for cross-platform compatibility, add platform-specific events when needed, and always clean up your event listeners to prevent memory leaks.
|
||||
|
|
@ -62,6 +62,7 @@ fileAssociations:
|
|||
| description | Description shown in file properties | Windows |
|
||||
| iconName | Name of the icon file (without extension) in the build folder | All |
|
||||
| role | Application's role for this file type (e.g., `Editor`, `Viewer`) | macOS |
|
||||
| mimeType | MIME type for the file (e.g., `image/jpeg`) | macOS |
|
||||
|
||||
## Listening for File Open Events
|
||||
|
||||
|
|
@ -105,6 +106,8 @@ Let's walk through setting up file associations for a simple text editor:
|
|||
Run `wails3 generate icons --help` for more information.
|
||||
:::
|
||||
|
||||
- For macOS add copy statement like `cp build/darwin/documenticon.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources` in the `create:app:bundle:` task.
|
||||
|
||||
2. ### Configure File Associations
|
||||
|
||||
Edit the `build/config.yml` file to add your file associations:
|
||||
|
|
|
|||
584
docs/src/content/docs/guides/gin-routing.mdx
Normal file
584
docs/src/content/docs/guides/gin-routing.mdx
Normal file
|
|
@ -0,0 +1,584 @@
|
|||
---
|
||||
title: Using Gin for Routing
|
||||
description: A comprehensive guide to integrating Gin web framework with Wails v3 applications
|
||||
---
|
||||
|
||||
This guide demonstrates how to integrate the [Gin web framework](https://github.com/gin-gonic/gin) with Wails v3. Gin is a high-performance HTTP web framework written in Go that makes it easy to build web applications and APIs.
|
||||
|
||||
## Introduction
|
||||
|
||||
Wails v3 provides a flexible asset system that allows you to use any HTTP handler, including popular web frameworks like Gin. This integration enables you to:
|
||||
|
||||
- Serve web content using Gin's powerful routing and middleware capabilities
|
||||
- Create RESTful APIs that can be accessed from your Wails application
|
||||
- Leverage Gin's extensive feature set whilst maintaining the benefits of Wails
|
||||
|
||||
## Setting Up Gin with Wails
|
||||
|
||||
To integrate Gin with Wails, you need to create a Gin router and configure it as the asset handler in your Wails application. Here's a step-by-step guide:
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
First, ensure you have the Gin package installed:
|
||||
|
||||
```bash
|
||||
go get -u github.com/gin-gonic/gin
|
||||
```
|
||||
|
||||
### 2. Create a Middleware for Gin
|
||||
|
||||
Create a middleware function that will handle the integration between Wails and Gin:
|
||||
|
||||
```go
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This middleware passes all HTTP requests to the Gin router.
|
||||
|
||||
### 3. Configure Your Gin Router
|
||||
|
||||
Set up your Gin router with routes, middlewares, and handlers:
|
||||
|
||||
```go
|
||||
// Create a new Gin router
|
||||
ginEngine := gin.New() // Using New() instead of Default() to add custom middleware
|
||||
|
||||
// Add middlewares
|
||||
ginEngine.Use(gin.Recovery())
|
||||
ginEngine.Use(LoggingMiddleware()) // Your custom middleware
|
||||
|
||||
// Define routes
|
||||
ginEngine.GET("/", func(c *gin.Context) {
|
||||
// Serve your main page
|
||||
})
|
||||
|
||||
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),
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### 4. Integrate with Wails Application
|
||||
|
||||
Configure your Wails application to use the Gin router as its asset handler:
|
||||
|
||||
```go
|
||||
// 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),
|
||||
},
|
||||
})
|
||||
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Wails + Gin Example",
|
||||
Width: 900,
|
||||
Height: 700,
|
||||
Centered: true,
|
||||
URL: "/", // This will load the route handled by Gin
|
||||
})
|
||||
```
|
||||
|
||||
## Serving Static Content
|
||||
|
||||
There are several ways to serve static content with Gin in a Wails application:
|
||||
|
||||
### Option 1: Using Go's embed Package
|
||||
|
||||
The recommended approach is to use Go's `embed` package to embed static files into your binary:
|
||||
|
||||
```go
|
||||
//go:embed static
|
||||
var staticFiles embed.FS
|
||||
|
||||
// In your main function:
|
||||
ginEngine.StaticFS("/static", http.FS(staticFiles))
|
||||
|
||||
// Serve index.html
|
||||
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)
|
||||
})
|
||||
```
|
||||
|
||||
### Option 2: Serving from Disk
|
||||
|
||||
For development purposes, you might prefer to serve files directly from disk:
|
||||
|
||||
```go
|
||||
// Serve static files from the "static" directory
|
||||
ginEngine.Static("/static", "./static")
|
||||
|
||||
// Serve index.html
|
||||
ginEngine.GET("/", func(c *gin.Context) {
|
||||
c.File("./static/index.html")
|
||||
})
|
||||
```
|
||||
|
||||
## Custom Middleware
|
||||
|
||||
Gin allows you to create custom middleware for various purposes. Here's an example of a logging middleware:
|
||||
|
||||
```go
|
||||
// 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Handling API Requests
|
||||
|
||||
Gin makes it easy to create RESTful APIs. Here's how to define API endpoints:
|
||||
|
||||
```go
|
||||
// GET endpoint
|
||||
ginEngine.GET("/api/users", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, users)
|
||||
})
|
||||
|
||||
// POST endpoint with JSON binding
|
||||
ginEngine.POST("/api/users", func(c *gin.Context) {
|
||||
var newUser User
|
||||
if err := c.ShouldBindJSON(&newUser); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
// Process the new user...
|
||||
c.JSON(http.StatusCreated, newUser)
|
||||
})
|
||||
|
||||
// Path parameters
|
||||
ginEngine.GET("/api/users/:id", func(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
// Find user by ID...
|
||||
c.JSON(http.StatusOK, user)
|
||||
})
|
||||
|
||||
// Query parameters
|
||||
ginEngine.GET("/api/search", func(c *gin.Context) {
|
||||
query := c.DefaultQuery("q", "")
|
||||
limit := c.DefaultQuery("limit", "10")
|
||||
// Perform search...
|
||||
c.JSON(http.StatusOK, results)
|
||||
})
|
||||
```
|
||||
|
||||
## Event Communication
|
||||
|
||||
One of the powerful features of Wails is its event system, which allows for communication between the frontend and backend. When using Gin as a service, you can still leverage this event system by serving the Wails runtime.js file to your frontend.
|
||||
|
||||
### Serving the Wails Runtime
|
||||
|
||||
To enable event communication, you need to serve the Wails runtime.js file at the `/wails/runtime.js` path. Here's how to implement this in your Gin service:
|
||||
|
||||
```go
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/runtime"
|
||||
)
|
||||
|
||||
// GinService implements a Wails service that uses Gin for HTTP handling
|
||||
type GinService struct {
|
||||
ginEngine *gin.Engine
|
||||
app *application.App
|
||||
// Other fields...
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler interface
|
||||
func (s *GinService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Special handling for Wails runtime.js
|
||||
if r.URL.Path == "/wails/runtime.js" {
|
||||
s.serveWailsRuntime(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// All other requests go to the Gin router
|
||||
s.ginEngine.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// serveWailsRuntime serves the Wails runtime.js file
|
||||
func (s *GinService) serveWailsRuntime(w http.ResponseWriter, r *http.Request) {
|
||||
// Open the runtime.js file from the public runtime package
|
||||
runtimeFile, err := runtime.Assets.Open(runtime.RuntimeJSPath)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to access runtime assets", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer runtimeFile.Close()
|
||||
|
||||
// Set the content type
|
||||
w.Header().Set("Content-Type", "application/javascript")
|
||||
|
||||
// Copy the file to the response
|
||||
_, err = io.Copy(w, runtimeFile)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to serve runtime.js", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Handling Events
|
||||
|
||||
You'll also need to add an endpoint to handle events from the frontend. This endpoint will bridge the gap between the HTTP requests and the Wails event system:
|
||||
|
||||
```go
|
||||
// In your setupRoutes method
|
||||
func (s *GinService) setupRoutes() {
|
||||
// Event handling endpoint
|
||||
s.ginEngine.POST("/events/emit", func(c *gin.Context) {
|
||||
var eventData struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&eventData); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Process the event using the Wails event system
|
||||
s.app.EmitEvent(eventData.Name, eventData.Data)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "Event processed successfully",
|
||||
})
|
||||
})
|
||||
|
||||
// Other routes...
|
||||
}
|
||||
```
|
||||
|
||||
### Using Events in the Frontend
|
||||
|
||||
In your frontend HTML, include the Wails runtime.js script and use the event API:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="/wails/runtime.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<button id="triggerEvent">Trigger Event</button>
|
||||
<pre id="eventResponse"></pre>
|
||||
|
||||
<script>
|
||||
// Emit an event to the backend
|
||||
document.getElementById('triggerEvent').addEventListener('click', () => {
|
||||
ce.Events.Emit("my-event", {
|
||||
message: "Hello from the frontend!",
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// Listen for events from the backend
|
||||
ce.Events.On("response-event", (data) => {
|
||||
document.getElementById('eventResponse').textContent =
|
||||
JSON.stringify(data, null, 2);
|
||||
});
|
||||
|
||||
// For the runtime.js stub implementation, add this polyfill
|
||||
if (window.ce && !window.ce._isNative) {
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = async function(url, options) {
|
||||
if (typeof url === 'string' && url.includes('/wails/events/emit')) {
|
||||
const req = new Request(url, options);
|
||||
const data = await req.json();
|
||||
// Forward the event to the backend through a regular API call
|
||||
await fetch('/api/events/emit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
return new Response(JSON.stringify({ success: true }), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
return originalFetch.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
This approach allows you to use the Wails event system seamlessly with your Gin service, providing a consistent experience across your application.
|
||||
|
||||
## Interacting with Wails
|
||||
|
||||
Your Gin-served web content can interact with Wails features like events and bindings. To enable this interaction, you
|
||||
must use the JavaScript API package `@wailsio/runtime`.
|
||||
|
||||
### Handling Wails Events in Go
|
||||
|
||||
```go
|
||||
// Register event handler
|
||||
app.OnEvent("my-event", func(event *application.CustomEvent) {
|
||||
log.Printf("Received event from frontend: %v", event.Data)
|
||||
// Process the event...
|
||||
})
|
||||
```
|
||||
|
||||
### Emitting Events from JavaScript
|
||||
|
||||
```html
|
||||
<script>
|
||||
document.getElementById('callApi').addEventListener('click', async () => {
|
||||
try {
|
||||
const response = await fetch('/api/hello');
|
||||
const data = await response.json();
|
||||
document.getElementById('apiResult').textContent = JSON.stringify(data, null, 2);
|
||||
} catch (error) {
|
||||
console.error('Error calling API:', error);
|
||||
document.getElementById('apiResult').textContent = 'Error: ' + error.message;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Customising Gin's Mode
|
||||
|
||||
Gin has three modes: debug, release, and test. For production applications, you should use release mode:
|
||||
|
||||
```go
|
||||
// Set Gin to release mode
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
|
||||
// Create a new Gin router
|
||||
ginEngine := gin.New()
|
||||
```
|
||||
|
||||
You can use Go's build tags to set the mode based on the build environment:
|
||||
|
||||
```go[main_prod.go]
|
||||
// +build production
|
||||
|
||||
var ginMode = gin.ReleaseMode
|
||||
```
|
||||
|
||||
```go[main_dev.go]
|
||||
// +build !production
|
||||
|
||||
var ginMode = gin.DebugMode
|
||||
```
|
||||
|
||||
```go [main.go]
|
||||
// In your main function:
|
||||
gin.SetMode(ginMode)
|
||||
```
|
||||
|
||||
### Handling WebSockets
|
||||
|
||||
You can integrate WebSockets with Gin using libraries like Gorilla WebSocket:
|
||||
|
||||
```go
|
||||
import "github.com/gorilla/websocket"
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true // Allow all connections
|
||||
},
|
||||
}
|
||||
|
||||
// In your route handler:
|
||||
ginEngine.GET("/ws", func(c *gin.Context) {
|
||||
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Handle WebSocket connection...
|
||||
})
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a complete example of integrating Gin with Wails:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"log"
|
||||
"net/http"
|
||||
"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 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.OnEvent("gin-button-clicked", func(event *application.CustomEvent) {
|
||||
log.Printf("Received event from frontend: %v", event.Data)
|
||||
})
|
||||
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Wails + Gin Example",
|
||||
Width: 900,
|
||||
Height: 700,
|
||||
URL: "/",
|
||||
})
|
||||
|
||||
// Run the app
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Use Go's embed Package:** Embed static files into your binary for better distribution.
|
||||
- **Separate Concerns:** Keep your API logic separate from your UI logic.
|
||||
- **Error Handling:** Implement proper error handling in both Gin routes and frontend code.
|
||||
- **Security:** Be mindful of security considerations, especially when handling user input.
|
||||
- **Performance:** Use Gin's release mode in production for better performance.
|
||||
- **Testing:** Write tests for your Gin routes using Gin's testing utilities.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Integrating Gin with Wails provides a powerful combination for building desktop applications with web technologies. Gin's performance and feature set complement Wails' desktop integration capabilities, allowing you to create sophisticated applications that leverage the best of both worlds.
|
||||
|
||||
For more information, refer to the [Gin documentation](https://github.com/gin-gonic/gin) and the [Wails documentation](https://wails.io).
|
||||
552
docs/src/content/docs/guides/gin-services.mdx
Normal file
552
docs/src/content/docs/guides/gin-services.mdx
Normal file
|
|
@ -0,0 +1,552 @@
|
|||
---
|
||||
title: Using Gin for Services
|
||||
description: A guide to integrating the Gin web framework with Wails v3 Services
|
||||
---
|
||||
|
||||
# Using Gin for Services
|
||||
|
||||
The Gin web framework is a popular choice for building HTTP services in Go. With Wails v3, you can easily integrate Gin-based services into your application, providing a powerful way to handle HTTP requests, implement RESTful APIs, and serve web content.
|
||||
|
||||
This guide will walk you through creating a Gin-based service that can be mounted at a specific route in your Wails application. We'll build a complete example that demonstrates how to:
|
||||
|
||||
1. Create a Gin-based service
|
||||
2. Implement the Wails Service interface
|
||||
3. Set up routes and middleware
|
||||
4. Integrate with the Wails event system
|
||||
5. Interact with the service from the frontend
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, make sure you have:
|
||||
|
||||
- Wails v3 installed
|
||||
- Basic knowledge of Go and the Gin framework
|
||||
- Familiarity with HTTP concepts and RESTful APIs
|
||||
|
||||
You'll need to add the Gin framework to your project:
|
||||
|
||||
```bash
|
||||
go get github.com/gin-gonic/gin
|
||||
```
|
||||
|
||||
## Creating a Gin-Based Service
|
||||
|
||||
Let's start by creating a Gin service that implements the Wails Service interface. Our service will manage a collection of users and provide API endpoints for retrieving and creating user records.
|
||||
|
||||
### 1. Define Your Data Models
|
||||
|
||||
First, define the data structures your service will work with:
|
||||
|
||||
```go
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"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"`
|
||||
}
|
||||
|
||||
// EventData represents data sent in events
|
||||
type EventData struct {
|
||||
Message string `json:"message"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create Your Service Structure
|
||||
|
||||
Next, define the service structure that will hold your Gin router and any state your service needs to maintain:
|
||||
|
||||
```go
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
// Define routes
|
||||
service.setupRoutes()
|
||||
|
||||
return service
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Implement the Service Interface
|
||||
|
||||
Implement the required methods for the Wails Service interface:
|
||||
|
||||
```go
|
||||
// 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 {
|
||||
// Store the application instance for later use
|
||||
s.app = application.Get()
|
||||
|
||||
// Register an event handler that can be triggered from the frontend
|
||||
s.app.OnEvent("gin-api-event", func(event *application.CustomEvent) {
|
||||
// Log the event data
|
||||
s.app.Logger.Info("Received event from frontend", "data", event.Data)
|
||||
|
||||
// Emit an event back to the frontend
|
||||
s.app.EmitEvent("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 resources if needed
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Implement the http.Handler Interface
|
||||
|
||||
To make your service mountable at a specific route, implement the `http.Handler` interface. This single method, `ServeHTTP`, is the gateway for all HTTP requests to your service. It delegates the request handling to the Gin router, allowing you to use all of Gin's powerful features for routing and middleware.
|
||||
|
||||
```go
|
||||
// ServeHTTP implements the http.Handler interface
|
||||
func (s *GinService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// All requests go to the Gin router
|
||||
s.ginEngine.ServeHTTP(w, r)
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Set Up Your Routes
|
||||
|
||||
Define your API routes in a separate method for better organisation. This approach keeps your code clean and makes it easier to understand the structure of your API. The Gin router provides a fluent API for defining routes, including support for route groups, which help organise related endpoints.
|
||||
|
||||
```go
|
||||
// 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
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// 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.EmitEvent("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"})
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Create Custom Middleware
|
||||
|
||||
You can create custom Gin middleware to enhance your service. Middleware functions in Gin are executed in the order they are added to the router and can perform tasks such as logging, authentication, and error handling. This example shows a simple logging middleware that records request details.
|
||||
|
||||
```go
|
||||
// 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
|
||||
log.Printf("[GIN] %s %s %d %s", c.Request.Method, c.Request.URL.Path, c.Writer.Status(), latency)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Registering Your Service
|
||||
|
||||
To use your Gin-based service in a Wails application, you need to register it with the application and specify the route where it should be mounted. This is done when creating the Wails application instance. The route you specify becomes the base path for all endpoints defined in your Gin router.
|
||||
|
||||
```go
|
||||
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),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
In this example, the Gin service is mounted at the `/api` route. This means that if your Gin router has an endpoint defined as `/info`, it will be accessible at `/api/info` in your application. This approach allows you to organise your API endpoints logically and avoid conflicts with other parts of your application.
|
||||
|
||||
## Integrating with the Wails Event System
|
||||
|
||||
One of the powerful features of using Gin with Wails Services is the ability to seamlessly integrate with the Wails event system. This allows for real-time communication between your backend service and the frontend.
|
||||
|
||||
In your service's `ServiceStartup` method, you can register event handlers to process events from the frontend:
|
||||
|
||||
```go
|
||||
s.app.OnEvent("gin-api-event", func(event *application.CustomEvent) {
|
||||
// Log the event data
|
||||
s.app.Logger.Info("Received event from frontend", "data", event.Data)
|
||||
|
||||
// Emit an event back to the frontend
|
||||
s.app.EmitEvent("gin-api-response", map[string]interface{}{
|
||||
"message": "Response from Gin API Service",
|
||||
"time": time.Now().Format(time.RFC3339),
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
You can also emit events to the frontend from your Gin routes or other parts of your service:
|
||||
|
||||
```go
|
||||
// After creating a new user
|
||||
s.app.EmitEvent("user-created", newUser)
|
||||
```
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
To interact with your Gin service from the frontend, you'll need to import the Wails runtime, make HTTP requests to your API endpoints, and use the Wails event system for real-time communication.
|
||||
|
||||
For production use, it's recommended to use the `@wailsio/runtime` package instead of directly importing `/wails/runtime.js`. This ensures type safety, better IDE support, version management through npm, and compatibility with modern JavaScript tooling.
|
||||
|
||||
Install the package:
|
||||
```bash
|
||||
npm install @wailsio/runtime
|
||||
```
|
||||
|
||||
Then use it in your code:
|
||||
```javascript
|
||||
import * as wails from '@wailsio/runtime';
|
||||
|
||||
// Event emission
|
||||
wails.Events.Emit('gin-api-event', eventData);
|
||||
```
|
||||
|
||||
Here's an example of how to set up frontend integration:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Gin Service Example</title>
|
||||
<!-- Styles omitted for brevity -->
|
||||
</head>
|
||||
<body>
|
||||
<h1>Gin Service Example</h1>
|
||||
|
||||
<div class="card">
|
||||
<h2>API Endpoints</h2>
|
||||
<p>Try the Gin API endpoints mounted at /api:</p>
|
||||
|
||||
<button id="getInfo">Get Service Info</button>
|
||||
<button id="getUsers">Get All Users</button>
|
||||
<button id="getUser">Get User by ID</button>
|
||||
<button id="createUser">Create User</button>
|
||||
<button id="deleteUser">Delete User</button>
|
||||
|
||||
<div id="apiResult">
|
||||
<pre id="apiResponse">Results will appear here...</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Event Communication</h2>
|
||||
<p>Trigger an event to communicate with the Gin service:</p>
|
||||
|
||||
<button id="triggerEvent">Trigger Event</button>
|
||||
|
||||
<div id="eventResult">
|
||||
<pre id="eventResponse">Event responses will appear here...</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" id="createUserForm" style="display: none; border: 2px solid #0078d7;">
|
||||
<h2>Create New User</h2>
|
||||
|
||||
<div>
|
||||
<label for="userName">Name:</label>
|
||||
<input type="text" id="userName" placeholder="Enter name">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="userEmail">Email:</label>
|
||||
<input type="email" id="userEmail" placeholder="Enter email">
|
||||
</div>
|
||||
|
||||
<button id="submitUser">Submit</button>
|
||||
<button id="cancelCreate">Cancel</button>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
// Import the Wails runtime
|
||||
// Note: In production, use '@wailsio/runtime' instead
|
||||
import * as wails from "/wails/runtime.js";
|
||||
|
||||
// Helper function to fetch API endpoints
|
||||
async function fetchAPI(endpoint, options = {}) {
|
||||
try {
|
||||
const response = await fetch(`/api${endpoint}`, options);
|
||||
const data = await response.json();
|
||||
|
||||
document.getElementById('apiResponse').textContent = JSON.stringify(data, null, 2);
|
||||
return data;
|
||||
} catch (error) {
|
||||
document.getElementById('apiResponse').textContent = `Error: ${error.message}`;
|
||||
console.error('API Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners for API buttons
|
||||
document.getElementById('getInfo').addEventListener('click', () => {
|
||||
fetchAPI('/info');
|
||||
});
|
||||
|
||||
document.getElementById('getUsers').addEventListener('click', () => {
|
||||
fetchAPI('/users');
|
||||
});
|
||||
|
||||
document.getElementById('getUser').addEventListener('click', async () => {
|
||||
const userId = prompt('Enter user ID:');
|
||||
if (userId) {
|
||||
await fetchAPI(`/users/${userId}`);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('createUser').addEventListener('click', () => {
|
||||
const form = document.getElementById('createUserForm');
|
||||
form.style.display = 'block';
|
||||
form.scrollIntoView({ behavior: 'smooth' });
|
||||
});
|
||||
|
||||
document.getElementById('cancelCreate').addEventListener('click', () => {
|
||||
document.getElementById('createUserForm').style.display = 'none';
|
||||
});
|
||||
|
||||
document.getElementById('submitUser').addEventListener('click', async () => {
|
||||
const name = document.getElementById('userName').value;
|
||||
const email = document.getElementById('userEmail').value;
|
||||
|
||||
if (!name || !email) {
|
||||
alert('Please enter both name and email');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await fetchAPI('/users', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ name, email })
|
||||
});
|
||||
|
||||
document.getElementById('createUserForm').style.display = 'none';
|
||||
document.getElementById('userName').value = '';
|
||||
document.getElementById('userEmail').value = '';
|
||||
|
||||
// Automatically fetch the updated user list
|
||||
await fetchAPI('/users');
|
||||
|
||||
// Show a success message
|
||||
const apiResponse = document.getElementById('apiResponse');
|
||||
const currentData = JSON.parse(apiResponse.textContent);
|
||||
apiResponse.textContent = JSON.stringify({
|
||||
message: "User created successfully!",
|
||||
users: currentData
|
||||
}, null, 2);
|
||||
} catch (error) {
|
||||
console.error('Error creating user:', error);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('deleteUser').addEventListener('click', async () => {
|
||||
const userId = prompt('Enter user ID to delete:');
|
||||
if (userId) {
|
||||
try {
|
||||
await fetchAPI(`/users/${userId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
// Show success message
|
||||
document.getElementById('apiResponse').textContent = JSON.stringify({
|
||||
message: `User with ID ${userId} deleted successfully`
|
||||
}, null, 2);
|
||||
|
||||
// Refresh the user list
|
||||
setTimeout(() => fetchAPI('/users'), 1000);
|
||||
} catch (error) {
|
||||
console.error('Error deleting user:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Using Wails Events API for event communication
|
||||
document.getElementById('triggerEvent').addEventListener('click', async () => {
|
||||
// Display the event being sent
|
||||
document.getElementById('eventResponse').textContent = JSON.stringify({
|
||||
status: "Sending event to backend...",
|
||||
data: { timestamp: new Date().toISOString() }
|
||||
}, null, 2);
|
||||
|
||||
// Use the Wails runtime to emit an event
|
||||
const eventData = {
|
||||
message: "Hello from the frontend!",
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
wails.Events.Emit({name: 'gin-api-event', data: eventData});
|
||||
});
|
||||
|
||||
// Set up event listener for responses from the backend
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
// Register event listener using Wails runtime
|
||||
wails.Events.On("gin-api-response", (data) => {
|
||||
document.getElementById('eventResponse').textContent = JSON.stringify(data, null, 2);
|
||||
});
|
||||
|
||||
// Also listen for user-created events
|
||||
wails.Events.On("user-created", (data) => {
|
||||
document.getElementById('eventResponse').textContent = JSON.stringify({
|
||||
event: "user-created",
|
||||
user: data
|
||||
}, null, 2);
|
||||
});
|
||||
|
||||
// Initial API call to get service info
|
||||
fetchAPI('/info');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Closing Thoughts
|
||||
|
||||
Integrating the Gin web framework with Wails v3 Services provides a powerful and flexible approach to building modular, maintainable web applications. By leveraging Gin's routing and middleware capabilities alongside the Wails event system, you can create rich, interactive applications with clean separation of concerns.
|
||||
|
||||
The complete example code for this guide can be found in the Wails repository under `v3/examples/gin-service`.
|
||||
|
|
@ -7,10 +7,10 @@ Wails v3 provides a powerful menu system that allows you to create both applicat
|
|||
|
||||
### Creating a Menu
|
||||
|
||||
To create a new menu, use the `NewMenu()` method from your application instance:
|
||||
To create a new menu, use the `New()` method from the Menus manager:
|
||||
|
||||
```go
|
||||
menu := application.NewMenu()
|
||||
menu := app.Menus.New()
|
||||
```
|
||||
|
||||
### Adding Menu Items
|
||||
|
|
@ -59,10 +59,10 @@ submenu.Add("Save")
|
|||
#### Combining menus
|
||||
A menu can be added into another menu by appending or prepending it.
|
||||
```go
|
||||
menu := application.NewMenu()
|
||||
menu := app.Menus.New()
|
||||
menu.Add("First Menu")
|
||||
|
||||
secondaryMenu := application.NewMenu()
|
||||
secondaryMenu := app.Menus.New()
|
||||
secondaryMenu.Add("Second Menu")
|
||||
|
||||
// insert 'secondaryMenu' after 'menu'
|
||||
|
|
@ -88,7 +88,7 @@ In some cases it'll be better to construct a whole new menu if you are working w
|
|||
This will clear all items on an existing menu and allows you to add items again.
|
||||
|
||||
```go
|
||||
menu := application.NewMenu()
|
||||
menu := app.Menus.New()
|
||||
menu.Add("Waiting for update...")
|
||||
|
||||
// after certain logic, the menu has to be updated
|
||||
|
|
@ -107,7 +107,7 @@ so be sure to manage your menus carefully.
|
|||
If you want to clear and release a menu, use the `Destroy()` method:
|
||||
|
||||
```go
|
||||
menu := application.NewMenu()
|
||||
menu := app.Menus.New()
|
||||
menu.Add("Waiting for update...")
|
||||
|
||||
// after certain logic, the menu has to be destroyed
|
||||
|
|
@ -265,7 +265,7 @@ These roles can be used to add individual menu items:
|
|||
Here's an example showing how to use both complete menus and individual roles:
|
||||
|
||||
```go
|
||||
menu := application.NewMenu()
|
||||
menu := app.Menus.New()
|
||||
|
||||
// Add complete menu structures
|
||||
menu.AddRole(application.AppMenu) // macOS only
|
||||
|
|
@ -287,8 +287,8 @@ Application menus are the menus that appear at the top of your application windo
|
|||
|
||||
### Application Menu Behaviour
|
||||
|
||||
When you set an application menu using `app.SetMenu()`, it becomes the main menu on macOS.
|
||||
Menus are set on a pre-window basis for Windows/Linux.
|
||||
When you set an application menu using `app.Menus.Set()`, it becomes the main menu on macOS.
|
||||
Menus are set on a per-window basis for Windows/Linux.
|
||||
|
||||
```go
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
|
|
@ -306,19 +306,19 @@ func main() {
|
|||
app := application.New(application.Options{})
|
||||
|
||||
// Create application menu
|
||||
appMenu := application.NewMenu()
|
||||
appMenu := app.Menus.New()
|
||||
fileMenu := appMenu.AddSubmenu("File")
|
||||
fileMenu.Add("New").OnClick(func(ctx *application.Context) {
|
||||
// This will be available in all windows unless overridden
|
||||
window := app.CurrentWindow()
|
||||
window := app.Windows.Current()
|
||||
window.SetTitle("New Window")
|
||||
})
|
||||
|
||||
// Set as application menu - this is for macOS
|
||||
app.SetMenu(appMenu)
|
||||
app.Menus.Set(appMenu)
|
||||
|
||||
// Window with custom menu on Windows
|
||||
customMenu := application.NewMenu()
|
||||
customMenu := app.Menus.New()
|
||||
customMenu.Add("Custom Action")
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Custom Menu",
|
||||
|
|
@ -366,6 +366,10 @@ You can control when the default context menu appears using the `--default-conte
|
|||
</div>
|
||||
```
|
||||
|
||||
:::note
|
||||
This feature will only work as expected after the runtime [has been initialised](../../learn/runtime#initialisation).
|
||||
:::
|
||||
|
||||
#### Nested Context Menu Behavior
|
||||
|
||||
When using the `--default-contextmenu` property on nested elements, the following rules apply:
|
||||
|
|
@ -411,7 +415,8 @@ When creating a custom context menu, you provide a unique identifier (name) that
|
|||
|
||||
```go
|
||||
// Create a context menu with identifier "imageMenu"
|
||||
contextMenu := application.NewContextMenu("imageMenu")
|
||||
contextMenu := application.NewContextMenu()
|
||||
app.ContextMenus.Add("imageMenu", contextMenu)
|
||||
```
|
||||
|
||||
The name parameter ("imageMenu" in this example) serves as a unique identifier that will be used to:
|
||||
|
|
@ -470,7 +475,8 @@ Here's a complete example of implementing a custom context menu for an image gal
|
|||
|
||||
```go
|
||||
// Backend: Create the context menu
|
||||
imageMenu := application.NewContextMenu("imageMenu")
|
||||
imageMenu := application.NewContextMenu()
|
||||
app.ContextMenus.Add("imageMenu", imageMenu)
|
||||
|
||||
// Add relevant operations
|
||||
imageMenu.Add("View Full Size").OnClick(func(ctx *application.Context) {
|
||||
|
|
|
|||
138
docs/src/content/docs/guides/msix-packaging.mdx
Normal file
138
docs/src/content/docs/guides/msix-packaging.mdx
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
---
|
||||
title: MSIX Packaging (Windows)
|
||||
description: Guide for creating MSIX packages with Wails v3
|
||||
---
|
||||
|
||||
# MSIX Packaging (Windows)
|
||||
|
||||
Wails v3 can generate modern **MSIX** installers for Windows applications, providing a cleaner, safer and Store-ready alternative to traditional **NSIS** or plain `.exe` bundles.
|
||||
|
||||
This guide walks through:
|
||||
|
||||
* Prerequisites & tool installation
|
||||
* Building your app as an MSIX package
|
||||
* Signing the package
|
||||
* Command-line reference
|
||||
* Troubleshooting
|
||||
|
||||
---
|
||||
|
||||
## 1. Prerequisites
|
||||
|
||||
| Requirement | Notes |
|
||||
|-------------|-------|
|
||||
| **Windows 10 1809+ / Windows 11** | MSIX is only supported on Windows. |
|
||||
| **Windows SDK** (for `MakeAppx.exe` & `signtool.exe`) | Install from the [Windows SDK download page](https://developer.microsoft.com/windows/downloads/windows-sdk/). |
|
||||
| **Microsoft MSIX Packaging Tool** (optional) | Available from the Microsoft Store – provides a GUI & CLI. |
|
||||
| **Code-signing certificate** (recommended) | A `.pfx` file generated by your CA or `New-SelfSignedCertificate`. |
|
||||
|
||||
> **Tip:** Wails ships a Task that opens the download pages for you:
|
||||
|
||||
```bash
|
||||
# installs MakeAppx / signtool (via Windows SDK) and the MSIX Packaging Tool
|
||||
wails3 task windows:install:msix:tools
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Building an MSIX package
|
||||
|
||||
### 2.1 Quick CLI
|
||||
|
||||
```bash
|
||||
# Production build + MSIX
|
||||
wails3 tool msix \
|
||||
--name MyApp \ # executable name
|
||||
--executable build/bin/MyApp.exe \
|
||||
--arch x64 \ # x64, x86 or arm64
|
||||
--out build/bin/MyApp-x64.msix
|
||||
```
|
||||
|
||||
The command will:
|
||||
|
||||
1. Create a temporary layout (`AppxManifest.xml`, `Assets/`).
|
||||
2. Call **MakeAppx.exe** (default) or **MsixPackagingTool.exe** if `--use-msix-tool` is passed.
|
||||
3. Optionally sign the package (see §3).
|
||||
|
||||
### 2.2 Using the generated Taskfile
|
||||
|
||||
When you ran `wails init`, Wails created `build/windows/Taskfile.yml`.
|
||||
Packaging with MSIX is a one-liner:
|
||||
|
||||
```bash
|
||||
# default=nsis, override FORMAT
|
||||
wails3 task windows:package FORMAT=msix
|
||||
```
|
||||
|
||||
Output goes to `build/bin/MyApp-<arch>.msix`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Signing the package
|
||||
|
||||
Windows will refuse unsigned MSIX packages unless you enable developer-mode, so signing is strongly recommended.
|
||||
|
||||
```bash
|
||||
wails3 tool msix \
|
||||
--cert build/cert/CodeSign.pfx \
|
||||
--cert-password "pfx-password" \
|
||||
--publisher "CN=MyCompany" \
|
||||
--out build/bin/MyApp.msix
|
||||
```
|
||||
|
||||
* If you pass `--cert`, Wails automatically runs `signtool sign …`.
|
||||
* `--publisher` sets the `Publisher` field inside `AppxManifest.xml`.
|
||||
It **must** match the subject of your certificate.
|
||||
|
||||
---
|
||||
|
||||
## 4. Command-line reference
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `--config` | `wails.json` | Project config with **Info** & `fileAssociations`. |
|
||||
| `--name` | — | Executable name inside the package (no spaces). |
|
||||
| `--executable` | — | Path to the built `.exe`. |
|
||||
| `--arch` | `x64` | `x64`, `x86`, or `arm64`. |
|
||||
| `--out` | `<name>.msix` | Output path / filename. |
|
||||
| `--publisher` | `CN=<CompanyName>` | Publisher string in the manifest. |
|
||||
| `--cert` | ― | Path to `.pfx` certificate for signing. |
|
||||
| `--cert-password` | ― | Password for the `.pfx`. |
|
||||
| `--use-msix-tool` | `false` | Use **MsixPackagingTool.exe** instead of **MakeAppx.exe**. |
|
||||
| `--use-makeappx` | `true` | Force MakeAppx even if the MSIX Tool is installed. |
|
||||
|
||||
---
|
||||
|
||||
## 5. File associations
|
||||
|
||||
Wails automatically injects file associations declared in `wails.json` into the package manifest:
|
||||
|
||||
```json
|
||||
"fileAssociations": [
|
||||
{ "ext": "wails", "name": "Wails Project", "description": "Wails file", "role": "Editor" }
|
||||
]
|
||||
```
|
||||
|
||||
After installation, Windows will offer your app as a handler for these extensions.
|
||||
|
||||
---
|
||||
|
||||
## 6. Troubleshooting
|
||||
|
||||
| Problem | Solution |
|
||||
|---------|----------|
|
||||
| `MakeAppx.exe not found` | Install the Windows SDK and restart the terminal. |
|
||||
| `signtool.exe not found` | Same as above – both live in the SDK’s *bin* folder. |
|
||||
| *Package cannot be installed because publisher mismatch* | The certificate subject (CN) must match `--publisher`. |
|
||||
| *The certificate is not trusted* | Import the certificate into **Trusted Root Certification Authorities** or use a publicly trusted code-signing cert. |
|
||||
| Need GUI | Install **MSIX Packaging Tool** from the store and run `MsixPackagingTool.exe`. The template generated by Wails is fully compatible. |
|
||||
|
||||
---
|
||||
|
||||
## 7. Next steps
|
||||
|
||||
* [Windows Installer (NSIS) guide](./windows-installer.mdx) – legacy format.
|
||||
* [Cross-platform update mechanism](../updates.mdx) – coming soon.
|
||||
* Join the community on Discord to share feedback!
|
||||
|
||||
Happy packaging!
|
||||
151
docs/src/content/docs/guides/windows-uac.mdx
Normal file
151
docs/src/content/docs/guides/windows-uac.mdx
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
---
|
||||
title: Windows UAC Configuration
|
||||
sidebar:
|
||||
order: 11
|
||||
---
|
||||
|
||||
import {Badge} from '@astrojs/starlight/components';
|
||||
|
||||
Relevant Platforms: <Badge text="Windows" variant="note" />
|
||||
<br/>
|
||||
|
||||
Windows User Account Control (UAC) determines the execution privileges of your Wails application. By default, Wails v3 applications include explicit UAC configuration in their Windows manifest, ensuring consistent behavior across different machines.
|
||||
|
||||
## UAC Execution Levels
|
||||
|
||||
Windows applications can request different execution levels through their manifest file. Wails v3 automatically includes UAC configuration with a default execution level that you can customize based on your application's needs.
|
||||
|
||||
### Available Execution Levels
|
||||
|
||||
| Level | Description | Use Case |
|
||||
|-------|-------------|----------|
|
||||
| `asInvoker` | Runs with the same privileges as the parent process | Default for most applications |
|
||||
| `highestAvailable` | Runs with the highest privileges available to the user | Applications that may need elevated access |
|
||||
| `requireAdministrator` | Always requires administrator privileges | System utilities, installers |
|
||||
|
||||
### Default Configuration
|
||||
|
||||
Wails v3 applications include a default UAC configuration in their Windows manifest:
|
||||
|
||||
```xml
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
```
|
||||
|
||||
This configuration ensures your application:
|
||||
- Runs with the same privileges as the launching process
|
||||
- Does not require elevation by default
|
||||
- Works consistently across different machines
|
||||
- Does not trigger UAC prompts for normal users
|
||||
|
||||
## Customizing UAC Configuration
|
||||
|
||||
Since Wails v3 encourages users to customize their build assets, you can modify the UAC configuration by editing your Windows manifest template directly.
|
||||
|
||||
### Locating the Manifest Template
|
||||
|
||||
The Windows manifest template is located at:
|
||||
```
|
||||
build/windows/wails.exe.manifest
|
||||
```
|
||||
|
||||
### Modifying the Execution Level
|
||||
|
||||
To change the execution level, edit the `level` attribute in the `requestedExecutionLevel` element:
|
||||
|
||||
```xml title="build/windows/wails.exe.manifest"
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
#### Standard Application (Default)
|
||||
Most applications should use the default `asInvoker` level:
|
||||
|
||||
```xml
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
```
|
||||
|
||||
#### System Utility
|
||||
Applications that need elevated access when available:
|
||||
|
||||
```xml
|
||||
<requestedExecutionLevel level="highestAvailable" uiAccess="false"/>
|
||||
```
|
||||
|
||||
#### Administrative Tool
|
||||
Applications that always require administrator privileges:
|
||||
|
||||
```xml
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
```
|
||||
|
||||
## UI Access
|
||||
|
||||
The `uiAccess` attribute controls whether your application can interact with higher-privilege UI elements. In most cases, this should remain `false`.
|
||||
|
||||
Set to `true` only if your application needs to:
|
||||
- Send input to other applications
|
||||
- Drive the UI of other applications
|
||||
- Access UI elements of higher-privilege processes
|
||||
|
||||
:::caution[UI Access Requirements]
|
||||
Setting `uiAccess="true"` requires your application to be:
|
||||
- Digitally signed with a certificate from a trusted certificate authority
|
||||
- Installed in a secure location (Program Files or Windows\System32)
|
||||
:::
|
||||
|
||||
## Building with Custom UAC Settings
|
||||
|
||||
After modifying your manifest template, build your application normally:
|
||||
|
||||
```bash
|
||||
wails3 build
|
||||
```
|
||||
|
||||
The build process will automatically embed your custom UAC configuration into the executable.
|
||||
|
||||
## Verifying UAC Configuration
|
||||
|
||||
You can verify that your UAC settings are properly embedded using the `go-winres` tool:
|
||||
|
||||
```bash
|
||||
go-winres extract --in your-app.exe --out extracted-resources/
|
||||
```
|
||||
|
||||
Then examine the extracted manifest file to confirm your UAC configuration is present.
|
||||
|
||||
:::tip[Manifest Persistence]
|
||||
Unlike some other frameworks, Wails v3's UAC configuration is embedded directly into the executable during compilation, ensuring it persists when the application is copied to other machines.
|
||||
:::
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### UAC Prompts Not Appearing
|
||||
If you set `requireAdministrator` but don't see UAC prompts:
|
||||
- Verify the manifest is properly embedded in your executable
|
||||
- Check that you're not running from an already-elevated process
|
||||
- Ensure the manifest syntax is valid XML
|
||||
|
||||
### Application Not Starting
|
||||
If your application fails to start after UAC changes:
|
||||
- Check the manifest syntax for XML errors
|
||||
- Verify the execution level value is valid
|
||||
- Try reverting to `asInvoker` to isolate the issue
|
||||
|
||||
### Inconsistent Behavior Across Machines
|
||||
If UAC behavior differs between machines:
|
||||
- Ensure the manifest is embedded in the executable (not external)
|
||||
- Check that the executable wasn't modified after building
|
||||
- Verify Windows UAC settings are enabled on the target machine
|
||||
|
|
@ -64,11 +64,11 @@ import CardAnimation from '../../components/CardAnimation.astro';
|
|||
go install github.com/wailsapp/wails/v3/cmd/wails3@latest
|
||||
|
||||
# Create a new project
|
||||
wails init -n myproject
|
||||
wails3 init -n myproject
|
||||
|
||||
# Run your project
|
||||
cd myproject
|
||||
wails dev
|
||||
wails3 dev
|
||||
```
|
||||
</Card>
|
||||
|
||||
|
|
|
|||
342
docs/src/content/docs/learn/advanced-binding.mdx
Normal file
342
docs/src/content/docs/learn/advanced-binding.mdx
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
---
|
||||
title: Advanced Binding Techniques
|
||||
sidebar:
|
||||
order: 22
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
|
||||
This guide covers advanced techniques for customizing and optimizing the binding generation process in Wails v3.
|
||||
|
||||
## Customizing Generated Code with Directives
|
||||
|
||||
### Injecting Custom Code
|
||||
|
||||
The `//wails:inject` directive allows you to inject custom JavaScript/TypeScript code into the generated bindings:
|
||||
|
||||
```go
|
||||
//wails:inject console.log("Hello from Wails!");
|
||||
type MyService struct {}
|
||||
|
||||
func (s *MyService) Greet(name string) string {
|
||||
return "Hello, " + name
|
||||
}
|
||||
```
|
||||
|
||||
This will inject the specified code into the generated JavaScript/TypeScript file for the `MyService` service.
|
||||
|
||||
You can also use conditional injection to target specific output formats:
|
||||
|
||||
```go
|
||||
//wails:inject j*:console.log("Hello JS!"); // JavaScript only
|
||||
//wails:inject t*:console.log("Hello TS!"); // TypeScript only
|
||||
```
|
||||
|
||||
### Including Additional Files
|
||||
|
||||
The `//wails:include` directive allows you to include additional files with the generated bindings:
|
||||
|
||||
```go
|
||||
//wails:include js/*.js
|
||||
package mypackage
|
||||
```
|
||||
|
||||
This directive is typically used in package documentation comments to include additional JavaScript/TypeScript files with the generated bindings.
|
||||
|
||||
### Marking Internal Types and Methods
|
||||
|
||||
The `//wails:internal` directive marks a type or method as internal, preventing it from being exported to the frontend:
|
||||
|
||||
```go
|
||||
//wails:internal
|
||||
type InternalModel struct {
|
||||
Field string
|
||||
}
|
||||
|
||||
//wails:internal
|
||||
func (s *MyService) InternalMethod() {}
|
||||
```
|
||||
|
||||
This is useful for types and methods that are only used internally by your Go code and should not be exposed to the frontend.
|
||||
|
||||
### Ignoring Methods
|
||||
|
||||
The `//wails:ignore` directive completely ignores a method during binding generation:
|
||||
|
||||
```go
|
||||
//wails:ignore
|
||||
func (s *MyService) IgnoredMethod() {}
|
||||
```
|
||||
|
||||
This is similar to `//wails:internal`, but it completely ignores the method rather than marking it as internal.
|
||||
|
||||
### Custom Method IDs
|
||||
|
||||
The `//wails:id` directive specifies a custom ID for a method, overriding the default hash-based ID:
|
||||
|
||||
```go
|
||||
//wails:id 42
|
||||
func (s *MyService) CustomIDMethod() {}
|
||||
```
|
||||
|
||||
This can be useful for maintaining compatibility when refactoring code.
|
||||
|
||||
## Working with Complex Types
|
||||
|
||||
### Nested Structs
|
||||
|
||||
The binding generator handles nested structs automatically:
|
||||
|
||||
```go
|
||||
type Address struct {
|
||||
Street string
|
||||
City string
|
||||
State string
|
||||
Zip string
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
Address Address
|
||||
}
|
||||
|
||||
func (s *MyService) GetPerson() Person {
|
||||
return Person{
|
||||
Name: "John Doe",
|
||||
Address: Address{
|
||||
Street: "123 Main St",
|
||||
City: "Anytown",
|
||||
State: "CA",
|
||||
Zip: "12345",
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The generated JavaScript/TypeScript code will include classes for both `Person` and `Address`.
|
||||
|
||||
### Maps and Slices
|
||||
|
||||
Maps and slices are also handled automatically:
|
||||
|
||||
```go
|
||||
type Person struct {
|
||||
Name string
|
||||
Attributes map[string]string
|
||||
Friends []string
|
||||
}
|
||||
|
||||
func (s *MyService) GetPerson() Person {
|
||||
return Person{
|
||||
Name: "John Doe",
|
||||
Attributes: map[string]string{
|
||||
"hair": "brown",
|
||||
"eyes": "blue",
|
||||
},
|
||||
Friends: []string{"Jane", "Bob", "Alice"},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In JavaScript, maps are represented as objects and slices as arrays. In TypeScript, maps are represented as `Record<K, V>` and slices as `T[]`.
|
||||
|
||||
### Generic Types
|
||||
|
||||
The binding generator supports generic types:
|
||||
|
||||
```go
|
||||
type Result[T any] struct {
|
||||
Data T
|
||||
Error string
|
||||
}
|
||||
|
||||
func (s *MyService) GetResult() Result[string] {
|
||||
return Result[string]{
|
||||
Data: "Hello, World!",
|
||||
Error: "",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The generated TypeScript code will include a generic class for `Result`:
|
||||
|
||||
```typescript
|
||||
export class Result<T> {
|
||||
"Data": T;
|
||||
"Error": string;
|
||||
|
||||
constructor(source: Partial<Result<T>> = {}) {
|
||||
if (!("Data" in source)) {
|
||||
this["Data"] = null as any;
|
||||
}
|
||||
if (!("Error" in source)) {
|
||||
this["Error"] = "";
|
||||
}
|
||||
|
||||
Object.assign(this, source);
|
||||
}
|
||||
|
||||
static createFrom<T>(source: string | object = {}): Result<T> {
|
||||
let parsedSource = typeof source === "string" ? JSON.parse(source) : source;
|
||||
return new Result<T>(parsedSource as Partial<Result<T>>);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Interfaces
|
||||
|
||||
The binding generator can generate TypeScript interfaces instead of classes using the `-i` flag:
|
||||
|
||||
```bash
|
||||
wails3 generate bindings -ts -i
|
||||
```
|
||||
|
||||
This will generate TypeScript interfaces for all models:
|
||||
|
||||
```typescript
|
||||
export interface Person {
|
||||
Name: string;
|
||||
Attributes: Record<string, string>;
|
||||
Friends: string[];
|
||||
}
|
||||
```
|
||||
|
||||
## Optimizing Binding Generation
|
||||
|
||||
### Using Names Instead of IDs
|
||||
|
||||
By default, the binding generator uses hash-based IDs for method calls. You can use the `-names` flag to use names instead:
|
||||
|
||||
```bash
|
||||
wails3 generate bindings -names
|
||||
```
|
||||
|
||||
This will generate code that uses method names instead of IDs:
|
||||
|
||||
```javascript
|
||||
export function Greet(name) {
|
||||
let $resultPromise = $Call.ByName("Greet", name);
|
||||
return $resultPromise;
|
||||
}
|
||||
```
|
||||
|
||||
This can make the generated code more readable and easier to debug, but it may be slightly less efficient.
|
||||
|
||||
### Bundling the Runtime
|
||||
|
||||
By default, the generated code imports the Wails runtime from the `@wailsio/runtime` npm package. You can use the `-b` flag to bundle the runtime with the generated code:
|
||||
|
||||
```bash
|
||||
wails3 generate bindings -b
|
||||
```
|
||||
|
||||
This will include the runtime code directly in the generated files, eliminating the need for the npm package.
|
||||
|
||||
### Disabling Index Files
|
||||
|
||||
If you don't need the index files, you can use the `-noindex` flag to disable their generation:
|
||||
|
||||
```bash
|
||||
wails3 generate bindings -noindex
|
||||
```
|
||||
|
||||
This can be useful if you prefer to import services and models directly from their respective files.
|
||||
|
||||
## Real-World Examples
|
||||
|
||||
### Authentication Service
|
||||
|
||||
Here's an example of an authentication service with custom directives:
|
||||
|
||||
```go
|
||||
package auth
|
||||
|
||||
//wails:inject console.log("Auth service initialized");
|
||||
type AuthService struct {
|
||||
// Private fields
|
||||
users map[string]User
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Username string
|
||||
Email string
|
||||
Role string
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
Success bool
|
||||
User User
|
||||
Token string
|
||||
Error string
|
||||
}
|
||||
|
||||
// Login authenticates a user
|
||||
func (s *AuthService) Login(req LoginRequest) LoginResponse {
|
||||
// Implementation...
|
||||
}
|
||||
|
||||
// GetCurrentUser returns the current user
|
||||
func (s *AuthService) GetCurrentUser() User {
|
||||
// Implementation...
|
||||
}
|
||||
|
||||
// Internal helper method
|
||||
//wails:internal
|
||||
func (s *AuthService) validateCredentials(username, password string) bool {
|
||||
// Implementation...
|
||||
}
|
||||
```
|
||||
|
||||
### Data Processing Service
|
||||
|
||||
Here's an example of a data processing service with generic types:
|
||||
|
||||
```go
|
||||
package data
|
||||
|
||||
type ProcessingResult[T any] struct {
|
||||
Data T
|
||||
Error string
|
||||
}
|
||||
|
||||
type DataService struct {}
|
||||
|
||||
// Process processes data and returns a result
|
||||
func (s *DataService) Process(data string) ProcessingResult[map[string]int] {
|
||||
// Implementation...
|
||||
}
|
||||
|
||||
// ProcessBatch processes multiple data items
|
||||
func (s *DataService) ProcessBatch(data []string) ProcessingResult[[]map[string]int] {
|
||||
// Implementation...
|
||||
}
|
||||
|
||||
// Internal helper method
|
||||
//wails:internal
|
||||
func (s *DataService) parseData(data string) (map[string]int, error) {
|
||||
// Implementation...
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional Code Injection
|
||||
|
||||
Here's an example of conditional code injection for different output formats:
|
||||
|
||||
```go
|
||||
//wails:inject j*:/**
|
||||
//wails:inject j*: * @param {string} arg
|
||||
//wails:inject j*: * @returns {Promise<void>}
|
||||
//wails:inject j*: */
|
||||
//wails:inject j*:export async function CustomMethod(arg) {
|
||||
//wails:inject t*:export async function CustomMethod(arg: string): Promise<void> {
|
||||
//wails:inject await InternalMethod("Hello " + arg + "!");
|
||||
//wails:inject }
|
||||
type Service struct{}
|
||||
```
|
||||
|
||||
This injects different code for JavaScript and TypeScript outputs, providing appropriate type annotations for each language.
|
||||
|
|
@ -10,12 +10,49 @@ Application menus provide the main menu bar interface for your application. They
|
|||
|
||||
## Creating an Application Menu
|
||||
|
||||
Create a new application menu using the `NewMenu` method:
|
||||
Create a new application menu using the `New` method from the Menu manager:
|
||||
|
||||
```go
|
||||
menu := app.NewMenu()
|
||||
menu := app.Menu.New()
|
||||
```
|
||||
|
||||
## Setting the Menu
|
||||
|
||||
The way to set the menu varies on the platform:
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS, there is only one menu bar per application. Set the menu using the `Set` method of the Menu manager:
|
||||
|
||||
```go
|
||||
app.Menu.Set(menu)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows, there is a menu bar per window. Set the menu using the `SetMenu` method of the window:
|
||||
|
||||
```go
|
||||
app.Window.Current().SetMenu(menu)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux, the menu bar is typically per window. Set the menu using the `SetMenu` method of the window:
|
||||
|
||||
```go
|
||||
app.Window.Current().SetMenu(menu)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Menu Roles
|
||||
|
||||
Wails provides predefined menu roles that automatically create platform-appropriate menu structures:
|
||||
|
|
@ -70,7 +107,7 @@ Menu items can control the application windows:
|
|||
```go
|
||||
viewMenu := menu.AddSubmenu("View")
|
||||
viewMenu.Add("Toggle Fullscreen").OnClick(func(ctx *application.Context) {
|
||||
window := app.CurrentWindow()
|
||||
window := app.Window.Current()
|
||||
if window.Fullscreen() {
|
||||
window.SetFullscreen(false)
|
||||
} else {
|
||||
|
|
@ -153,7 +190,7 @@ Always test menu functionality across all target platforms to ensure consistent
|
|||
:::
|
||||
|
||||
:::tip[Pro Tip]
|
||||
Consider using the `application.CurrentWindow()` method in menu handlers to affect the active window, rather than storing window references.
|
||||
Consider using the `app.Window.Current()` method in menu handlers to affect the active window, rather than storing window references.
|
||||
:::
|
||||
|
||||
## Complete Example
|
||||
|
|
@ -174,7 +211,7 @@ func main() {
|
|||
})
|
||||
|
||||
// Create main menu
|
||||
menu := app.NewMenu()
|
||||
menu := app.Menu.New()
|
||||
|
||||
// Add platform-specific application menu
|
||||
if runtime.GOOS == "darwin" {
|
||||
|
|
@ -208,7 +245,7 @@ func main() {
|
|||
})
|
||||
|
||||
// Set the menu
|
||||
app.SetMenu(menu)
|
||||
app.Menu.Set(menu)
|
||||
|
||||
// Create main window
|
||||
app.NewWebviewWindow()
|
||||
|
|
|
|||
195
docs/src/content/docs/learn/badges.mdx
Normal file
195
docs/src/content/docs/learn/badges.mdx
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
---
|
||||
title: Badges
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Introduction
|
||||
|
||||
Wails provides a cross-platform badge service for desktop applications. This service allows you to display badges on your application tile or dock icon, which is useful for indicating unread messages, notifications, or other status information.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### 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),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### 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}, // Blue 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:
|
||||
|
||||
```go
|
||||
// Set a default badge
|
||||
badgeService.SetBadge("")
|
||||
|
||||
// Set a numeric badge
|
||||
badgeService.SetBadge("3")
|
||||
|
||||
// Set a text badge
|
||||
badgeService.SetBadge("New")
|
||||
```
|
||||
|
||||
### Setting a Custom Badge
|
||||
|
||||
Set a badge on the application tile/dock icon with one-off options applied:
|
||||
|
||||
#### Go
|
||||
```go
|
||||
options := badge.Options{
|
||||
BackgroundColour: color.RGBA{0, 255, 255, 255},
|
||||
FontName: "arialb.ttf", // System font
|
||||
FontSize: 16,
|
||||
SmallFontSize: 10,
|
||||
TextColour: color.RGBA{0, 0, 0, 255},
|
||||
}
|
||||
|
||||
// Set a default badge
|
||||
badgeService.SetCustomBadge("", options)
|
||||
|
||||
// Set a numeric badge
|
||||
badgeService.SetCustomBadge("3", options)
|
||||
|
||||
// Set a text badge
|
||||
badgeService.SetCustomBadge("New", options)
|
||||
```
|
||||
|
||||
### Removing a Badge
|
||||
|
||||
Remove the badge from the application icon:
|
||||
|
||||
```go
|
||||
badgeService.RemoveBadge()
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS, badges:
|
||||
|
||||
- Are displayed directly on the dock icon
|
||||
- Support text values
|
||||
- Automatically handle dark/light mode appearance
|
||||
- Use the standard macOS dock badge styling
|
||||
- Automatically handle label overflow
|
||||
- Do not support customization options (any options passed to NewWithOptions will be ignored)
|
||||
- Will display "●" as the default badge if an empty label is provided
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows, badges:
|
||||
|
||||
- Are displayed as an overlay icon in the taskbar
|
||||
- Support text values
|
||||
- Allow customization of colors, font, and font sizes
|
||||
- Adapt to Windows theme settings
|
||||
- Require the application to have a window
|
||||
- Use smaller font size automatically for badges with multiple characters
|
||||
- Do not handle label overflow
|
||||
- Support the following customization options:
|
||||
- TextColour: Text color (default: white)
|
||||
- BackgroundColour: Badge background color (default: red)
|
||||
- FontName: Font file name (default: "segoeuib.ttf")
|
||||
- FontSize: Font size for single character (default: 18)
|
||||
- SmallFontSize: Font size for multiple characters (default: 14)
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux:
|
||||
|
||||
- Badge functionality is not available
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Use badges sparingly:
|
||||
- Too many badge updates can distract users
|
||||
- Reserve badges for important notifications
|
||||
|
||||
2. Keep badge text short:
|
||||
- Numeric badges are most effective
|
||||
- On macOS, text badges should be brief
|
||||
|
||||
3. For Windows customization:
|
||||
- Ensure high contrast between text and background colors
|
||||
- Test with different text lengths as font size decreases with length
|
||||
- Use common system fonts to ensure availability
|
||||
|
||||
## API Reference
|
||||
|
||||
### Service Management
|
||||
| Method | Description |
|
||||
|--------------------------------------------|-------------------------------------------------------|
|
||||
| `New()` | Creates a new badge service with default options |
|
||||
| `NewWithOptions(options Options)` | Creates a new badge service with custom options (Windows only, options are ignored on macOS) |
|
||||
|
||||
### Badge Operations
|
||||
| Method | Description |
|
||||
|----------------------------------------------|------------------------------------------------------------|
|
||||
| `SetBadge(label string) error` | Sets a badge with the specified label |
|
||||
| `SetCustomBadge(label string, options Options) error` | Sets a badge with the specified label and custom styling options (Windows only) |
|
||||
| `RemoveBadge() error` | Removes the badge from the application icon |
|
||||
### Structs and Types
|
||||
|
||||
```go
|
||||
// Options for customizing badge appearance (Windows only)
|
||||
type Options struct {
|
||||
TextColour color.RGBA // Color of the badge text
|
||||
BackgroundColour color.RGBA // Color of the badge background
|
||||
FontName string // Font file name (e.g., "segoeuib.ttf")
|
||||
FontSize int // Font size for single character
|
||||
SmallFontSize int // Font size for multiple characters
|
||||
}
|
||||
```
|
||||
115
docs/src/content/docs/learn/binding-best-practices.mdx
Normal file
115
docs/src/content/docs/learn/binding-best-practices.mdx
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
title: Binding Best Practices
|
||||
sidebar:
|
||||
order: 23
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
|
||||
This guide provides best practices and patterns for using the Wails binding system effectively in your applications.
|
||||
|
||||
## Service Design Patterns
|
||||
|
||||
### Service Organization
|
||||
|
||||
Organize your services based on functionality rather than technical concerns. For example, instead of having a single large service, split it into smaller, focused services:
|
||||
|
||||
```go
|
||||
// Instead of this:
|
||||
type AppService struct {}
|
||||
|
||||
func (s *AppService) GetUser() User { /* ... */ }
|
||||
func (s *AppService) UpdateUser(user User) error { /* ... */ }
|
||||
func (s *AppService) GetProducts() []Product { /* ... */ }
|
||||
func (s *AppService) AddProduct(product Product) error { /* ... */ }
|
||||
|
||||
// Do this:
|
||||
type UserService struct {}
|
||||
func (s *UserService) GetUser() User { /* ... */ }
|
||||
func (s *UserService) UpdateUser(user User) error { /* ... */ }
|
||||
|
||||
type ProductService struct {}
|
||||
func (s *ProductService) GetProducts() []Product { /* ... */ }
|
||||
func (s *ProductService) AddProduct(product Product) error { /* ... */ }
|
||||
```
|
||||
|
||||
This makes your code more maintainable and easier to understand.
|
||||
|
||||
### Use JSON Tags
|
||||
|
||||
Use JSON tags to control how your models are serialized:
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"-"` // Exclude from JSON
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
```
|
||||
|
||||
### Separate Frontend and Backend Models
|
||||
|
||||
Consider using different models for the frontend and backend:
|
||||
|
||||
```go
|
||||
// Backend model
|
||||
type User struct {
|
||||
ID int
|
||||
Name string
|
||||
Email string
|
||||
Password string // Sensitive data
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// Frontend model
|
||||
type UserDTO struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (s *UserService) GetUser() UserDTO {
|
||||
user := getUserFromDatabase()
|
||||
return UserDTO{
|
||||
ID: user.ID,
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
CreatedAt: user.CreatedAt,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This gives you more control over what data is exposed to the frontend.
|
||||
|
||||
### Use Context for Cancellation
|
||||
|
||||
Use context for cancellation to avoid wasting resources on abandoned requests:
|
||||
|
||||
```go
|
||||
func (s *ProductService) GetProducts(ctx context.Context, req ProductsRequest) (ProductsResponse, error) {
|
||||
// Check if the request has been cancelled
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ProductsResponse{}, ctx.Err()
|
||||
default:
|
||||
// Continue processing
|
||||
}
|
||||
|
||||
products, total, err := getProductsFromDatabase(ctx, req.Page, req.PageSize, req.Filter)
|
||||
if err != nil {
|
||||
return ProductsResponse{}, err
|
||||
}
|
||||
|
||||
return ProductsResponse{
|
||||
Products: products,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
155
docs/src/content/docs/learn/binding-system.mdx
Normal file
155
docs/src/content/docs/learn/binding-system.mdx
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
---
|
||||
title: Binding System Internals
|
||||
sidebar:
|
||||
order: 21
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
|
||||
This guide explains how the Wails binding system works internally, providing insights for developers who want to understand the mechanics behind the automatic code generation.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The Wails binding system consists of three main components:
|
||||
|
||||
1. **Collection**: Analyzes Go code to extract information about services, models, and other declarations
|
||||
2. **Configuration**: Manages settings and options for the binding generation process
|
||||
3. **Rendering**: Generates JavaScript/TypeScript code based on the collected information
|
||||
|
||||
<FileTree>
|
||||
- internal/generator/
|
||||
- collect/ # Package analysis and information extraction
|
||||
- config/ # Configuration structures and interfaces
|
||||
- render/ # Code generation for JS/TS
|
||||
</FileTree>
|
||||
|
||||
## Collection Process
|
||||
|
||||
The collection process is responsible for analyzing Go packages and extracting information about services, models, and other declarations. This is handled by the `collect` package.
|
||||
|
||||
### Key Components
|
||||
|
||||
- **Collector**: Manages package information and caches collected data
|
||||
- **Package**: Represents a Go package being analyzed and stores collected services, models, and directives
|
||||
- **Service**: Collects information about service types and their methods
|
||||
- **Model**: Collects detailed information about model types, including fields, values, and type parameters
|
||||
- **Directive**: Parses and interprets `//wails:` directives in Go source code
|
||||
|
||||
### Collection Flow
|
||||
|
||||
1. The collector scans the Go packages specified in the project
|
||||
2. It identifies service types (structs with methods that will be exposed to the frontend)
|
||||
3. For each service, it collects information about its methods
|
||||
4. It identifies model types (structs used as parameters or return values in service methods)
|
||||
5. For each model, it collects information about its fields and type parameters
|
||||
6. It processes any `//wails:` directives found in the code
|
||||
|
||||
## Rendering Process
|
||||
|
||||
The rendering process is responsible for generating JavaScript/TypeScript code based on the collected information. This is handled by the `render` package.
|
||||
|
||||
### Key Components
|
||||
|
||||
- **Renderer**: Orchestrates the rendering of service, model, and index files
|
||||
- **Module**: Represents a single generated JavaScript/TypeScript module
|
||||
- **Templates**: Text templates used for code generation
|
||||
|
||||
### Rendering Flow
|
||||
|
||||
1. For each service, the renderer generates a JavaScript/TypeScript file with functions that mirror the service methods
|
||||
2. For each model, the renderer generates a JavaScript/TypeScript class that mirrors the model struct
|
||||
3. The renderer generates index files that re-export all services and models
|
||||
4. The renderer applies any custom code injections specified by `//wails:inject` directives
|
||||
|
||||
## Type Mapping
|
||||
|
||||
One of the most important aspects of the binding system is how Go types are mapped to JavaScript/TypeScript types. Here's a summary of the mapping:
|
||||
|
||||
| Go Type | JavaScript Type | TypeScript Type |
|
||||
|---------|----------------|----------------|
|
||||
| `bool` | `boolean` | `boolean` |
|
||||
| `int`, `int8`, `int16`, `int32`, `int64`, `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `float32`, `float64` | `number` | `number` |
|
||||
| `string` | `string` | `string` |
|
||||
| `[]byte` | `Uint8Array` | `Uint8Array` |
|
||||
| `[]T` | `Array<T>` | `T[]` |
|
||||
| `map[K]V` | `Object` | `Record<K, V>` |
|
||||
| `struct` | `Object` | Custom class |
|
||||
| `interface{}` | `any` | `any` |
|
||||
| `*T` | `T \| null` | `T \| null` |
|
||||
| `func` | Not supported | Not supported |
|
||||
| `chan` | Not supported | Not supported |
|
||||
|
||||
## Directives System
|
||||
|
||||
The binding system supports several directives that can be used to customize the generated code. These directives are added as comments in your Go code.
|
||||
|
||||
### Available Directives
|
||||
|
||||
- `//wails:inject`: Injects custom JavaScript/TypeScript code into the generated bindings
|
||||
- `//wails:include`: Includes additional files with the generated bindings
|
||||
- `//wails:internal`: Marks a type or method as internal, preventing it from being exported to the frontend
|
||||
- `//wails:ignore`: Completely ignores a method during binding generation
|
||||
- `//wails:id`: Specifies a custom ID for a method, overriding the default hash-based ID
|
||||
|
||||
### Directive Processing
|
||||
|
||||
1. During the collection phase, the collector identifies and parses directives in the Go code
|
||||
2. The directives are stored with the corresponding declarations (services, methods, models, etc.)
|
||||
3. During the rendering phase, the renderer applies the directives to customize the generated code
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Conditional Code Generation
|
||||
|
||||
The binding system supports conditional code generation using a two-character condition prefix for `include` and `inject` directives:
|
||||
|
||||
```
|
||||
<language><style>:<content>
|
||||
```
|
||||
|
||||
Where:
|
||||
- `<language>` can be:
|
||||
- `*` - Both JavaScript and TypeScript
|
||||
- `j` - JavaScript only
|
||||
- `t` - TypeScript only
|
||||
|
||||
- `<style>` can be:
|
||||
- `*` - Both classes and interfaces
|
||||
- `c` - Classes only
|
||||
- `i` - Interfaces only
|
||||
|
||||
For example:
|
||||
```go
|
||||
//wails:inject j*:console.log("JavaScript only");
|
||||
//wails:inject t*:console.log("TypeScript only");
|
||||
```
|
||||
|
||||
### Custom Method IDs
|
||||
|
||||
By default, methods are identified by a hash-based ID. However, you can specify a custom ID using the `//wails:id` directive:
|
||||
|
||||
```go
|
||||
//wails:id 42
|
||||
func (s *Service) CustomIDMethod() {}
|
||||
```
|
||||
|
||||
This can be useful for maintaining compatibility when refactoring code.
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
The binding generator is designed to be efficient, but there are a few things to keep in mind:
|
||||
|
||||
1. The first run will be slower as it builds up a cache of packages to scan
|
||||
2. Subsequent runs will be faster as they use the cached information
|
||||
3. The generator processes all packages in the project, which can be time-consuming for large projects
|
||||
4. You can use the `-clean` flag to clean the output directory before generation
|
||||
|
||||
## Debugging
|
||||
|
||||
If you encounter issues with the binding generation, you can use the `-v` flag to enable debug output:
|
||||
|
||||
```bash
|
||||
wails3 generate bindings -v
|
||||
```
|
||||
|
||||
This will provide detailed information about the collection and rendering process, which can help identify the source of the issue.
|
||||
|
|
@ -183,7 +183,6 @@ func main() {
|
|||
},
|
||||
})
|
||||
// ....
|
||||
app.NewWebviewWindow()
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
@ -437,8 +436,9 @@ func (s *MyService) WindowAwareMethod(ctx context.Context) (string, error) {
|
|||
```
|
||||
|
||||
From the frontend, these methods can be called normally. If you need to cancel a
|
||||
long-running operation, the Promise will be rejected with the cancellation
|
||||
error:
|
||||
long-running operation, you can call the special `cancel` method on the promise
|
||||
and it will reject immediately with a special cancellation error;
|
||||
the Go context will be cancelled and the actual result of the call will be discarded:
|
||||
|
||||
```javascript
|
||||
// Call the method
|
||||
|
|
@ -448,3 +448,227 @@ const promise = MyService.LongRunningTask("input");
|
|||
// This will cause the context to be cancelled in the Go method
|
||||
promise.cancel();
|
||||
```
|
||||
|
||||
In fact, the runtime returns a special promise wrapper
|
||||
that provides cancellation support for arbitrarily long promise chains.
|
||||
For example:
|
||||
|
||||
```javascript
|
||||
import { CancelError } from "@wailsio/runtime";
|
||||
|
||||
// Call the method and process its output
|
||||
const promise = MyService.LongRunningTask("input").then((result) => {
|
||||
console.log(result);
|
||||
}).catch((err) => {
|
||||
if (err instanceof CancelError) {
|
||||
console.log("Cancelled.", err.cause);
|
||||
} else {
|
||||
console.error("Failed.", err);
|
||||
}
|
||||
});
|
||||
|
||||
// Later...
|
||||
// cancel() accepts an optional cause parameter
|
||||
// that will be attached to the cancellation error:
|
||||
promise.cancel("I'm tired of waiting!").then(() => {
|
||||
// Cancellation has been requested successfully
|
||||
// and all handlers attached above have run.
|
||||
console.log("Ready for the next adventure!");
|
||||
});
|
||||
```
|
||||
|
||||
The `cancel` method returns a promise that fulfills always (and never rejects)
|
||||
after the cancellation request has been submitted successfully
|
||||
and all previously attached handlers have run.
|
||||
|
||||
:::note
|
||||
Calling the `cancel` method on a settled promise is safe and has no effect;
|
||||
if the task completes before the call to `cancel`, the code above is going to log:
|
||||
|
||||
```
|
||||
completed
|
||||
Ready for the next adventure!
|
||||
```
|
||||
|
||||
However, if `cancel` is called before the task finishes, the output will be:
|
||||
|
||||
```
|
||||
Cancelled. I'm tired of waiting!
|
||||
Ready for the next adventure!
|
||||
```
|
||||
:::
|
||||
|
||||
The approach discussed above requires storing and chaining promises manually,
|
||||
which can be cumbersome for code written in `async`/`await` style.
|
||||
If you target plaforms that support the `AbortController`/`AbortSignal` idiom,
|
||||
you can call the `cancelOn` method and tie call cancellation to an `AbortSignal` instead:
|
||||
|
||||
```javascript
|
||||
async function callBinding(signal) {
|
||||
try {
|
||||
await MyService.LongRunningTask("input").cancelOn(signal);
|
||||
} catch (err) {
|
||||
if (err instanceof CancelError) {
|
||||
console.log("Cancelled! Cause: ", err.cause);
|
||||
} else {
|
||||
console.error("Failed! Error: ", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let controller = new AbortController();
|
||||
callBinding(controller.signal);
|
||||
|
||||
// Later...
|
||||
controller.abort("I'm tired of waiting!");
|
||||
```
|
||||
|
||||
:::caution
|
||||
On the macOS platform, `AbortSignal` is only supported from macOS 10.15 Catalina onwards.
|
||||
:::
|
||||
|
||||
### Handling errors
|
||||
|
||||
As you may have noticed above, bound methods can return errors, which are handled specially.
|
||||
When a result field has type `error`, it is omitted by default from the values returned to JS.
|
||||
When such a field is _non-nil_, the promise rejects with a `RuntimeError` exception
|
||||
that wraps the Go error message:
|
||||
|
||||
```go
|
||||
func (*MyService) FailingMethod(name string) error {
|
||||
return fmt.Errorf("Welcome to an imperfect world, %s", name)
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
import { MyService } from './bindings/changeme';
|
||||
|
||||
try {
|
||||
await MyService.FailingMethod("CLU")
|
||||
} catch (err) {
|
||||
if (err.name === 'RuntimeError') {
|
||||
console.log(err.message); // Prints 'Welcome to an imperfect world, CLU'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The exception will be an instance of the `Call.RuntimeError` class from the wails runtime,
|
||||
hence you can also test its type like this:
|
||||
|
||||
```js
|
||||
import { Call } from '@wailsio/runtime';
|
||||
|
||||
try {
|
||||
// ...
|
||||
} catch (err) {
|
||||
if (err instanceof Call.RuntimeError) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the Go error value supports JSON marshaling, the exception's `cause` property
|
||||
will hold the marshaled version of the error:
|
||||
|
||||
```go
|
||||
type ImperfectWorldError struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (err *ImperfectWorldError) Error() {
|
||||
return fmt.Sprintf("Welcome to an imperfect world, %s", err.Name)
|
||||
}
|
||||
|
||||
func (*MyService) FailingMethod(name string) error {
|
||||
return &ImperfectWorldError{
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
import { MyService } from './bindings/changeme';
|
||||
|
||||
try {
|
||||
await MyService.FailingMethod("CLU")
|
||||
} catch (err) {
|
||||
if (err.name === 'RuntimeError') {
|
||||
console.log(err.cause.name); // Prints 'CLU'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Generally, many Go error values will only have limited or no support for marshaling to JSON.
|
||||
If you so wish, you can customise the value provided as cause
|
||||
by specifying either a global or per-service error marshaling function:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
MarshalError: func(err error) []byte {
|
||||
// ...
|
||||
},
|
||||
Services: []application.Service{
|
||||
application.NewServiceWithOptions(&MyService{}, application.ServiceOptions{
|
||||
MarshalError: func(err error) []byte {
|
||||
// ...
|
||||
},
|
||||
}),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Per-service functions override the global function,
|
||||
which in turn overrides the default behaviour of using `json.Marshal`.
|
||||
If a marshaling function returns `nil`, it falls back to the outer function:
|
||||
per-service functions fall back to the global function,
|
||||
which in turn falls back to the default behaviour.
|
||||
|
||||
:::tip
|
||||
If you wish to omit the `cause` property on the resulting exception,
|
||||
let the marshaling function return a falsy JSON value like `[]byte("null")`.
|
||||
:::
|
||||
|
||||
Here's an example marshaling function that unwraps path errors and reports the file path:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
MarshalError: func(err error) []byte {
|
||||
var perr *fs.PathError
|
||||
if !errors.As(err, &perr) {
|
||||
// Not a path error, fall back to default handling.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal path string
|
||||
path, err := json.Marshal(&perr.Path)
|
||||
if err != nil {
|
||||
// String marshaling failed, fall back to default handling.
|
||||
return nil
|
||||
}
|
||||
|
||||
return []byte(fmt.Sprintf(`{"path":%s}`, path))
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
:::note
|
||||
Error marshaling functions are not allowed to fail.
|
||||
If they are not able to process a given error and return valid JSON,
|
||||
they should return `nil` and fall back to a more generic handler.
|
||||
If no strategy succeeds, the exception will not have a `cause` property.
|
||||
:::
|
||||
|
||||
Binding call promises may also reject with a `TypeError`
|
||||
when the method has been passed the wrong number of arguments,
|
||||
when the conversion of arguments from JSON to their Go types fails,
|
||||
or when the conversion of results to JSON fails.
|
||||
These problems will usually be caught early by the type system.
|
||||
If your code typechecks but you still get type errors,
|
||||
it might be that some of your Go types are not supported by the `encoding/json` package:
|
||||
look for warnings from the binding generator to catch these.
|
||||
|
||||
:::caution
|
||||
If you see a `ReferenceError` complaining about unknown methods,
|
||||
it could mean that your JS bindings have gotten out of sync with Go code
|
||||
and must be regenerated.
|
||||
:::
|
||||
|
|
|
|||
510
docs/src/content/docs/learn/browser.mdx
Normal file
510
docs/src/content/docs/learn/browser.mdx
Normal file
|
|
@ -0,0 +1,510 @@
|
|||
---
|
||||
title: Browser Integration
|
||||
sidebar:
|
||||
order: 58
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
Wails provides simple browser integration through the BrowserManager API, allowing your application to open URLs and files in the user's default web browser. This is useful for opening external links, documentation, or files that should be handled by the browser.
|
||||
|
||||
## Accessing the Browser Manager
|
||||
|
||||
The browser manager is accessed through the `Browser` property on your application instance:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "Browser Integration Demo",
|
||||
})
|
||||
|
||||
// Access the browser manager
|
||||
browser := app.Browser
|
||||
```
|
||||
|
||||
## Opening URLs
|
||||
|
||||
### Open Web URLs
|
||||
|
||||
Open URLs in the user's default web browser:
|
||||
|
||||
```go
|
||||
// Open a website
|
||||
err := app.Browser.OpenURL("https://wails.io")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open URL", "error", err)
|
||||
}
|
||||
|
||||
// Open specific pages
|
||||
err = app.Browser.OpenURL("https://github.com/wailsapp/wails")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open GitHub", "error", err)
|
||||
}
|
||||
```
|
||||
|
||||
### Open Local URLs
|
||||
|
||||
Open local development servers or local network resources:
|
||||
|
||||
```go
|
||||
// Open local development server
|
||||
err := app.Browser.OpenURL("http://localhost:3000")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open local server", "error", err)
|
||||
}
|
||||
|
||||
// Open network resource
|
||||
err = app.Browser.OpenURL("http://192.168.1.100:8080")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open network resource", "error", err)
|
||||
}
|
||||
```
|
||||
|
||||
## Opening Files
|
||||
|
||||
### Open HTML Files
|
||||
|
||||
Open local HTML files in the browser:
|
||||
|
||||
```go
|
||||
// Open an HTML file
|
||||
err := app.Browser.OpenFile("/path/to/documentation.html")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open HTML file", "error", err)
|
||||
}
|
||||
|
||||
// Open generated reports
|
||||
reportPath := "/tmp/report.html"
|
||||
err = app.Browser.OpenFile(reportPath)
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open report", "error", err)
|
||||
}
|
||||
```
|
||||
|
||||
### Open Other File Types
|
||||
|
||||
Open various file types that browsers can handle:
|
||||
|
||||
```go
|
||||
// Open PDF files
|
||||
err := app.Browser.OpenFile("/path/to/document.pdf")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open PDF", "error", err)
|
||||
}
|
||||
|
||||
// Open image files
|
||||
err = app.Browser.OpenFile("/path/to/image.png")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open image", "error", err)
|
||||
}
|
||||
|
||||
// Open text files
|
||||
err = app.Browser.OpenFile("/path/to/readme.txt")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open text file", "error", err)
|
||||
}
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Help and Documentation
|
||||
|
||||
Provide easy access to help resources:
|
||||
|
||||
```go
|
||||
// Create help menu
|
||||
func setupHelpMenu(app *application.App) {
|
||||
menu := app.Menu.New()
|
||||
helpMenu := menu.AddSubmenu("Help")
|
||||
|
||||
helpMenu.Add("Online Documentation").OnClick(func(ctx *application.Context) {
|
||||
err := app.Browser.OpenURL("https://docs.yourapp.com")
|
||||
if err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Error").
|
||||
SetMessage("Could not open documentation").
|
||||
Show()
|
||||
}
|
||||
})
|
||||
|
||||
helpMenu.Add("GitHub Repository").OnClick(func(ctx *application.Context) {
|
||||
app.Browser.OpenURL("https://github.com/youruser/yourapp")
|
||||
})
|
||||
|
||||
helpMenu.Add("Report Issue").OnClick(func(ctx *application.Context) {
|
||||
app.Browser.OpenURL("https://github.com/youruser/yourapp/issues/new")
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### External Links in Content
|
||||
|
||||
Handle external links from your application content:
|
||||
|
||||
```go
|
||||
func handleExternalLink(app *application.App, url string) {
|
||||
// Validate the URL before opening
|
||||
if !isValidURL(url) {
|
||||
app.Logger.Warn("Invalid URL", "url", url)
|
||||
return
|
||||
}
|
||||
|
||||
// Optionally confirm with user
|
||||
dialog := app.Dialog.Question()
|
||||
dialog.SetTitle("Open External Link")
|
||||
dialog.SetMessage(fmt.Sprintf("Open %s in your browser?", url))
|
||||
|
||||
dialog.AddButton("Open").OnClick(func() {
|
||||
err := app.Browser.OpenURL(url)
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open URL", "url", url, "error", err)
|
||||
}
|
||||
})
|
||||
|
||||
dialog.AddButton("Cancel")
|
||||
dialog.Show()
|
||||
}
|
||||
|
||||
func isValidURL(url string) bool {
|
||||
parsed, err := url.Parse(url)
|
||||
return err == nil && (parsed.Scheme == "http" || parsed.Scheme == "https")
|
||||
}
|
||||
```
|
||||
|
||||
### Export and View Reports
|
||||
|
||||
Generate and open reports in the browser:
|
||||
|
||||
```go
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func generateAndOpenReport(app *application.App, data interface{}) error {
|
||||
// Create temporary file for the report
|
||||
tmpDir := os.TempDir()
|
||||
reportPath := filepath.Join(tmpDir, "report.html")
|
||||
|
||||
// Generate HTML report
|
||||
tmpl := `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Application Report</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
.header { border-bottom: 2px solid #333; padding-bottom: 10px; }
|
||||
.data { margin-top: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>Application Report</h1>
|
||||
<p>Generated: {{.Timestamp}}</p>
|
||||
</div>
|
||||
<div class="data">
|
||||
<!-- Report content here -->
|
||||
{{range .Items}}
|
||||
<p>{{.}}</p>
|
||||
{{end}}
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
// Write report to file
|
||||
file, err := os.Create(reportPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
t, err := template.New("report").Parse(tmpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = t.Execute(file, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Open in browser
|
||||
return app.Browser.OpenFile(reportPath)
|
||||
}
|
||||
```
|
||||
|
||||
### Development Tools
|
||||
|
||||
Open development resources during development:
|
||||
|
||||
```go
|
||||
func setupDevelopmentMenu(app *application.App) {
|
||||
if !app.Environment.Info().Debug {
|
||||
return // Only show in debug mode
|
||||
}
|
||||
|
||||
menu := app.Menu.New()
|
||||
devMenu := menu.AddSubmenu("Development")
|
||||
|
||||
devMenu.Add("Open DevTools").OnClick(func(ctx *application.Context) {
|
||||
// This would open browser devtools if available
|
||||
window := app.Window.Current()
|
||||
if window != nil {
|
||||
window.OpenDevTools()
|
||||
}
|
||||
})
|
||||
|
||||
devMenu.Add("View Source").OnClick(func(ctx *application.Context) {
|
||||
// Open source code repository
|
||||
app.Browser.OpenURL("https://github.com/youruser/yourapp")
|
||||
})
|
||||
|
||||
devMenu.Add("API Documentation").OnClick(func(ctx *application.Context) {
|
||||
// Open local API docs
|
||||
app.Browser.OpenURL("http://localhost:8080/docs")
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Graceful Error Handling
|
||||
|
||||
Always handle potential errors when opening URLs or files:
|
||||
|
||||
```go
|
||||
func openURLWithFallback(app *application.App, url string, fallbackMessage string) {
|
||||
err := app.Browser.OpenURL(url)
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open URL", "url", url, "error", err)
|
||||
|
||||
// Show fallback dialog with URL
|
||||
dialog := app.Dialog.Info()
|
||||
dialog.SetTitle("Unable to Open Link")
|
||||
dialog.SetMessage(fmt.Sprintf("%s\n\nURL: %s", fallbackMessage, url))
|
||||
dialog.Show()
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
openURLWithFallback(app,
|
||||
"https://docs.example.com",
|
||||
"Please open the following URL manually in your browser:")
|
||||
```
|
||||
|
||||
### User Feedback
|
||||
|
||||
Provide feedback when operations succeed or fail:
|
||||
|
||||
```go
|
||||
func openURLWithFeedback(app *application.App, url string) {
|
||||
err := app.Browser.OpenURL(url)
|
||||
if err != nil {
|
||||
// Show error dialog
|
||||
app.Dialog.Error().
|
||||
SetTitle("Browser Error").
|
||||
SetMessage(fmt.Sprintf("Could not open URL: %s", err.Error())).
|
||||
Show()
|
||||
} else {
|
||||
// Optionally show success notification
|
||||
app.Logger.Info("URL opened successfully", "url", url)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS:
|
||||
|
||||
- Uses the `open` command to launch the default browser
|
||||
- Respects user's default browser setting in System Preferences
|
||||
- May prompt for permission if the application is sandboxed
|
||||
- Handles `file://` URLs correctly for local files
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows:
|
||||
|
||||
- Uses Windows Shell API to open URLs
|
||||
- Respects default browser setting in Windows Settings
|
||||
- Handles Windows path formats correctly
|
||||
- May show security warnings for untrusted URLs
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux:
|
||||
|
||||
- Attempts to use `xdg-open` first, falls back to other methods
|
||||
- Behavior varies by desktop environment
|
||||
- Respects `BROWSER` environment variable if set
|
||||
- May require additional packages in minimal installations
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always Handle Errors**: Browser operations can fail for various reasons:
|
||||
```go
|
||||
if err := app.Browser.OpenURL(url); err != nil {
|
||||
app.Logger.Error("Failed to open browser", "error", err)
|
||||
// Provide fallback or user notification
|
||||
}
|
||||
```
|
||||
|
||||
2. **Validate URLs**: Ensure URLs are well-formed before opening:
|
||||
```go
|
||||
func isValidHTTPURL(str string) bool {
|
||||
u, err := url.Parse(str)
|
||||
return err == nil && (u.Scheme == "http" || u.Scheme == "https")
|
||||
}
|
||||
```
|
||||
|
||||
3. **User Confirmation**: For external links, consider asking user permission:
|
||||
```go
|
||||
// Show confirmation dialog before opening external links
|
||||
confirmAndOpen(app, "https://external-site.com")
|
||||
```
|
||||
|
||||
4. **Secure File Paths**: When opening files, ensure paths are safe:
|
||||
```go
|
||||
func openSafeFile(app *application.App, filename string) error {
|
||||
// Ensure file exists and is readable
|
||||
if _, err := os.Stat(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
return app.Browser.OpenFile(filename)
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a complete example showing various browser integration patterns:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Browser Integration Demo",
|
||||
})
|
||||
|
||||
// Setup menu with browser actions
|
||||
setupMenu(app)
|
||||
|
||||
// Create main window
|
||||
window := app.Window.New()
|
||||
window.SetTitle("Browser Integration")
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupMenu(app *application.App) {
|
||||
menu := app.Menu.New()
|
||||
|
||||
// File menu
|
||||
fileMenu := menu.AddSubmenu("File")
|
||||
fileMenu.Add("Generate Report").OnClick(func(ctx *application.Context) {
|
||||
generateHTMLReport(app)
|
||||
})
|
||||
|
||||
// Help menu
|
||||
helpMenu := menu.AddSubmenu("Help")
|
||||
helpMenu.Add("Documentation").OnClick(func(ctx *application.Context) {
|
||||
openWithConfirmation(app, "https://docs.example.com")
|
||||
})
|
||||
helpMenu.Add("Support").OnClick(func(ctx *application.Context) {
|
||||
openWithConfirmation(app, "https://support.example.com")
|
||||
})
|
||||
|
||||
app.Menu.Set(menu)
|
||||
}
|
||||
|
||||
func openWithConfirmation(app *application.App, url string) {
|
||||
dialog := app.Dialog.Question()
|
||||
dialog.SetTitle("Open External Link")
|
||||
dialog.SetMessage(fmt.Sprintf("Open %s in your browser?", url))
|
||||
|
||||
dialog.AddButton("Open").OnClick(func() {
|
||||
if err := app.Browser.OpenURL(url); err != nil {
|
||||
showError(app, "Failed to open URL", err)
|
||||
}
|
||||
})
|
||||
|
||||
dialog.AddButton("Cancel")
|
||||
dialog.Show()
|
||||
}
|
||||
|
||||
func generateHTMLReport(app *application.App) {
|
||||
// Create temporary HTML file
|
||||
tmpDir := os.TempDir()
|
||||
reportPath := filepath.Join(tmpDir, "demo_report.html")
|
||||
|
||||
html := `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Demo Report</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
.header { color: #333; border-bottom: 1px solid #ccc; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>Application Report</h1>
|
||||
<p>This is a sample report generated by the application.</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2>Report Details</h2>
|
||||
<p>This report was generated to demonstrate browser integration.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
err := os.WriteFile(reportPath, []byte(html), 0644)
|
||||
if err != nil {
|
||||
showError(app, "Failed to create report", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Open in browser
|
||||
err = app.Browser.OpenFile(reportPath)
|
||||
if err != nil {
|
||||
showError(app, "Failed to open report", err)
|
||||
}
|
||||
}
|
||||
|
||||
func showError(app *application.App, message string, err error) {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Error").
|
||||
SetMessage(fmt.Sprintf("%s: %v", message, err)).
|
||||
Show()
|
||||
}
|
||||
```
|
||||
|
||||
:::tip[Pro Tip]
|
||||
Consider providing fallback mechanisms for when browser operations fail, such as copying URLs to clipboard or showing them in a dialog for manual opening.
|
||||
:::
|
||||
|
||||
:::danger[Warning]
|
||||
Always validate URLs and file paths before opening them to prevent security issues. Be cautious about opening user-provided URLs without validation.
|
||||
:::
|
||||
|
|
@ -123,6 +123,7 @@ the application on macOS. Key features include:
|
|||
- Building binaries for amd64, arm64 and universal (both) architectures
|
||||
- Generating `.icns` icon file
|
||||
- Creating an `.app` bundle for distributing
|
||||
- Ad-hoc signing `.app` bundles
|
||||
- Setting macOS-specific build flags and environment variables
|
||||
|
||||
## Task Execution and Command Aliases
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ The Wails Clipboard API provides a simple interface for interacting with the sys
|
|||
The clipboard can be accessed through the application instance:
|
||||
|
||||
```go
|
||||
clipboard := app.Clipboard()
|
||||
clipboard := app.Clipboard
|
||||
```
|
||||
|
||||
## Setting Text
|
||||
|
|
@ -19,7 +19,7 @@ clipboard := app.Clipboard()
|
|||
To set text to the clipboard, utilise the `SetText` method:
|
||||
|
||||
```go
|
||||
success := app.Clipboard().SetText("Hello World")
|
||||
success := app.Clipboard.SetText("Hello World")
|
||||
if !success {
|
||||
// Handle error
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ Setting an empty string (`""`) effectively clears the text content from the clip
|
|||
To retrieve text from the clipboard, utilise the `Text` method:
|
||||
|
||||
```go
|
||||
text, ok := app.Clipboard().Text()
|
||||
text, ok := app.Clipboard.Text()
|
||||
if !ok {
|
||||
// Handle error
|
||||
} else {
|
||||
|
|
@ -75,7 +75,7 @@ func main() {
|
|||
})
|
||||
|
||||
// Create a custom menu
|
||||
menu := app.NewMenu()
|
||||
menu := app.Menu.New()
|
||||
if runtime.GOOS == "darwin" {
|
||||
menu.AddRole(application.AppMenu)
|
||||
}
|
||||
|
|
@ -83,33 +83,33 @@ func main() {
|
|||
// Add clipboard operations to menu
|
||||
setClipboardMenu := menu.AddSubmenu("Set Clipboard")
|
||||
setClipboardMenu.Add("Set Text 'Hello'").OnClick(func(ctx *application.Context) {
|
||||
success := app.Clipboard().SetText("Hello")
|
||||
success := app.Clipboard.SetText("Hello")
|
||||
if !success {
|
||||
application.InfoDialog().SetMessage("Failed to set clipboard text").Show()
|
||||
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()
|
||||
result, ok := app.Clipboard.Text()
|
||||
if !ok {
|
||||
application.InfoDialog().SetMessage("Failed to get clipboard text").Show()
|
||||
app.Dialog.Info().SetMessage("Failed to get clipboard text").Show()
|
||||
} else {
|
||||
application.InfoDialog().SetMessage("Got:\n\n" + result).Show()
|
||||
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("")
|
||||
success := app.Clipboard.SetText("")
|
||||
if success {
|
||||
application.InfoDialog().SetMessage("Clipboard text cleared").Show()
|
||||
app.Dialog.Info().SetMessage("Clipboard text cleared").Show()
|
||||
} else {
|
||||
application.InfoDialog().SetMessage("Clipboard text not cleared").Show()
|
||||
app.Dialog.Info().SetMessage("Clipboard text not cleared").Show()
|
||||
}
|
||||
})
|
||||
|
||||
app.SetMenu(menu)
|
||||
app.Menu.Set(menu)
|
||||
app.NewWebviewWindow()
|
||||
|
||||
err := app.Run()
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ Context menus are popup menus that appear when right-clicking elements in your a
|
|||
|
||||
## Creating a Context Menu
|
||||
|
||||
To create a context menu, utilise the `NewContextMenu` method from your application instance:
|
||||
To create a context menu, use the `Add` method from the ContextMenu manager:
|
||||
|
||||
```go
|
||||
contextMenu := application.NewContextMenu("menu-id")
|
||||
contextMenu := app.ContextMenu.Add("menu-id", application.NewContextMenu())
|
||||
```
|
||||
|
||||
The `menu-id` parameter is a unique identifier for the menu that will be used to associate it with HTML elements.
|
||||
|
|
@ -23,7 +23,7 @@ The `menu-id` parameter is a unique identifier for the menu that will be used to
|
|||
You can add items to your context menu using the same methods as application menus. Here's a simple example:
|
||||
|
||||
```go
|
||||
contextMenu := application.NewContextMenu("editor-menu")
|
||||
contextMenu := application.NewContextMenu()
|
||||
contextMenu.Add("Cut").OnClick(func(ctx *application.Context) {
|
||||
// Handle cut action
|
||||
})
|
||||
|
|
@ -33,6 +33,9 @@ contextMenu.Add("Copy").OnClick(func(ctx *application.Context) {
|
|||
contextMenu.Add("Paste").OnClick(func(ctx *application.Context) {
|
||||
// Handle paste action
|
||||
})
|
||||
|
||||
// Register the context menu with the manager
|
||||
app.ContextMenu.Add("editor-menu", contextMenu)
|
||||
```
|
||||
|
||||
:::tip[Menu Items]
|
||||
|
|
@ -44,12 +47,15 @@ For detailed information about available menu item types and properties, refer t
|
|||
Context menus can receive data from the HTML element that triggered them. This data can be accessed in the click handlers:
|
||||
|
||||
```go
|
||||
contextMenu := application.NewContextMenu("image-menu")
|
||||
contextMenu := app.ContextMenu.New()
|
||||
menuItem := contextMenu.Add("Process Image")
|
||||
menuItem.OnClick(func(ctx *application.Context) {
|
||||
imageID := ctx.ContextMenuData()
|
||||
// Process the image using the ID
|
||||
})
|
||||
|
||||
// Register the context menu with the manager
|
||||
app.ContextMenu.Add("image-menu", contextMenu)
|
||||
```
|
||||
|
||||
## Associating with HTML Elements
|
||||
|
|
@ -65,6 +71,10 @@ To associate a context menu with an HTML element, use the `--custom-contextmenu`
|
|||
- `--custom-contextmenu`: Specifies the menu ID (must match the ID used in `NewContextMenu`)
|
||||
- `--custom-contextmenu-data`: Optional data that will be passed to the click handlers
|
||||
|
||||
:::note
|
||||
This feature will only work as expected after the runtime [has been initialised](../runtime#initialisation).
|
||||
:::
|
||||
|
||||
## Default Context Menu
|
||||
|
||||
The default context menu is the webview's built-in context menu that provides system-level operations. You can control its visibility using the `--default-contextmenu` CSS property:
|
||||
|
|
@ -99,9 +109,12 @@ The `auto` setting enables "smart" context menu behaviour:
|
|||
Menu items can be updated dynamically using the `SetLabel` method and other property setters. After making changes, call `Update` on the menu to apply them:
|
||||
|
||||
```go
|
||||
contextMenu := application.NewContextMenu("dynamic-menu")
|
||||
contextMenu := application.NewContextMenu()
|
||||
menuItem := contextMenu.Add("Initial Label")
|
||||
|
||||
// Register the context menu with the manager
|
||||
app.ContextMenu.Add("dynamic-menu", contextMenu)
|
||||
|
||||
// Later, update the menu item
|
||||
menuItem.SetLabel("New Label")
|
||||
contextMenu.Update()
|
||||
|
|
@ -182,7 +195,7 @@ func main() {
|
|||
})
|
||||
|
||||
// Create a context menu
|
||||
contextMenu := application.NewContextMenu("test")
|
||||
contextMenu := app.ContextMenu.New()
|
||||
|
||||
// Add items that respond to context data
|
||||
clickMe := contextMenu.Add("Click to show context data")
|
||||
|
|
@ -194,6 +207,9 @@ func main() {
|
|||
contextMenu.Update()
|
||||
})
|
||||
|
||||
// Register the context menu with the manager
|
||||
app.ContextMenu.Add("test", contextMenu)
|
||||
|
||||
window := app.NewWebviewWindow()
|
||||
window.SetTitle("Context Menu Demo")
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Wails provides a comprehensive dialog system for displaying native system dialog
|
|||
Display simple informational messages to users:
|
||||
|
||||
```go
|
||||
dialog := application.InfoDialog()
|
||||
dialog := app.Dialog.Info()
|
||||
dialog.SetTitle("Welcome")
|
||||
dialog.SetMessage("Welcome to our application!")
|
||||
dialog.Show()
|
||||
|
|
@ -26,7 +26,7 @@ dialog.Show()
|
|||
Present users with questions and customisable buttons:
|
||||
|
||||
```go
|
||||
dialog := application.QuestionDialog()
|
||||
dialog := app.Dialog.Question()
|
||||
dialog.SetTitle("Save Changes")
|
||||
dialog.SetMessage("Do you want to save your changes?")
|
||||
dialog.AddButton("Save").OnClick(func() {
|
||||
|
|
@ -42,7 +42,7 @@ dialog.Show()
|
|||
Display error messages:
|
||||
|
||||
```go
|
||||
dialog := application.ErrorDialog()
|
||||
dialog := app.Dialog.Error()
|
||||
dialog.SetTitle("Error")
|
||||
dialog.SetMessage("Failed to save file")
|
||||
dialog.Show()
|
||||
|
|
@ -55,7 +55,7 @@ dialog.Show()
|
|||
Allow users to select files to open:
|
||||
|
||||
```go
|
||||
dialog := application.OpenFileDialog()
|
||||
dialog := app.Dialog.OpenFile()
|
||||
dialog.SetTitle("Select Image")
|
||||
dialog.SetFilters([]*application.FileFilter{
|
||||
{
|
||||
|
|
@ -80,7 +80,7 @@ if paths, err := dialog.PromptForMultipleSelection(); err == nil {
|
|||
Allow users to choose where to save files:
|
||||
|
||||
```go
|
||||
dialog := application.SaveFileDialog()
|
||||
dialog := app.Dialog.SaveFile()
|
||||
dialog.SetTitle("Save Document")
|
||||
dialog.SetDefaultFilename("document.txt")
|
||||
dialog.SetFilters([]*application.FileFilter{
|
||||
|
|
@ -102,7 +102,7 @@ if path, err := dialog.PromptForSingleSelection(); err == nil {
|
|||
Dialogs can use custom icons from the built-in icon set:
|
||||
|
||||
```go
|
||||
dialog := application.InfoDialog()
|
||||
dialog := app.Dialog.Info()
|
||||
dialog.SetIcon(icons.ApplicationDarkMode256)
|
||||
```
|
||||
|
||||
|
|
@ -111,8 +111,8 @@ dialog.SetIcon(icons.ApplicationDarkMode256)
|
|||
Dialogs can be attached to specific windows:
|
||||
|
||||
```go
|
||||
dialog := application.QuestionDialog()
|
||||
dialog.AttachToWindow(app.CurrentWindow())
|
||||
dialog := app.Dialog.Question()
|
||||
dialog.AttachToWindow(app.Window.Current())
|
||||
dialog.Show()
|
||||
```
|
||||
|
||||
|
|
@ -121,7 +121,7 @@ dialog.Show()
|
|||
Create buttons with custom labels and actions:
|
||||
|
||||
```go
|
||||
dialog := application.QuestionDialog()
|
||||
dialog := app.Dialog.Question()
|
||||
dialog.SetMessage("Choose an action")
|
||||
|
||||
// Add buttons with custom handlers
|
||||
|
|
@ -183,7 +183,9 @@ dialog.SetDefaultButton(cancelButton) // Set default button
|
|||
Allow users to select directories:
|
||||
|
||||
```go
|
||||
dialog := application.DirectoryDialog()
|
||||
dialog := app.Dialog.OpenFile()
|
||||
dialog.CanChooseDirectories(true)
|
||||
dialog.CanChooseFiles(false)
|
||||
dialog.SetTitle("Select Project Directory")
|
||||
if path, err := dialog.PromptForSingleSelection(); err == nil {
|
||||
// Use selected directory path
|
||||
|
|
@ -195,7 +197,7 @@ if path, err := dialog.PromptForSingleSelection(); err == nil {
|
|||
Display application information:
|
||||
|
||||
```go
|
||||
app.ShowAboutDialog()
|
||||
app.Menu.ShowAbout()
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
|
|
|||
620
docs/src/content/docs/learn/environment.mdx
Normal file
620
docs/src/content/docs/learn/environment.mdx
Normal file
|
|
@ -0,0 +1,620 @@
|
|||
---
|
||||
title: Environment
|
||||
sidebar:
|
||||
order: 59
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
Wails provides comprehensive environment information through the EnvironmentManager API. This allows your application to detect system properties, theme preferences, and integrate with the operating system's file manager.
|
||||
|
||||
## Accessing the Environment Manager
|
||||
|
||||
The environment manager is accessed through the `Env` property on your application instance:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "Environment Demo",
|
||||
})
|
||||
|
||||
// Access the environment manager
|
||||
env := app.Env
|
||||
```
|
||||
|
||||
## System Information
|
||||
|
||||
### Get Environment Information
|
||||
|
||||
Retrieve comprehensive information about the runtime environment:
|
||||
|
||||
```go
|
||||
envInfo := app.Env.Info()
|
||||
|
||||
app.Logger.Info("Environment information",
|
||||
"os", envInfo.OS, // "windows", "darwin", "linux"
|
||||
"arch", envInfo.Arch, // "amd64", "arm64", etc.
|
||||
"debug", envInfo.Debug, // Debug mode flag
|
||||
)
|
||||
|
||||
// Operating system details
|
||||
if envInfo.OSInfo != nil {
|
||||
app.Logger.Info("OS details",
|
||||
"name", envInfo.OSInfo.Name,
|
||||
"version", envInfo.OSInfo.Version,
|
||||
)
|
||||
}
|
||||
|
||||
// Platform-specific information
|
||||
for key, value := range envInfo.PlatformInfo {
|
||||
app.Logger.Info("Platform info", "key", key, "value", value)
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Structure
|
||||
|
||||
The environment information includes several important fields:
|
||||
|
||||
```go
|
||||
type EnvironmentInfo struct {
|
||||
OS string // Operating system: "windows", "darwin", "linux"
|
||||
Arch string // Architecture: "amd64", "arm64", "386", etc.
|
||||
Debug bool // Whether running in debug mode
|
||||
OSInfo *operatingsystem.OS // Detailed OS information
|
||||
PlatformInfo map[string]any // Platform-specific details
|
||||
}
|
||||
```
|
||||
|
||||
## Theme Detection
|
||||
|
||||
### Dark Mode Detection
|
||||
|
||||
Detect whether the system is using dark mode:
|
||||
|
||||
```go
|
||||
if app.Env.IsDarkMode() {
|
||||
app.Logger.Info("System is in dark mode")
|
||||
// Apply dark theme to your application
|
||||
applyDarkTheme()
|
||||
} else {
|
||||
app.Logger.Info("System is in light mode")
|
||||
// Apply light theme to your application
|
||||
applyLightTheme()
|
||||
}
|
||||
```
|
||||
|
||||
### Theme Change Monitoring
|
||||
|
||||
Listen for theme changes to update your application dynamically:
|
||||
|
||||
```go
|
||||
import "github.com/wailsapp/wails/v3/pkg/events"
|
||||
|
||||
// Listen for theme changes
|
||||
app.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
|
||||
if app.Env.IsDarkMode() {
|
||||
app.Logger.Info("Switched to dark mode")
|
||||
updateApplicationTheme("dark")
|
||||
} else {
|
||||
app.Logger.Info("Switched to light mode")
|
||||
updateApplicationTheme("light")
|
||||
}
|
||||
})
|
||||
|
||||
func updateApplicationTheme(theme string) {
|
||||
// Update your application's theme
|
||||
// This could emit an event to the frontend
|
||||
app.Event.Emit("theme:changed", theme)
|
||||
}
|
||||
```
|
||||
|
||||
## File Manager Integration
|
||||
|
||||
### Open File Manager
|
||||
|
||||
Open the system's file manager at a specific location:
|
||||
|
||||
```go
|
||||
// Open file manager at a directory
|
||||
err := app.Env.OpenFileManager("/Users/username/Documents", false)
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open file manager", "error", err)
|
||||
}
|
||||
|
||||
// Open file manager and select a specific file
|
||||
err = app.Env.OpenFileManager("/Users/username/Documents/report.pdf", true)
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open file manager with selection", "error", err)
|
||||
}
|
||||
```
|
||||
|
||||
### Common Use Cases
|
||||
|
||||
Show files or folders in the file manager from your application:
|
||||
|
||||
```go
|
||||
func showInFileManager(app *application.App, path string) {
|
||||
err := app.Env.OpenFileManager(path, true)
|
||||
if err != nil {
|
||||
// Fallback: try opening just the directory
|
||||
dir := filepath.Dir(path)
|
||||
err = app.Env.OpenFileManager(dir, false)
|
||||
if err != nil {
|
||||
app.Logger.Error("Could not open file manager", "path", path, "error", err)
|
||||
|
||||
// Show error to user
|
||||
app.Dialog.Error().
|
||||
SetTitle("File Manager Error").
|
||||
SetMessage("Could not open file manager").
|
||||
Show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage examples
|
||||
func setupFileMenu(app *application.App) {
|
||||
menu := app.Menu.New()
|
||||
fileMenu := menu.AddSubmenu("File")
|
||||
|
||||
fileMenu.Add("Show Downloads Folder").OnClick(func(ctx *application.Context) {
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
downloadsDir := filepath.Join(homeDir, "Downloads")
|
||||
showInFileManager(app, downloadsDir)
|
||||
})
|
||||
|
||||
fileMenu.Add("Show Application Data").OnClick(func(ctx *application.Context) {
|
||||
configDir, _ := os.UserConfigDir()
|
||||
appDir := filepath.Join(configDir, "MyApp")
|
||||
showInFileManager(app, appDir)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Platform-Specific Behavior
|
||||
|
||||
### Adaptive Application Behavior
|
||||
|
||||
Use environment information to adapt your application's behavior:
|
||||
|
||||
```go
|
||||
func configureForPlatform(app *application.App) {
|
||||
envInfo := app.Env.Info()
|
||||
|
||||
switch envInfo.OS {
|
||||
case "darwin":
|
||||
configureMacOS(app)
|
||||
case "windows":
|
||||
configureWindows(app)
|
||||
case "linux":
|
||||
configureLinux(app)
|
||||
}
|
||||
|
||||
// Adapt to architecture
|
||||
if envInfo.Arch == "arm64" {
|
||||
app.Logger.Info("Running on ARM architecture")
|
||||
// Potentially optimize for ARM
|
||||
}
|
||||
}
|
||||
|
||||
func configureMacOS(app *application.App) {
|
||||
app.Logger.Info("Configuring for macOS")
|
||||
|
||||
// macOS-specific configuration
|
||||
menu := app.Menu.New()
|
||||
menu.AddRole(application.AppMenu) // Add standard macOS app menu
|
||||
|
||||
// Handle dark mode
|
||||
if app.Env.IsDarkMode() {
|
||||
setMacOSDarkTheme()
|
||||
}
|
||||
}
|
||||
|
||||
func configureWindows(app *application.App) {
|
||||
app.Logger.Info("Configuring for Windows")
|
||||
|
||||
// Windows-specific configuration
|
||||
// Set up Windows-style menus, key bindings, etc.
|
||||
}
|
||||
|
||||
func configureLinux(app *application.App) {
|
||||
app.Logger.Info("Configuring for Linux")
|
||||
|
||||
// Linux-specific configuration
|
||||
// Adapt to different desktop environments
|
||||
}
|
||||
```
|
||||
|
||||
## Debug Mode Handling
|
||||
|
||||
### Development vs Production
|
||||
|
||||
Use debug mode information to enable development features:
|
||||
|
||||
```go
|
||||
func setupApplicationMode(app *application.App) {
|
||||
envInfo := app.Env.Info()
|
||||
|
||||
if envInfo.Debug {
|
||||
app.Logger.Info("Running in debug mode")
|
||||
setupDevelopmentFeatures(app)
|
||||
} else {
|
||||
app.Logger.Info("Running in production mode")
|
||||
setupProductionFeatures(app)
|
||||
}
|
||||
}
|
||||
|
||||
func setupDevelopmentFeatures(app *application.App) {
|
||||
// Enable development-only features
|
||||
menu := app.Menu.New()
|
||||
|
||||
// Add development menu
|
||||
devMenu := menu.AddSubmenu("Development")
|
||||
devMenu.Add("Reload Application").OnClick(func(ctx *application.Context) {
|
||||
// Reload the application
|
||||
window := app.Window.Current()
|
||||
if window != nil {
|
||||
window.Reload()
|
||||
}
|
||||
})
|
||||
|
||||
devMenu.Add("Open DevTools").OnClick(func(ctx *application.Context) {
|
||||
window := app.Window.Current()
|
||||
if window != nil {
|
||||
window.OpenDevTools()
|
||||
}
|
||||
})
|
||||
|
||||
devMenu.Add("Show Environment").OnClick(func(ctx *application.Context) {
|
||||
showEnvironmentDialog(app)
|
||||
})
|
||||
}
|
||||
|
||||
func setupProductionFeatures(app *application.App) {
|
||||
// Production-only features
|
||||
// Disable debug logging, enable analytics, etc.
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Information Dialog
|
||||
|
||||
### Display System Information
|
||||
|
||||
Create a dialog showing environment information:
|
||||
|
||||
```go
|
||||
func showEnvironmentDialog(app *application.App) {
|
||||
envInfo := app.Env.Info()
|
||||
|
||||
details := fmt.Sprintf(`Environment Information:
|
||||
|
||||
Operating System: %s
|
||||
Architecture: %s
|
||||
Debug Mode: %t
|
||||
|
||||
Dark Mode: %t
|
||||
|
||||
Platform Information:`,
|
||||
envInfo.OS,
|
||||
envInfo.Arch,
|
||||
envInfo.Debug,
|
||||
app.Env.IsDarkMode())
|
||||
|
||||
// Add platform-specific details
|
||||
for key, value := range envInfo.PlatformInfo {
|
||||
details += fmt.Sprintf("\n%s: %v", key, value)
|
||||
}
|
||||
|
||||
if envInfo.OSInfo != nil {
|
||||
details += fmt.Sprintf("\n\nOS Details:\nName: %s\nVersion: %s",
|
||||
envInfo.OSInfo.Name,
|
||||
envInfo.OSInfo.Version)
|
||||
}
|
||||
|
||||
dialog := app.Dialog.Info()
|
||||
dialog.SetTitle("Environment Information")
|
||||
dialog.SetMessage(details)
|
||||
dialog.Show()
|
||||
}
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS:
|
||||
|
||||
- Dark mode detection uses system appearance settings
|
||||
- File manager operations use Finder
|
||||
- Platform info includes macOS version details
|
||||
- Architecture may be "arm64" on Apple Silicon Macs
|
||||
|
||||
```go
|
||||
if envInfo.OS == "darwin" {
|
||||
// macOS-specific handling
|
||||
if envInfo.Arch == "arm64" {
|
||||
app.Logger.Info("Running on Apple Silicon")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows:
|
||||
|
||||
- Dark mode detection uses Windows theme settings
|
||||
- File manager operations use Windows Explorer
|
||||
- Platform info includes Windows version details
|
||||
- May include additional Windows-specific information
|
||||
|
||||
```go
|
||||
if envInfo.OS == "windows" {
|
||||
// Windows-specific handling
|
||||
for key, value := range envInfo.PlatformInfo {
|
||||
if key == "windows_version" {
|
||||
app.Logger.Info("Windows version", "version", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux:
|
||||
|
||||
- Dark mode detection varies by desktop environment
|
||||
- File manager operations use system default file manager
|
||||
- Platform info includes distribution details
|
||||
- Behavior may vary between different Linux distributions
|
||||
|
||||
```go
|
||||
if envInfo.OS == "linux" {
|
||||
// Linux-specific handling
|
||||
if distro, ok := envInfo.PlatformInfo["distribution"]; ok {
|
||||
app.Logger.Info("Linux distribution", "distro", distro)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Cache Environment Information**: Environment info rarely changes during runtime:
|
||||
```go
|
||||
type App struct {
|
||||
envInfo *application.EnvironmentInfo
|
||||
}
|
||||
|
||||
func (a *App) getEnvInfo() application.EnvironmentInfo {
|
||||
if a.envInfo == nil {
|
||||
info := a.app.Env.Info()
|
||||
a.envInfo = &info
|
||||
}
|
||||
return *a.envInfo
|
||||
}
|
||||
```
|
||||
|
||||
2. **Handle Theme Changes**: Listen for system theme changes:
|
||||
```go
|
||||
app.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
|
||||
updateTheme(app.Env.IsDarkMode())
|
||||
})
|
||||
```
|
||||
|
||||
3. **Graceful File Manager Failures**: Always handle file manager errors:
|
||||
```go
|
||||
func openFileManagerSafely(app *application.App, path string) {
|
||||
err := app.Env.OpenFileManager(path, false)
|
||||
if err != nil {
|
||||
// Provide fallback or user notification
|
||||
app.Logger.Warn("Could not open file manager", "path", path)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **Platform-Specific Features**: Use environment info to enable platform features:
|
||||
```go
|
||||
envInfo := app.Env.Info()
|
||||
if envInfo.OS == "darwin" {
|
||||
// Enable macOS-specific features
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a complete example demonstrating environment management:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Environment Demo",
|
||||
})
|
||||
|
||||
// Setup application based on environment
|
||||
setupForEnvironment(app)
|
||||
|
||||
// Monitor theme changes
|
||||
monitorThemeChanges(app)
|
||||
|
||||
// Create menu with environment features
|
||||
setupEnvironmentMenu(app)
|
||||
|
||||
// Create main window
|
||||
window := app.Window.New()
|
||||
window.SetTitle("Environment Demo")
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupForEnvironment(app *application.App) {
|
||||
envInfo := app.Env.Info()
|
||||
|
||||
app.Logger.Info("Application environment",
|
||||
"os", envInfo.OS,
|
||||
"arch", envInfo.Arch,
|
||||
"debug", envInfo.Debug,
|
||||
"darkMode", app.Env.IsDarkMode(),
|
||||
)
|
||||
|
||||
// Configure for platform
|
||||
switch envInfo.OS {
|
||||
case "darwin":
|
||||
app.Logger.Info("Configuring for macOS")
|
||||
// macOS-specific setup
|
||||
case "windows":
|
||||
app.Logger.Info("Configuring for Windows")
|
||||
// Windows-specific setup
|
||||
case "linux":
|
||||
app.Logger.Info("Configuring for Linux")
|
||||
// Linux-specific setup
|
||||
}
|
||||
|
||||
// Apply initial theme
|
||||
if app.Env.IsDarkMode() {
|
||||
applyDarkTheme(app)
|
||||
} else {
|
||||
applyLightTheme(app)
|
||||
}
|
||||
}
|
||||
|
||||
func monitorThemeChanges(app *application.App) {
|
||||
app.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
|
||||
if app.Env.IsDarkMode() {
|
||||
app.Logger.Info("System switched to dark mode")
|
||||
applyDarkTheme(app)
|
||||
} else {
|
||||
app.Logger.Info("System switched to light mode")
|
||||
applyLightTheme(app)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func setupEnvironmentMenu(app *application.App) {
|
||||
menu := app.Menu.New()
|
||||
|
||||
// Add platform-specific app menu
|
||||
if runtime.GOOS == "darwin" {
|
||||
menu.AddRole(application.AppMenu)
|
||||
}
|
||||
|
||||
// Tools menu
|
||||
toolsMenu := menu.AddSubmenu("Tools")
|
||||
|
||||
toolsMenu.Add("Show Environment Info").OnClick(func(ctx *application.Context) {
|
||||
showEnvironmentInfo(app)
|
||||
})
|
||||
|
||||
toolsMenu.Add("Open Downloads Folder").OnClick(func(ctx *application.Context) {
|
||||
openDownloadsFolder(app)
|
||||
})
|
||||
|
||||
toolsMenu.Add("Toggle Theme").OnClick(func(ctx *application.Context) {
|
||||
// This would typically be handled by the system
|
||||
// but shown here for demonstration
|
||||
toggleTheme(app)
|
||||
})
|
||||
|
||||
app.Menu.Set(menu)
|
||||
}
|
||||
|
||||
func showEnvironmentInfo(app *application.App) {
|
||||
envInfo := app.Env.Info()
|
||||
|
||||
message := fmt.Sprintf(`Environment Information:
|
||||
|
||||
Operating System: %s
|
||||
Architecture: %s
|
||||
Debug Mode: %t
|
||||
Dark Mode: %t
|
||||
|
||||
Platform Details:`,
|
||||
envInfo.OS,
|
||||
envInfo.Arch,
|
||||
envInfo.Debug,
|
||||
app.Env.IsDarkMode())
|
||||
|
||||
for key, value := range envInfo.PlatformInfo {
|
||||
message += fmt.Sprintf("\n%s: %v", key, value)
|
||||
}
|
||||
|
||||
if envInfo.OSInfo != nil {
|
||||
message += fmt.Sprintf("\n\nOS Information:\nName: %s\nVersion: %s",
|
||||
envInfo.OSInfo.Name,
|
||||
envInfo.OSInfo.Version)
|
||||
}
|
||||
|
||||
dialog := app.Dialog.Info()
|
||||
dialog.SetTitle("Environment Information")
|
||||
dialog.SetMessage(message)
|
||||
dialog.Show()
|
||||
}
|
||||
|
||||
func openDownloadsFolder(app *application.App) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
app.Logger.Error("Could not get home directory", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
downloadsDir := filepath.Join(homeDir, "Downloads")
|
||||
err = app.Env.OpenFileManager(downloadsDir, false)
|
||||
if err != nil {
|
||||
app.Logger.Error("Could not open Downloads folder", "error", err)
|
||||
|
||||
app.Dialog.Error().
|
||||
SetTitle("File Manager Error").
|
||||
SetMessage("Could not open Downloads folder").
|
||||
Show()
|
||||
}
|
||||
}
|
||||
|
||||
func applyDarkTheme(app *application.App) {
|
||||
app.Logger.Info("Applying dark theme")
|
||||
// Emit theme change to frontend
|
||||
app.Event.Emit("theme:apply", "dark")
|
||||
}
|
||||
|
||||
func applyLightTheme(app *application.App) {
|
||||
app.Logger.Info("Applying light theme")
|
||||
// Emit theme change to frontend
|
||||
app.Event.Emit("theme:apply", "light")
|
||||
}
|
||||
|
||||
func toggleTheme(app *application.App) {
|
||||
// This is just for demonstration
|
||||
// Real theme changes should come from the system
|
||||
currentlyDark := app.Env.IsDarkMode()
|
||||
if currentlyDark {
|
||||
applyLightTheme(app)
|
||||
} else {
|
||||
applyDarkTheme(app)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::tip[Pro Tip]
|
||||
Use environment information to provide platform-appropriate user experiences. For example, use Command key shortcuts on macOS and Control key shortcuts on Windows/Linux.
|
||||
:::
|
||||
|
||||
:::danger[Warning]
|
||||
Environment information is generally stable during application runtime, but theme preferences can change. Always listen for theme change events to keep your UI synchronized.
|
||||
:::
|
||||
|
|
@ -422,33 +422,85 @@ app.OnApplicationEvent(events.Windows.WebViewNavigationCompleted, func(event *ap
|
|||
|
||||
## Custom Events
|
||||
|
||||
You can emit and listen for custom events to enable communication between different parts of your application.
|
||||
You can emit and listen for custom events to enable communication between different parts of your application. Wails v3 provides both a traditional API and a new [Manager API](/learn/manager-api) for better organization.
|
||||
|
||||
### Emitting Events
|
||||
|
||||
You can emit custom events from anywhere in your application:
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Manager API (Recommended)">
|
||||
```go
|
||||
// Emit an event with data from the application
|
||||
// NEW: Using the Event Manager (recommended)
|
||||
app.Event.Emit("myevent", "hello")
|
||||
|
||||
// Emit from a specific window
|
||||
window.EmitEvent("windowevent", "window specific data")
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem label="Traditional API">
|
||||
```go
|
||||
// Traditional API (still supported)
|
||||
app.EmitEvent("myevent", "hello")
|
||||
|
||||
// Emit from a specific window
|
||||
window.EmitEvent("windowevent", "window specific data")
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Handling Custom Events
|
||||
|
||||
Listen for custom events using the `OnEvent` method:
|
||||
Listen for custom events using the event management methods:
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Manager API (Recommended)">
|
||||
```go
|
||||
app.OnEvent("myevent", func(e *application.CustomEvent) {
|
||||
// NEW: Using the Event Manager (recommended)
|
||||
cancelFunc := app.Event.On("myevent", func(e *application.CustomEvent) {
|
||||
// Access event information
|
||||
name := e.Name // Event name
|
||||
data := e.Data // Event data
|
||||
sender := e.Sender // Name of the window sending the event, or "" if sent from application
|
||||
cancelled := e.IsCancelled() // Event cancellation status
|
||||
})
|
||||
|
||||
// Remove specific event listener
|
||||
app.Event.Off("myevent")
|
||||
|
||||
// Remove all event listeners
|
||||
app.Event.Reset()
|
||||
|
||||
// Listen for events a limited number of times
|
||||
app.Event.OnMultiple("myevent", func(e *application.CustomEvent) {
|
||||
// This will only be called 3 times
|
||||
}, 3)
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem label="Traditional API">
|
||||
```go
|
||||
// Traditional API (still supported)
|
||||
cancelFunc := app.OnEvent("myevent", func(e *application.CustomEvent) {
|
||||
// Access event information
|
||||
name := e.Name // Event name
|
||||
data := e.Data // Event data
|
||||
sender := e.Sender // Name of the window sending the event, or "" if sent from application
|
||||
cancelled := e.IsCancelled() // Event cancellation status
|
||||
})
|
||||
|
||||
// Remove specific event listener
|
||||
app.OffEvent("myevent")
|
||||
|
||||
// Remove all event listeners
|
||||
app.ResetEvents()
|
||||
|
||||
// Listen for events a limited number of times
|
||||
app.OnMultipleEvent("myevent", func(e *application.CustomEvent) {
|
||||
// This will only be called 3 times
|
||||
}, 3)
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Event Cancellation
|
||||
|
||||
|
|
@ -479,7 +531,7 @@ window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent
|
|||
})
|
||||
|
||||
// For custom events
|
||||
app.OnEvent("myevent", func(e *application.CustomEvent) {
|
||||
app.Event.On("myevent", func(e *application.CustomEvent) {
|
||||
if e.IsCancelled() {
|
||||
app.Logger.Info("Event was cancelled")
|
||||
return
|
||||
|
|
|
|||
437
docs/src/content/docs/learn/keybindings.mdx
Normal file
437
docs/src/content/docs/learn/keybindings.mdx
Normal file
|
|
@ -0,0 +1,437 @@
|
|||
---
|
||||
title: Key Bindings
|
||||
sidebar:
|
||||
order: 56
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
Wails provides a powerful key binding system that allows you to register global keyboard shortcuts that work across all windows in your application. This enables users to quickly access functionality without navigating through menus.
|
||||
|
||||
## Accessing the Key Binding Manager
|
||||
|
||||
The key binding manager is accessed through the `KeyBindings` property on your application instance:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "Keyboard Shortcuts Demo",
|
||||
})
|
||||
|
||||
// Access the key binding manager
|
||||
keyBindings := app.KeyBinding
|
||||
```
|
||||
|
||||
## Adding Key Bindings
|
||||
|
||||
### Basic Key Binding
|
||||
|
||||
Register a simple keyboard shortcut:
|
||||
|
||||
```go
|
||||
app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) {
|
||||
// Handle save action
|
||||
app.Logger.Info("Save shortcut triggered")
|
||||
// Perform save operation...
|
||||
})
|
||||
```
|
||||
|
||||
### Multiple Key Bindings
|
||||
|
||||
Register multiple shortcuts for common operations:
|
||||
|
||||
```go
|
||||
// File operations
|
||||
app.KeyBinding.Add("Ctrl+N", func(window *application.WebviewWindow) {
|
||||
// New file
|
||||
window.EmitEvent("file:new", nil)
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("Ctrl+O", func(window *application.WebviewWindow) {
|
||||
// Open file
|
||||
dialog := app.Dialog.OpenFile()
|
||||
if file, err := dialog.PromptForSingleSelection(); err == nil {
|
||||
window.EmitEvent("file:open", file)
|
||||
}
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) {
|
||||
// Save file
|
||||
window.EmitEvent("file:save", nil)
|
||||
})
|
||||
|
||||
// Edit operations
|
||||
app.KeyBinding.Add("Ctrl+Z", func(window *application.WebviewWindow) {
|
||||
// Undo
|
||||
window.EmitEvent("edit:undo", nil)
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("Ctrl+Y", func(window *application.WebviewWindow) {
|
||||
// Redo (Windows/Linux)
|
||||
window.EmitEvent("edit:redo", nil)
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("Cmd+Shift+Z", func(window *application.WebviewWindow) {
|
||||
// Redo (macOS)
|
||||
window.EmitEvent("edit:redo", nil)
|
||||
})
|
||||
```
|
||||
|
||||
## Key Binding Accelerators
|
||||
|
||||
### Accelerator Format
|
||||
|
||||
Key bindings use a standard accelerator format with modifiers and keys:
|
||||
|
||||
```go
|
||||
// Modifier keys
|
||||
"Ctrl+S" // Control + S
|
||||
"Cmd+S" // Command + S (macOS)
|
||||
"Alt+F4" // Alt + F4
|
||||
"Shift+Ctrl+Z" // Shift + Control + Z
|
||||
|
||||
// Function keys
|
||||
"F1" // F1 key
|
||||
"Ctrl+F5" // Control + F5
|
||||
|
||||
// Special keys
|
||||
"Escape" // Escape key
|
||||
"Enter" // Enter key
|
||||
"Space" // Spacebar
|
||||
"Tab" // Tab key
|
||||
"Backspace" // Backspace key
|
||||
"Delete" // Delete key
|
||||
|
||||
// Arrow keys
|
||||
"Up" // Up arrow
|
||||
"Down" // Down arrow
|
||||
"Left" // Left arrow
|
||||
"Right" // Right arrow
|
||||
```
|
||||
|
||||
### Platform-Specific Accelerators
|
||||
|
||||
Handle platform differences for common shortcuts:
|
||||
|
||||
```go
|
||||
import "runtime"
|
||||
|
||||
// Cross-platform save shortcut
|
||||
if runtime.GOOS == "darwin" {
|
||||
app.KeyBinding.Add("Cmd+S", saveHandler)
|
||||
} else {
|
||||
app.KeyBinding.Add("Ctrl+S", saveHandler)
|
||||
}
|
||||
|
||||
// Or register both
|
||||
app.KeyBinding.Add("Ctrl+S", saveHandler)
|
||||
app.KeyBinding.Add("Cmd+S", saveHandler)
|
||||
```
|
||||
|
||||
## Managing Key Bindings
|
||||
|
||||
### Removing Key Bindings
|
||||
|
||||
Remove key bindings when they're no longer needed:
|
||||
|
||||
```go
|
||||
// Remove a specific key binding
|
||||
app.KeyBinding.Remove("Ctrl+S")
|
||||
|
||||
// Example: Temporary key binding for a modal
|
||||
app.KeyBinding.Add("Escape", func(window *application.WebviewWindow) {
|
||||
// Close modal
|
||||
window.EmitEvent("modal:close", nil)
|
||||
// Remove this temporary binding
|
||||
app.KeyBinding.Remove("Escape")
|
||||
})
|
||||
```
|
||||
|
||||
### Getting All Key Bindings
|
||||
|
||||
Retrieve all registered key bindings:
|
||||
|
||||
```go
|
||||
allBindings := app.KeyBinding.GetAll()
|
||||
for _, binding := range allBindings {
|
||||
app.Logger.Info("Key binding", "accelerator", binding.Accelerator)
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Context-Aware Key Bindings
|
||||
|
||||
Make key bindings context-aware by checking application state:
|
||||
|
||||
```go
|
||||
app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) {
|
||||
// Check current application state
|
||||
if isEditMode() {
|
||||
// Save document
|
||||
saveDocument()
|
||||
} else if isInSettings() {
|
||||
// Save settings
|
||||
saveSettings()
|
||||
} else {
|
||||
app.Logger.Info("Save not available in current context")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Window-Specific Actions
|
||||
|
||||
Key bindings receive the active window, allowing window-specific behavior:
|
||||
|
||||
```go
|
||||
app.KeyBinding.Add("F11", func(window *application.WebviewWindow) {
|
||||
// Toggle fullscreen for the active window
|
||||
if window.Fullscreen() {
|
||||
window.SetFullscreen(false)
|
||||
} else {
|
||||
window.SetFullscreen(true)
|
||||
}
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("Ctrl+W", func(window *application.WebviewWindow) {
|
||||
// Close the active window
|
||||
window.Close()
|
||||
})
|
||||
```
|
||||
|
||||
### Dynamic Key Binding Management
|
||||
|
||||
Dynamically add and remove key bindings based on application state:
|
||||
|
||||
```go
|
||||
func enableEditMode() {
|
||||
// Add edit-specific key bindings
|
||||
app.KeyBinding.Add("Ctrl+B", func(window *application.WebviewWindow) {
|
||||
window.EmitEvent("format:bold", nil)
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("Ctrl+I", func(window *application.WebviewWindow) {
|
||||
window.EmitEvent("format:italic", nil)
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("Ctrl+U", func(window *application.WebviewWindow) {
|
||||
window.EmitEvent("format:underline", nil)
|
||||
})
|
||||
}
|
||||
|
||||
func disableEditMode() {
|
||||
// Remove edit-specific key bindings
|
||||
app.KeyBinding.Remove("Ctrl+B")
|
||||
app.KeyBinding.Remove("Ctrl+I")
|
||||
app.KeyBinding.Remove("Ctrl+U")
|
||||
}
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS:
|
||||
|
||||
- Use `Cmd` instead of `Ctrl` for standard shortcuts
|
||||
- `Cmd+Q` is typically reserved for quitting the application
|
||||
- `Cmd+H` hides the application
|
||||
- `Cmd+M` minimizes windows
|
||||
- Consider standard macOS keyboard shortcuts
|
||||
|
||||
Common macOS patterns:
|
||||
```go
|
||||
app.KeyBinding.Add("Cmd+N", newFileHandler) // New
|
||||
app.KeyBinding.Add("Cmd+O", openFileHandler) // Open
|
||||
app.KeyBinding.Add("Cmd+S", saveFileHandler) // Save
|
||||
app.KeyBinding.Add("Cmd+Z", undoHandler) // Undo
|
||||
app.KeyBinding.Add("Cmd+Shift+Z", redoHandler) // Redo
|
||||
app.KeyBinding.Add("Cmd+C", copyHandler) // Copy
|
||||
app.KeyBinding.Add("Cmd+V", pasteHandler) // Paste
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows:
|
||||
|
||||
- Use `Ctrl` for standard shortcuts
|
||||
- `Alt+F4` closes applications
|
||||
- `F1` typically opens help
|
||||
- Consider Windows keyboard conventions
|
||||
|
||||
Common Windows patterns:
|
||||
```go
|
||||
app.KeyBinding.Add("Ctrl+N", newFileHandler) // New
|
||||
app.KeyBinding.Add("Ctrl+O", openFileHandler) // Open
|
||||
app.KeyBinding.Add("Ctrl+S", saveFileHandler) // Save
|
||||
app.KeyBinding.Add("Ctrl+Z", undoHandler) // Undo
|
||||
app.KeyBinding.Add("Ctrl+Y", redoHandler) // Redo
|
||||
app.KeyBinding.Add("Ctrl+C", copyHandler) // Copy
|
||||
app.KeyBinding.Add("Ctrl+V", pasteHandler) // Paste
|
||||
app.KeyBinding.Add("F1", helpHandler) // Help
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux:
|
||||
|
||||
- Generally follows Windows conventions with `Ctrl`
|
||||
- May vary by desktop environment
|
||||
- Consider GNOME/KDE standard shortcuts
|
||||
- Some desktop environments reserve certain shortcuts
|
||||
|
||||
Common Linux patterns:
|
||||
```go
|
||||
app.KeyBinding.Add("Ctrl+N", newFileHandler) // New
|
||||
app.KeyBinding.Add("Ctrl+O", openFileHandler) // Open
|
||||
app.KeyBinding.Add("Ctrl+S", saveFileHandler) // Save
|
||||
app.KeyBinding.Add("Ctrl+Z", undoHandler) // Undo
|
||||
app.KeyBinding.Add("Ctrl+Shift+Z", redoHandler) // Redo
|
||||
app.KeyBinding.Add("Ctrl+C", copyHandler) // Copy
|
||||
app.KeyBinding.Add("Ctrl+V", pasteHandler) // Paste
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Standard Shortcuts**: Follow platform conventions for common operations:
|
||||
```go
|
||||
// Cross-platform save
|
||||
if runtime.GOOS == "darwin" {
|
||||
app.KeyBinding.Add("Cmd+S", saveHandler)
|
||||
} else {
|
||||
app.KeyBinding.Add("Ctrl+S", saveHandler)
|
||||
}
|
||||
```
|
||||
|
||||
2. **Provide Visual Feedback**: Let users know when shortcuts are triggered:
|
||||
```go
|
||||
app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) {
|
||||
saveDocument()
|
||||
// Show brief notification
|
||||
window.EmitEvent("notification:show", "Document saved")
|
||||
})
|
||||
```
|
||||
|
||||
3. **Handle Conflicts**: Be careful not to override important system shortcuts:
|
||||
```go
|
||||
// Avoid overriding system shortcuts like:
|
||||
// Ctrl+Alt+Del (Windows)
|
||||
// Cmd+Space (macOS Spotlight)
|
||||
// Alt+Tab (Window switching)
|
||||
```
|
||||
|
||||
4. **Document Shortcuts**: Provide help or documentation for available shortcuts:
|
||||
```go
|
||||
app.KeyBinding.Add("F1", func(window *application.WebviewWindow) {
|
||||
// Show help dialog with available shortcuts
|
||||
showKeyboardShortcutsHelp()
|
||||
})
|
||||
```
|
||||
|
||||
5. **Clean Up**: Remove temporary key bindings when they're no longer needed:
|
||||
```go
|
||||
func enterEditMode() {
|
||||
app.KeyBinding.Add("Escape", exitEditModeHandler)
|
||||
}
|
||||
|
||||
func exitEditModeHandler(window *application.WebviewWindow) {
|
||||
exitEditMode()
|
||||
app.KeyBinding.Remove("Escape") // Clean up temporary binding
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a complete example of a text editor with keyboard shortcuts:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Text Editor with Shortcuts",
|
||||
})
|
||||
|
||||
// File operations
|
||||
if runtime.GOOS == "darwin" {
|
||||
app.KeyBinding.Add("Cmd+N", func(window *application.WebviewWindow) {
|
||||
window.EmitEvent("file:new", nil)
|
||||
})
|
||||
app.KeyBinding.Add("Cmd+O", func(window *application.WebviewWindow) {
|
||||
openFile(app, window)
|
||||
})
|
||||
app.KeyBinding.Add("Cmd+S", func(window *application.WebviewWindow) {
|
||||
window.EmitEvent("file:save", nil)
|
||||
})
|
||||
} else {
|
||||
app.KeyBinding.Add("Ctrl+N", func(window *application.WebviewWindow) {
|
||||
window.EmitEvent("file:new", nil)
|
||||
})
|
||||
app.KeyBinding.Add("Ctrl+O", func(window *application.WebviewWindow) {
|
||||
openFile(app, window)
|
||||
})
|
||||
app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) {
|
||||
window.EmitEvent("file:save", nil)
|
||||
})
|
||||
}
|
||||
|
||||
// View operations
|
||||
app.KeyBinding.Add("F11", func(window *application.WebviewWindow) {
|
||||
window.SetFullscreen(!window.Fullscreen())
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("F1", func(window *application.WebviewWindow) {
|
||||
showKeyboardShortcuts(window)
|
||||
})
|
||||
|
||||
// Create main window
|
||||
window := app.Window.New()
|
||||
window.SetTitle("Text Editor")
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func openFile(app *application.App, window *application.WebviewWindow) {
|
||||
dialog := app.Dialog.OpenFile()
|
||||
dialog.AddFilter("Text Files", "*.txt;*.md")
|
||||
|
||||
if file, err := dialog.PromptForSingleSelection(); err == nil {
|
||||
window.EmitEvent("file:open", file)
|
||||
}
|
||||
}
|
||||
|
||||
func showKeyboardShortcuts(window *application.WebviewWindow) {
|
||||
shortcuts := `
|
||||
Keyboard Shortcuts:
|
||||
- Ctrl/Cmd+N: New file
|
||||
- Ctrl/Cmd+O: Open file
|
||||
- Ctrl/Cmd+S: Save file
|
||||
- F11: Toggle fullscreen
|
||||
- F1: Show this help
|
||||
`
|
||||
window.EmitEvent("help:show", shortcuts)
|
||||
}
|
||||
```
|
||||
|
||||
:::tip[Pro Tip]
|
||||
Test your key bindings on all target platforms to ensure they work correctly and don't conflict with system shortcuts.
|
||||
:::
|
||||
|
||||
:::danger[Warning]
|
||||
Be careful not to override critical system shortcuts. Some key combinations are reserved by the operating system and cannot be captured by applications.
|
||||
:::
|
||||
265
docs/src/content/docs/learn/manager-api.mdx
Normal file
265
docs/src/content/docs/learn/manager-api.mdx
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
---
|
||||
title: Manager API
|
||||
sidebar:
|
||||
order: 25
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
The Wails v3 Manager API provides an organized and discoverable way to access application functionality through focused manager structs. This new API structure groups related methods together while maintaining full backward compatibility with the traditional App API.
|
||||
|
||||
## Overview
|
||||
|
||||
The Manager API organizes application functionality into eleven focused areas:
|
||||
|
||||
- **`app.Window`** - Window creation, management, and callbacks
|
||||
- **`app.ContextMenu`** - Context menu registration and management
|
||||
- **`app.KeyBinding`** - Global key binding management
|
||||
- **`app.Browser`** - Browser integration (opening URLs and files)
|
||||
- **`app.Env`** - Environment information and system state
|
||||
- **`app.Dialog`** - File and message dialog operations
|
||||
- **`app.Event`** - Custom event handling and application events
|
||||
- **`app.Menu`** - Application menu management
|
||||
- **`app.Screen`** - Screen management and coordinate transformations
|
||||
- **`app.Clipboard`** - Clipboard text operations
|
||||
- **`app.SystemTray`** - System tray icon creation and management
|
||||
|
||||
## Benefits
|
||||
|
||||
- **Better discoverability** - IDE autocomplete shows organized API surface
|
||||
- **Improved code organization** - Related methods grouped together
|
||||
- **Enhanced maintainability** - Separation of concerns across managers
|
||||
- **Future extensibility** - Easier to add new features to specific areas
|
||||
|
||||
## Usage
|
||||
|
||||
The Manager API provides organized access to all application functionality:
|
||||
|
||||
```go
|
||||
// Events and custom event handling
|
||||
app.Event.Emit("custom", data)
|
||||
app.Event.On("custom", func(e *CustomEvent) { ... })
|
||||
|
||||
// Window management
|
||||
window, _ := app.Window.GetByName("main")
|
||||
app.Window.OnCreate(func(window Window) { ... })
|
||||
|
||||
// Browser integration
|
||||
app.Browser.OpenURL("https://wails.io")
|
||||
|
||||
// Menu management
|
||||
menu := app.Menu.New()
|
||||
app.Menu.Set(menu)
|
||||
|
||||
// System tray
|
||||
systray := app.SystemTray.New()
|
||||
```
|
||||
|
||||
## Manager Reference
|
||||
|
||||
### Window Manager
|
||||
|
||||
Manages window creation, retrieval, and lifecycle callbacks.
|
||||
|
||||
```go
|
||||
// Create windows
|
||||
window := app.Window.New()
|
||||
window := app.Window.NewWithOptions(options)
|
||||
current := app.Window.Current()
|
||||
|
||||
// Find windows
|
||||
window, exists := app.Window.GetByName("main")
|
||||
windows := app.Window.GetAll()
|
||||
|
||||
// Window callbacks
|
||||
app.Window.OnCreate(func(window Window) {
|
||||
// Handle window creation
|
||||
})
|
||||
```
|
||||
|
||||
### Event Manager
|
||||
|
||||
Handles custom events and application event listening.
|
||||
|
||||
```go
|
||||
// Custom events
|
||||
app.Event.Emit("userAction", data)
|
||||
cancelFunc := app.Event.On("userAction", func(e *CustomEvent) {
|
||||
// Handle event
|
||||
})
|
||||
app.Event.Off("userAction")
|
||||
app.Event.Reset() // Remove all listeners
|
||||
|
||||
// Application events
|
||||
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *ApplicationEvent) {
|
||||
// Handle system theme change
|
||||
})
|
||||
```
|
||||
|
||||
### Browser Manager
|
||||
|
||||
Provides browser integration for opening URLs and files.
|
||||
|
||||
```go
|
||||
// Open URLs and files in default browser
|
||||
err := app.Browser.OpenURL("https://wails.io")
|
||||
err := app.Browser.OpenFile("/path/to/document.pdf")
|
||||
```
|
||||
|
||||
### Environment Manager
|
||||
|
||||
Access to system environment information.
|
||||
|
||||
```go
|
||||
// Get environment info
|
||||
env := app.Env.Info()
|
||||
fmt.Printf("OS: %s, Arch: %s\n", env.OS, env.Arch)
|
||||
|
||||
// Check system theme
|
||||
if app.Env.IsDarkMode() {
|
||||
// Dark mode is active
|
||||
}
|
||||
|
||||
// Open file manager
|
||||
err := app.Env.OpenFileManager("/path/to/folder", false)
|
||||
```
|
||||
|
||||
### Dialog Manager
|
||||
|
||||
Organized access to file and message dialogs.
|
||||
|
||||
```go
|
||||
// File dialogs
|
||||
result, err := app.Dialog.OpenFile().
|
||||
AddFilter("Text Files", "*.txt").
|
||||
PromptForSingleSelection()
|
||||
|
||||
result, err = app.Dialog.SaveFile().
|
||||
SetDefaultFilename("document.txt").
|
||||
PromptForSingleSelection()
|
||||
|
||||
// Message dialogs
|
||||
app.Dialog.Info().
|
||||
SetTitle("Information").
|
||||
SetMessage("Operation completed successfully").
|
||||
Show()
|
||||
|
||||
app.Dialog.Error().
|
||||
SetTitle("Error").
|
||||
SetMessage("An error occurred").
|
||||
Show()
|
||||
```
|
||||
|
||||
### Menu Manager
|
||||
|
||||
Application menu creation and management.
|
||||
|
||||
```go
|
||||
// Create and set application menu
|
||||
menu := app.Menu.New()
|
||||
fileMenu := menu.AddSubmenu("File")
|
||||
fileMenu.Add("New").OnClick(func(ctx *Context) {
|
||||
// Handle menu click
|
||||
})
|
||||
|
||||
app.Menu.Set(menu)
|
||||
|
||||
// Show about dialog
|
||||
app.Menu.ShowAbout()
|
||||
```
|
||||
|
||||
### Key Binding Manager
|
||||
|
||||
Dynamic management of global key bindings.
|
||||
|
||||
```go
|
||||
// Add key bindings
|
||||
app.KeyBinding.Add("ctrl+n", func(window *WebviewWindow) {
|
||||
// Handle Ctrl+N
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("ctrl+q", func(window *WebviewWindow) {
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
// Remove key bindings
|
||||
app.KeyBinding.Remove("ctrl+n")
|
||||
|
||||
// Get all bindings
|
||||
bindings := app.KeyBinding.GetAll()
|
||||
```
|
||||
|
||||
### Context Menu Manager
|
||||
|
||||
Advanced context menu management (for library authors).
|
||||
|
||||
```go
|
||||
// Create and register context menu
|
||||
menu := app.ContextMenu.New()
|
||||
app.ContextMenu.Add("myMenu", menu)
|
||||
|
||||
// Retrieve context menu
|
||||
menu, exists := app.ContextMenu.Get("myMenu")
|
||||
|
||||
// Remove context menu
|
||||
app.ContextMenu.Remove("myMenu")
|
||||
```
|
||||
|
||||
### Screen Manager
|
||||
|
||||
Screen management and coordinate transformations for multi-monitor setups.
|
||||
|
||||
```go
|
||||
// Get screen information
|
||||
screens := app.Screen.GetAll()
|
||||
primary := app.Screen.GetPrimary()
|
||||
|
||||
// Coordinate transformations
|
||||
physicalPoint := app.Screen.DipToPhysicalPoint(logicalPoint)
|
||||
logicalPoint := app.Screen.PhysicalToDipPoint(physicalPoint)
|
||||
|
||||
// Screen detection
|
||||
screen := app.Screen.ScreenNearestDipPoint(point)
|
||||
screen = app.Screen.ScreenNearestDipRect(rect)
|
||||
```
|
||||
|
||||
### Clipboard Manager
|
||||
|
||||
Clipboard operations for reading and writing text.
|
||||
|
||||
```go
|
||||
// Set text to clipboard
|
||||
success := app.Clipboard.SetText("Hello World")
|
||||
if !success {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Get text from clipboard
|
||||
text, ok := app.Clipboard.Text()
|
||||
if !ok {
|
||||
// Handle error
|
||||
} else {
|
||||
// Use the text
|
||||
}
|
||||
```
|
||||
|
||||
### SystemTray Manager
|
||||
|
||||
System tray icon creation and management.
|
||||
|
||||
```go
|
||||
// Create system tray
|
||||
systray := app.SystemTray.New()
|
||||
systray.SetLabel("My App")
|
||||
systray.SetIcon(iconBytes)
|
||||
|
||||
// Add menu to system tray
|
||||
menu := app.Menu.New()
|
||||
menu.Add("Open").OnClick(func(ctx *Context) {
|
||||
// Handle click
|
||||
})
|
||||
systray.SetMenu(menu)
|
||||
|
||||
// Destroy system tray when done
|
||||
systray.Destroy()
|
||||
```
|
||||
304
docs/src/content/docs/learn/notifications.mdx
Normal file
304
docs/src/content/docs/learn/notifications.mdx
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
---
|
||||
title: Notifications
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Introduction
|
||||
|
||||
Wails provides a comprehensive cross-platform notification system for desktop applications. This service allows you to display native system notifications, with support for interactive elements like action buttons and text input fields.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Creating the Service
|
||||
|
||||
First, initialize the notifications service:
|
||||
|
||||
```go
|
||||
import "github.com/wailsapp/wails/v3/pkg/application"
|
||||
import "github.com/wailsapp/wails/v3/services/notifications"
|
||||
|
||||
// Create a new notification service
|
||||
notifier := notifications.New()
|
||||
|
||||
//Register the service with the application
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(notifier),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Notification Authorization
|
||||
|
||||
Notifications on macOS require user authorization. Request and check authorization:
|
||||
|
||||
```go
|
||||
authorized, err := notifier.CheckNotificationAuthorization()
|
||||
if err != nil {
|
||||
// Handle authorization error
|
||||
}
|
||||
if authorized {
|
||||
// Send notifications
|
||||
} else {
|
||||
// Request authorization
|
||||
authorized, err = notifier.RequestNotificationAuthorization()
|
||||
}
|
||||
```
|
||||
On Windows and Linux this always returns `true`.
|
||||
|
||||
## Notification Types
|
||||
|
||||
### Basic Notifications
|
||||
|
||||
Send a basic notification with a unique id, title, optional subtitle (macOS and Linux), and body text to users:
|
||||
|
||||
```go
|
||||
notifier.SendNotification(notifications.NotificationOptions{
|
||||
ID: "unique-id",
|
||||
Title: "New Calendar Invite",
|
||||
Subtitle: "From: Jane Doe", // Optional
|
||||
Body: "Tap to view the event",
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
### Interactive Notifications
|
||||
Send a notification with action buttons and text inputs. These notifications require a notification category to be resgistered first:
|
||||
|
||||
```go
|
||||
// Define a unique category id
|
||||
categoryID := "unique-category-id"
|
||||
|
||||
// Define a category with actions
|
||||
category := notifications.NotificationCategory{
|
||||
ID: categoryID,
|
||||
Actions: []notifications.NotificationAction{
|
||||
{
|
||||
ID: "OPEN",
|
||||
Title: "Open",
|
||||
},
|
||||
{
|
||||
ID: "ARCHIVE",
|
||||
Title: "Archive",
|
||||
Destructive: true, /* macOS-specific */
|
||||
},
|
||||
},
|
||||
HasReplyField: true,
|
||||
ReplyPlaceholder: "message...",
|
||||
ReplyButtonTitle: "Reply",
|
||||
}
|
||||
|
||||
// Register the category
|
||||
notifier.RegisterNotificationCategory(category)
|
||||
|
||||
// Send an interactive notification with the actions registered in the provided category
|
||||
notifier.SendNotificationWithActions(notifications.NotificationOptions{
|
||||
ID: "unique-id",
|
||||
Title: "New Message",
|
||||
Subtitle: "From: Jane Doe",
|
||||
Body: "Are you able to make it?",
|
||||
CategoryID: categoryID,
|
||||
})
|
||||
```
|
||||
|
||||
## Notification Responses
|
||||
|
||||
Process user interactions with notifications:
|
||||
|
||||
```go
|
||||
notifier.OnNotificationResponse(func(result notifications.NotificationResult) {
|
||||
response := result.Response
|
||||
fmt.Printf("Notification %s was actioned with: %s\n", response.ID, response.ActionIdentifier)
|
||||
|
||||
if response.ActionIdentifier == "TEXT_REPLY" {
|
||||
fmt.Printf("User replied: %s\n", response.UserText)
|
||||
}
|
||||
|
||||
if data, ok := response.UserInfo["sender"].(string); ok {
|
||||
fmt.Printf("Original sender: %s\n", data)
|
||||
}
|
||||
|
||||
// Emit an event to the frontend
|
||||
app.Event.Emit("notification", result.Response)
|
||||
})
|
||||
```
|
||||
|
||||
## Notification Customisation
|
||||
|
||||
### Custom Metadata
|
||||
|
||||
Basic and interactive notifications can include custom data:
|
||||
|
||||
```go
|
||||
notifier.SendNotification(notifications.NotificationOptions{
|
||||
ID: "unique-id",
|
||||
Title: "New Calendar Invite",
|
||||
Subtitle: "From: Jane Doe", // Optional
|
||||
Body: "Tap to view the event",
|
||||
Data: map[string]interface{}{
|
||||
"sender": "jane.doe@example.com",
|
||||
"timestamp": "2025-03-10T15:30:00Z",
|
||||
}
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS, notifications:
|
||||
|
||||
- Require user authorization
|
||||
- Require the app to be notorized for distribution
|
||||
- Use system-standard notification appearances
|
||||
- Support `subtitle`
|
||||
- Support user text input
|
||||
- Support the `Destructive` action option
|
||||
- Automatically handle dark/light mode
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows, notifications:
|
||||
|
||||
- Use Windows system toast styles
|
||||
- Adapt to Windows theme settings
|
||||
- Support user text input
|
||||
- Support high DPI displays
|
||||
- Do not support `subtitle`
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux, dialog behaviour depends on the desktop environment:
|
||||
|
||||
- Use native notifications when available
|
||||
- Follow desktop environment theme
|
||||
- Position according to desktop environment rules
|
||||
- Support `subtitle`
|
||||
- Do not support user text input
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Check and request for authorization:
|
||||
- macOS requires user authorization
|
||||
|
||||
2. Provide clear and concise notifications:
|
||||
- Use descriptive titles, subtitles, text, and action titles
|
||||
|
||||
3. Handle dialog responses appropriately:
|
||||
- Check for errors in notification responses
|
||||
- Provide feedback for user actions
|
||||
|
||||
4. Consider platform conventions:
|
||||
- Follow platform-specific notification patterns
|
||||
- Respect system settings
|
||||
|
||||
## Examples
|
||||
|
||||
Explore this example:
|
||||
|
||||
- [Notifications](/examples/notifications)
|
||||
|
||||
## API Reference
|
||||
|
||||
### Service Management
|
||||
| Method | Description |
|
||||
|--------------------------------------------|-------------------------------------------------------|
|
||||
| `New()` | Creates a new notifications service |
|
||||
|
||||
### Notification Authorization
|
||||
| Method | Description |
|
||||
|----------------------------------------------|------------------------------------------------------------|
|
||||
| `RequestNotificationAuthorization()` | Requests permission to display notifications (macOS) |
|
||||
| `CheckNotificationAuthorization()` | Checks current notification authorization status (macOS) |
|
||||
|
||||
### Sending Notifications
|
||||
| Method | Description |
|
||||
|------------------------------------------------------------|---------------------------------------------------|
|
||||
| `SendNotification(options NotificationOptions)` | Sends a basic notification |
|
||||
| `SendNotificationWithActions(options NotificationOptions)` | Sends an interactive notification with actions |
|
||||
|
||||
### Notification Categories
|
||||
| Method | Description |
|
||||
|---------------------------------------------------------------|---------------------------------------------------|
|
||||
| `RegisterNotificationCategory(category NotificationCategory)` | Registers a reusable notification category |
|
||||
| `RemoveNotificationCategory(categoryID string)` | Removes a previously registered category |
|
||||
|
||||
### Managing Notifications
|
||||
| Method | Description |
|
||||
|-------------------------------------------------|---------------------------------------------------------------------|
|
||||
| `RemoveAllPendingNotifications()` | Removes all pending notifications (macOS and Linux only) |
|
||||
| `RemovePendingNotification(identifier string)` | Removes a specific pending notification (macOS and Linux only) |
|
||||
| `RemoveAllDeliveredNotifications()` | Removes all delivered notifications (macOS and Linux only) |
|
||||
| `RemoveDeliveredNotification(identifier string)`| Removes a specific delivered notification (macOS and Linux only) |
|
||||
| `RemoveNotification(identifier string)` | Removes a notification (Linux-specific) |
|
||||
|
||||
### Event Handling
|
||||
| Method | Description |
|
||||
|--------------------------------------------------------------------|-------------------------------------------------|
|
||||
| `OnNotificationResponse(callback func(result NotificationResult))` | Registers a callback for notification responses |
|
||||
|
||||
### Structs and Types
|
||||
|
||||
#### NotificationOptions
|
||||
```go
|
||||
type NotificationOptions struct {
|
||||
ID string // Unique identifier for the notification
|
||||
Title string // Main notification title
|
||||
Subtitle string // Subtitle text (macOS and Linux only)
|
||||
Body string // Main notification content
|
||||
CategoryID string // Category identifier for interactive notifications
|
||||
Data map[string]interface{} // Custom data to associate with the notification
|
||||
}
|
||||
```
|
||||
|
||||
#### NotificationCategory
|
||||
```go
|
||||
type NotificationCategory struct {
|
||||
ID string // Unique identifier for the category
|
||||
Actions []NotificationAction // Button actions for the notification
|
||||
HasReplyField bool // Whether to include a text input field
|
||||
ReplyPlaceholder string // Placeholder text for the input field
|
||||
ReplyButtonTitle string // Text for the reply button
|
||||
}
|
||||
```
|
||||
|
||||
#### NotificationAction
|
||||
```go
|
||||
type NotificationAction struct {
|
||||
ID string // Unique identifier for the action
|
||||
Title string // Button text
|
||||
Destructive bool // Whether the action is destructive (macOS-specific)
|
||||
}
|
||||
```
|
||||
|
||||
#### NotificationResponse
|
||||
```go
|
||||
type NotificationResponse struct {
|
||||
ID string // Notification identifier
|
||||
ActionIdentifier string // Action that was triggered
|
||||
CategoryID string // Category of the notification
|
||||
Title string // Title of the notification
|
||||
Subtitle string // Subtitle of the notification
|
||||
Body string // Body text of the notification
|
||||
UserText string // Text entered by the user
|
||||
UserInfo map[string]interface{} // Custom data from the notification
|
||||
}
|
||||
```
|
||||
|
||||
#### NotificationResult
|
||||
```go
|
||||
type NotificationResult struct {
|
||||
Response NotificationResponse // Response data
|
||||
Error error // Any error that occurred
|
||||
}
|
||||
```
|
||||
|
|
@ -22,12 +22,12 @@ The runtime is required for integration between Go and the frontend. There are 2
|
|||
ways to integrate the runtime:
|
||||
|
||||
- Using the `@wailsio/runtime` package
|
||||
- Using a pre-built version of the runtime
|
||||
- Using a pre-built bundle
|
||||
|
||||
## Using the npm package
|
||||
|
||||
The `@wailsio/runtime` package is a JavaScript package that provides access to
|
||||
the Wails runtime from the frontend. It is used in by all the standard templates
|
||||
the Wails runtime from the frontend. It is used by all standard templates
|
||||
and is the recommended way to integrate the runtime into your application.
|
||||
By using the `@wailsio/runtime` package, you will only include the parts of the runtime that you use.
|
||||
|
||||
|
|
@ -37,25 +37,55 @@ The package is available on npm and can be installed using:
|
|||
npm install --save @wailsio/runtime
|
||||
```
|
||||
|
||||
## Using a pre-built local version of the runtime
|
||||
## Using a pre-built bundle
|
||||
|
||||
Some projects will not use a Javascript bundler and may prefer to use a
|
||||
pre-built version of the runtime. This is the default for the examples in
|
||||
`v3/examples`. The pre-built version of the runtime can be generated using the
|
||||
following command:
|
||||
pre-built bundled version of the runtime. This version can be generated locally
|
||||
using the following command:
|
||||
|
||||
```shell
|
||||
wails3 generate runtime
|
||||
```
|
||||
|
||||
This will generate a `runtime.js` (and `runtime.debug.js`) file in the current
|
||||
directory. This file can be used by your application by adding it to your frontend project:
|
||||
The command will output a `runtime.js` (and `runtime.debug.js`) file in the current
|
||||
directory. This file is an ES module that can be imported by your application scripts
|
||||
just like the npm package, but the API is also exported to the global window object,
|
||||
so for simpler applications you can use it as follows:
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<script src="./runtime.js"></script>
|
||||
<script type="module" src="./runtime.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
wails.Window.SetTitle("A new window title");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<!--- ... -->
|
||||
</>
|
||||
</html>
|
||||
```
|
||||
|
||||
:::caution
|
||||
It is important to include the `type="module"` attribute on the `<script>` tag that loads the runtime
|
||||
and to wait for the page to be fully loaded before calling the API,
|
||||
because scripts with the `type="module"` attribute run asynchronously.
|
||||
:::
|
||||
|
||||
## Initialisation
|
||||
|
||||
Apart from the API functions, the runtime provides support for context menus and window dragging.
|
||||
These features will only work as expected after the runtime has been initialised.
|
||||
Even if you don't use the API, make sure to include a side-effect import statement
|
||||
somewhere in your frontend code:
|
||||
|
||||
```javascript
|
||||
import "@wailsio/runtime";
|
||||
```
|
||||
|
||||
Your bundler should detect the presence of side-effects and include
|
||||
all required initialisation code in the build.
|
||||
|
||||
:::note
|
||||
If you prefer the pre-built bundle, adding a script tag as shown above suffices.
|
||||
:::
|
||||
505
docs/src/content/docs/learn/screens.mdx
Normal file
505
docs/src/content/docs/learn/screens.mdx
Normal file
|
|
@ -0,0 +1,505 @@
|
|||
---
|
||||
title: Screen Management
|
||||
sidebar:
|
||||
order: 57
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
Wails provides comprehensive screen management capabilities for multi-monitor setups and high-DPI displays. The ScreenManager API allows you to query screen information, handle coordinate transformations, and manage window placement across multiple displays.
|
||||
|
||||
## Accessing the Screen Manager
|
||||
|
||||
The screen manager is accessed through the `Screens` property on your application instance:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "Multi-Monitor App",
|
||||
})
|
||||
|
||||
// Access the screen manager
|
||||
screens := app.Screen
|
||||
```
|
||||
|
||||
## Getting Screen Information
|
||||
|
||||
### Get All Screens
|
||||
|
||||
Retrieve information about all connected displays:
|
||||
|
||||
```go
|
||||
allScreens := app.Screen.GetAll()
|
||||
for i, screen := range allScreens {
|
||||
app.Logger.Info("Screen info",
|
||||
"index", i,
|
||||
"name", screen.Name,
|
||||
"id", screen.ID,
|
||||
"primary", screen.IsPrimary,
|
||||
"width", screen.Size.Width,
|
||||
"height", screen.Size.Height,
|
||||
"scaleFactor", screen.ScaleFactor,
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Get Primary Screen
|
||||
|
||||
Get the primary (main) display:
|
||||
|
||||
```go
|
||||
primaryScreen := app.Screen.GetPrimary()
|
||||
if primaryScreen != nil {
|
||||
app.Logger.Info("Primary screen",
|
||||
"name", primaryScreen.Name,
|
||||
"resolution", fmt.Sprintf("%dx%d", primaryScreen.Size.Width, primaryScreen.Size.Height),
|
||||
"workArea", primaryScreen.WorkArea,
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Screen Properties
|
||||
|
||||
### Screen Structure
|
||||
|
||||
Each screen provides comprehensive information about the display:
|
||||
|
||||
```go
|
||||
type Screen struct {
|
||||
ID string // Unique identifier for the display
|
||||
Name string // Display name (e.g., "Built-in Retina Display")
|
||||
ScaleFactor float32 // DPI scale factor (1.0 = 96 DPI, 2.0 = 192 DPI)
|
||||
X int // X coordinate of top-left corner
|
||||
Y int // Y coordinate of top-left corner
|
||||
Size Size // Logical size of the display
|
||||
Bounds Rect // Display bounds in logical coordinates
|
||||
PhysicalBounds Rect // Physical bounds before scaling
|
||||
WorkArea Rect // Available area (excluding taskbars/docks)
|
||||
PhysicalWorkArea Rect // Physical work area
|
||||
IsPrimary bool // Whether this is the primary display
|
||||
Rotation float32 // Display rotation in degrees
|
||||
}
|
||||
```
|
||||
|
||||
### Coordinate Systems
|
||||
|
||||
Wails handles two coordinate systems:
|
||||
|
||||
- **Logical (DIP)**: Device-independent pixels, scaled for readability
|
||||
- **Physical**: Actual pixel coordinates on the hardware
|
||||
|
||||
```go
|
||||
screen := app.Screen.GetPrimary()
|
||||
|
||||
// Logical coordinates (what you typically work with)
|
||||
logicalWidth := screen.Size.Width
|
||||
logicalHeight := screen.Size.Height
|
||||
|
||||
// Physical coordinates (actual hardware pixels)
|
||||
physicalWidth := screen.PhysicalBounds.Width
|
||||
physicalHeight := screen.PhysicalBounds.Height
|
||||
|
||||
// Scale factor relationship
|
||||
scaleFactor := screen.ScaleFactor // e.g., 2.0 for Retina displays
|
||||
// physicalWidth = logicalWidth * scaleFactor
|
||||
```
|
||||
|
||||
## Coordinate Transformations
|
||||
|
||||
### Point Transformations
|
||||
|
||||
Convert coordinates between logical and physical systems:
|
||||
|
||||
```go
|
||||
// Convert logical point to physical coordinates
|
||||
logicalPoint := application.Point{X: 100, Y: 50}
|
||||
physicalPoint := app.Screen.DipToPhysicalPoint(logicalPoint)
|
||||
|
||||
// Convert physical point to logical coordinates
|
||||
physicalPoint2 := application.Point{X: 200, Y: 100}
|
||||
logicalPoint2 := app.Screen.PhysicalToDipPoint(physicalPoint2)
|
||||
|
||||
app.Logger.Info("Coordinate conversion",
|
||||
"logical", logicalPoint,
|
||||
"physical", physicalPoint,
|
||||
"backToLogical", logicalPoint2,
|
||||
)
|
||||
```
|
||||
|
||||
### Rectangle Transformations
|
||||
|
||||
Convert rectangular areas between coordinate systems:
|
||||
|
||||
```go
|
||||
// Define a logical rectangle
|
||||
logicalRect := application.Rect{
|
||||
X: 100, Y: 100,
|
||||
Width: 400, Height: 300,
|
||||
}
|
||||
|
||||
// Convert to physical coordinates
|
||||
physicalRect := app.Screen.DipToPhysicalRect(logicalRect)
|
||||
|
||||
// Convert back to logical coordinates
|
||||
logicalRect2 := app.Screen.PhysicalToDipRect(physicalRect)
|
||||
|
||||
app.Logger.Info("Rectangle conversion",
|
||||
"original", logicalRect,
|
||||
"physical", physicalRect,
|
||||
"converted", logicalRect2,
|
||||
)
|
||||
```
|
||||
|
||||
## Screen Detection and Positioning
|
||||
|
||||
### Find Screen by Point
|
||||
|
||||
Determine which screen contains a specific point:
|
||||
|
||||
```go
|
||||
// Find screen containing a logical point
|
||||
point := application.Point{X: 500, Y: 300}
|
||||
screen := app.Screen.ScreenNearestDipPoint(point)
|
||||
|
||||
// Find screen containing a physical point
|
||||
physicalPoint := application.Point{X: 1000, Y: 600}
|
||||
screenPhys := app.Screen.ScreenNearestPhysicalPoint(physicalPoint)
|
||||
|
||||
app.Logger.Info("Screen detection",
|
||||
"point", point,
|
||||
"screen", screen.Name,
|
||||
"physicalPoint", physicalPoint,
|
||||
"physicalScreen", screenPhys.Name,
|
||||
)
|
||||
```
|
||||
|
||||
### Find Screen by Rectangle
|
||||
|
||||
Determine which screen best contains or overlaps with a rectangle:
|
||||
|
||||
```go
|
||||
// Find screen for a logical rectangle
|
||||
rect := application.Rect{X: 200, Y: 150, Width: 800, Height: 600}
|
||||
screen := app.Screen.ScreenNearestDipRect(rect)
|
||||
|
||||
// Find screen for a physical rectangle
|
||||
physicalRect := application.Rect{X: 400, Y: 300, Width: 1600, Height: 1200}
|
||||
screenPhys := app.Screen.ScreenNearestPhysicalRect(physicalRect)
|
||||
|
||||
app.Logger.Info("Screen detection by area",
|
||||
"rect", rect,
|
||||
"screen", screen.Name,
|
||||
"physicalRect", physicalRect,
|
||||
"physicalScreen", screenPhys.Name,
|
||||
)
|
||||
```
|
||||
|
||||
## Window Positioning
|
||||
|
||||
### Position Window on Specific Screen
|
||||
|
||||
Place a window on a particular screen:
|
||||
|
||||
```go
|
||||
func positionWindowOnScreen(window *application.WebviewWindow, targetScreen *application.Screen) {
|
||||
// Calculate center position on target screen
|
||||
centerX := targetScreen.WorkArea.X + (targetScreen.WorkArea.Width-800)/2
|
||||
centerY := targetScreen.WorkArea.Y + (targetScreen.WorkArea.Height-600)/2
|
||||
|
||||
// Position the window
|
||||
window.SetPosition(centerX, centerY)
|
||||
window.SetSize(800, 600)
|
||||
}
|
||||
|
||||
// Usage
|
||||
primaryScreen := app.Screen.GetPrimary()
|
||||
if primaryScreen != nil {
|
||||
window := app.Window.New()
|
||||
positionWindowOnScreen(window, primaryScreen)
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Monitor Window Management
|
||||
|
||||
Manage windows across multiple screens:
|
||||
|
||||
```go
|
||||
func distributeWindowsAcrossScreens(app *application.App) {
|
||||
screens := app.Screen.GetAll()
|
||||
if len(screens) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Create one window per screen
|
||||
for i, screen := range screens {
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: fmt.Sprintf("window-%d", i),
|
||||
Title: fmt.Sprintf("Window on %s", screen.Name),
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
})
|
||||
|
||||
// Position window in center of screen's work area
|
||||
x := screen.WorkArea.X + (screen.WorkArea.Width-800)/2
|
||||
y := screen.WorkArea.Y + (screen.WorkArea.Height-600)/2
|
||||
window.SetPosition(x, y)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## High-DPI Support
|
||||
|
||||
### Handling Different Scale Factors
|
||||
|
||||
Properly handle displays with different DPI settings:
|
||||
|
||||
```go
|
||||
func analyzeDisplayScaling(app *application.App) {
|
||||
screens := app.Screen.GetAll()
|
||||
|
||||
for _, screen := range screens {
|
||||
scaleFactor := screen.ScaleFactor
|
||||
|
||||
switch {
|
||||
case scaleFactor == 1.0:
|
||||
app.Logger.Info("Standard DPI display", "screen", screen.Name)
|
||||
case scaleFactor == 1.25:
|
||||
app.Logger.Info("125% scaled display", "screen", screen.Name)
|
||||
case scaleFactor == 1.5:
|
||||
app.Logger.Info("150% scaled display", "screen", screen.Name)
|
||||
case scaleFactor == 2.0:
|
||||
app.Logger.Info("High DPI display (200%)", "screen", screen.Name)
|
||||
default:
|
||||
app.Logger.Info("Custom scale display",
|
||||
"screen", screen.Name,
|
||||
"scale", scaleFactor,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DPI-Aware Measurements
|
||||
|
||||
Convert measurements appropriately for different displays:
|
||||
|
||||
```go
|
||||
func getDPIAwareSizes(screen *application.Screen, logicalSize int) (int, int) {
|
||||
// Physical size in actual pixels
|
||||
physicalSize := int(float32(logicalSize) * screen.ScaleFactor)
|
||||
|
||||
return logicalSize, physicalSize
|
||||
}
|
||||
|
||||
// Usage
|
||||
screen := app.Screen.GetPrimary()
|
||||
logicalWidth, physicalWidth := getDPIAwareSizes(screen, 400)
|
||||
app.Logger.Info("Size conversion",
|
||||
"logical", logicalWidth,
|
||||
"physical", physicalWidth,
|
||||
"scaleFactor", screen.ScaleFactor,
|
||||
)
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS:
|
||||
|
||||
- Screen coordinates start from bottom-left (different from Windows/Linux)
|
||||
- Built-in Retina displays typically have 2.0 scale factor
|
||||
- External displays may have different scale factors
|
||||
- Mission Control and Spaces affect screen management
|
||||
- Screen arrangement can be configured in System Preferences
|
||||
|
||||
```go
|
||||
// macOS-specific screen handling
|
||||
if runtime.GOOS == "darwin" {
|
||||
// Handle coordinate system differences
|
||||
screen := app.Screen.GetPrimary()
|
||||
// Y coordinates are flipped on macOS
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows:
|
||||
|
||||
- Screen coordinates start from top-left
|
||||
- Scale factors vary: 100%, 125%, 150%, 175%, 200%, etc.
|
||||
- Multiple monitor configurations are common
|
||||
- Taskbar affects work area calculations
|
||||
- Windows display settings control scaling
|
||||
|
||||
```go
|
||||
// Windows-specific screen handling
|
||||
if runtime.GOOS == "windows" {
|
||||
screen := app.Screen.GetPrimary()
|
||||
// Account for taskbar in work area
|
||||
workArea := screen.WorkArea
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux:
|
||||
|
||||
- Screen coordinates start from top-left
|
||||
- Scale factors depend on desktop environment
|
||||
- X11 and Wayland have different behaviors
|
||||
- Panel/dock configurations affect work areas
|
||||
- Multi-monitor support varies by environment
|
||||
|
||||
```go
|
||||
// Linux-specific screen handling
|
||||
if runtime.GOOS == "linux" {
|
||||
screen := app.Screen.GetPrimary()
|
||||
// Handle different desktop environments
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always Check for Screens**: Ensure screens are available before using them:
|
||||
```go
|
||||
screens := app.Screen.GetAll()
|
||||
if len(screens) == 0 {
|
||||
app.Logger.Warn("No screens detected")
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
2. **Use Work Areas**: Position windows within work areas to avoid taskbars/docks:
|
||||
```go
|
||||
screen := app.Screen.GetPrimary()
|
||||
workArea := screen.WorkArea
|
||||
// Position within workArea, not full screen bounds
|
||||
```
|
||||
|
||||
3. **Handle Scale Factors**: Always consider DPI scaling for proper sizing:
|
||||
```go
|
||||
screen := app.Screen.GetPrimary()
|
||||
if screen.ScaleFactor > 1.0 {
|
||||
// Adjust UI elements for high-DPI displays
|
||||
}
|
||||
```
|
||||
|
||||
4. **Monitor Screen Changes**: Listen for screen configuration changes:
|
||||
```go
|
||||
app.OnApplicationEvent(events.Common.SystemDisplayChanged, func(event *application.ApplicationEvent) {
|
||||
// Refresh screen information
|
||||
screens := app.Screen.GetAll()
|
||||
// Reposition windows if needed
|
||||
})
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a complete example demonstrating screen management:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Screen Management Demo",
|
||||
})
|
||||
|
||||
// Analyze connected screens
|
||||
analyzeScreens(app)
|
||||
|
||||
// Create windows strategically positioned
|
||||
createPositionedWindows(app)
|
||||
|
||||
// Setup screen change monitoring
|
||||
monitorScreenChanges(app)
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func analyzeScreens(app *application.App) {
|
||||
screens := app.Screen.GetAll()
|
||||
app.Logger.Info("Screen analysis", "count", len(screens))
|
||||
|
||||
primary := app.Screen.GetPrimary()
|
||||
if primary != nil {
|
||||
app.Logger.Info("Primary screen",
|
||||
"name", primary.Name,
|
||||
"size", fmt.Sprintf("%dx%d", primary.Size.Width, primary.Size.Height),
|
||||
"scaleFactor", primary.ScaleFactor,
|
||||
"workArea", primary.WorkArea,
|
||||
)
|
||||
}
|
||||
|
||||
for i, screen := range screens {
|
||||
app.Logger.Info("Screen details",
|
||||
"index", i,
|
||||
"name", screen.Name,
|
||||
"primary", screen.IsPrimary,
|
||||
"bounds", screen.Bounds,
|
||||
"scaleFactor", screen.ScaleFactor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func createPositionedWindows(app *application.App) {
|
||||
screens := app.Screen.GetAll()
|
||||
|
||||
for i, screen := range screens {
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: fmt.Sprintf("screen-%d", i),
|
||||
Title: fmt.Sprintf("Window on %s", screen.Name),
|
||||
Width: 600,
|
||||
Height: 400,
|
||||
})
|
||||
|
||||
// Center window in screen's work area
|
||||
x := screen.WorkArea.X + (screen.WorkArea.Width-600)/2
|
||||
y := screen.WorkArea.Y + (screen.WorkArea.Height-400)/2
|
||||
window.SetPosition(x, y)
|
||||
|
||||
app.Logger.Info("Created window",
|
||||
"screen", screen.Name,
|
||||
"position", fmt.Sprintf("%d,%d", x, y),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func monitorScreenChanges(app *application.App) {
|
||||
// Monitor for screen configuration changes
|
||||
app.OnApplicationEvent(events.Common.SystemDisplayChanged, func(event *application.ApplicationEvent) {
|
||||
app.Logger.Info("Screen configuration changed")
|
||||
|
||||
// Re-analyze screens
|
||||
screens := app.Screen.GetAll()
|
||||
app.Logger.Info("Updated screen count", "count", len(screens))
|
||||
|
||||
// Could reposition windows here if needed
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
:::tip[Pro Tip]
|
||||
When developing multi-monitor applications, test on various configurations including different DPI settings and monitor arrangements to ensure proper behavior.
|
||||
:::
|
||||
|
||||
:::danger[Warning]
|
||||
Screen configurations can change during application runtime (monitors connected/disconnected, resolution changes). Always handle these changes gracefully and avoid storing screen references long-term.
|
||||
:::
|
||||
|
|
@ -40,9 +40,9 @@ greeting.
|
|||
## Registering a Service
|
||||
|
||||
To register a service with the application, you need to provide an instance of
|
||||
the service to the `Services` field of the `application.Options` struct (All
|
||||
services need to be wrapped by an `application.NewService` call. Here's an
|
||||
example:
|
||||
the service to the `Services` field of the `application.Options` struct.
|
||||
All services need to be wrapped by an `application.NewService` call.
|
||||
Here's an example:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
|
|
@ -70,6 +70,25 @@ ServiceOptions has the following fields:
|
|||
- Name - Specify a custom name for the Service
|
||||
- Route - A route to bind the Service to the frontend (more on this below)
|
||||
|
||||
After the application has been created but not yet started,
|
||||
you can register more services using the `RegisterService` method.
|
||||
This is useful when you need to feed a service some value
|
||||
that is only available after the application has been created.
|
||||
For example, let's wire application's logger into your own service:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{})
|
||||
|
||||
app.RegisterService(application.NewService(NewMyService(app.Logger)))
|
||||
|
||||
// ...
|
||||
|
||||
err := app.Run()
|
||||
```
|
||||
|
||||
Services may only be registered before running the application:
|
||||
`RegisterService` will panic if called after the `Run` method.
|
||||
|
||||
## Optional Methods
|
||||
|
||||
Services can implement optional methods to hook into the application lifecycle.
|
||||
|
|
@ -98,8 +117,12 @@ func (s *Service) ServiceStartup(ctx context.Context, options application.Servic
|
|||
|
||||
This method is called when the application is starting up. You can use it to
|
||||
initialize resources, set up connections, or perform any necessary setup tasks.
|
||||
The context is the application context, and the `options` parameter provides
|
||||
additional information about the service.
|
||||
The context is the application context that will be canceled upon shutdown,
|
||||
and the `options` parameter provides additional information about the service.
|
||||
|
||||
Services are initialised in the exact order of registration:
|
||||
first those listed in the `Services` field of the `application.Options` struct,
|
||||
then those added through the `RegisterService` method.
|
||||
|
||||
### ServiceShutdown
|
||||
|
||||
|
|
@ -110,6 +133,9 @@ func (s *Service) ServiceShutdown() error
|
|||
This method is called when the application is shutting down. Use it to clean up
|
||||
resources, close connections, or perform any necessary cleanup tasks.
|
||||
|
||||
Services are shut down in reverse registration order.
|
||||
The application context will be canceled before `ServiceShutdown` is called.
|
||||
|
||||
### ServeHTTP
|
||||
|
||||
```go
|
||||
|
|
@ -133,14 +159,16 @@ application.NewServiceWithOptions(fileserver.New(&fileserver.Config{
|
|||
Let's look at a simplified version of the `fileserver` service as an example:
|
||||
|
||||
```go
|
||||
type Service struct {
|
||||
config *Config
|
||||
fs http.Handler
|
||||
type Config struct {
|
||||
RootPath string
|
||||
}
|
||||
|
||||
func New(config *Config) *Service {
|
||||
type Service struct {
|
||||
fs http.Handler
|
||||
}
|
||||
|
||||
func NewWithConfig(config *Config) *Service {
|
||||
return &Service{
|
||||
config: config,
|
||||
fs: http.FileServer(http.Dir(config.RootPath)),
|
||||
}
|
||||
}
|
||||
|
|
@ -149,11 +177,6 @@ func (s *Service) ServiceName() string {
|
|||
return "github.com/wailsapp/wails/v3/services/fileserver"
|
||||
}
|
||||
|
||||
func (s *Service) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
// Any initialization code here
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.fs.ServeHTTP(w, r)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ To create a basic system tray icon:
|
|||
|
||||
```go
|
||||
app := application.New(options)
|
||||
systray := app.NewSystemTray()
|
||||
systray := app.SystemTray.New()
|
||||
systray.SetLabel("My App")
|
||||
systray.SetIcon(iconBytes)
|
||||
systray.Run()
|
||||
app.Run()
|
||||
```
|
||||
|
||||
## Setting the Icon
|
||||
|
|
@ -85,10 +85,10 @@ You can add a menu to your system tray icon:
|
|||
|
||||
```go
|
||||
menu := application.NewMenu()
|
||||
menu.Add("Open").OnClick(func() {
|
||||
menu.Add("Open").OnClick(func(ctx *application.Context) {
|
||||
// Handle click
|
||||
})
|
||||
menu.Add("Quit").OnClick(func() {
|
||||
menu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
|
|
@ -108,7 +108,7 @@ Here's a complete example:
|
|||
app := application.New()
|
||||
|
||||
// Create system tray
|
||||
systray := app.NewSystemTray()
|
||||
systray := app.SystemTray.New()
|
||||
systray.SetLabel("My App")
|
||||
|
||||
// Create a window
|
||||
|
|
@ -133,7 +133,7 @@ menu.Add("Quit").OnClick(func() {
|
|||
})
|
||||
systray.SetMenu(menu)
|
||||
|
||||
systray.Run()
|
||||
app.Run()
|
||||
```
|
||||
|
||||
## Icon Position <Badge text="macOS" variant="success" />
|
||||
|
|
@ -182,7 +182,7 @@ Explore these examples for more advanced usage:
|
|||
### Core Methods
|
||||
| Method | Description |
|
||||
|------------------------------------------|--------------------------------------------|
|
||||
| `NewSystemTray()` | Creates a new system tray instance |
|
||||
| `app.SystemTray.New()` | Creates a new system tray instance |
|
||||
| `Run()` | Starts the system tray |
|
||||
| `SetLabel(label string)` | Sets the text label |
|
||||
| `SetTooltip(tooltip string)` | Sets the tooltip text (Windows) |
|
||||
|
|
|
|||
352
docs/src/content/docs/learn/windows.mdx
Normal file
352
docs/src/content/docs/learn/windows.mdx
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
---
|
||||
title: Windows
|
||||
sidebar:
|
||||
order: 52
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
Wails provides a comprehensive window management system through the WindowManager API. This allows you to create, manage, and control multiple windows in your application.
|
||||
|
||||
## Accessing the Window Manager
|
||||
|
||||
The window manager is accessed through the `Windows` property on your application instance:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "Multi-Window App",
|
||||
})
|
||||
|
||||
// Access the window manager
|
||||
windowManager := app.Window
|
||||
```
|
||||
|
||||
## Creating Windows
|
||||
|
||||
### Basic Window Creation
|
||||
|
||||
Create a new window with default settings:
|
||||
|
||||
```go
|
||||
window := app.Window.New()
|
||||
```
|
||||
|
||||
### Window Creation with Options
|
||||
|
||||
Create a window with custom configuration:
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Custom Window",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
Mac: application.MacOptions{
|
||||
TitleBarAppearsTransparent: true,
|
||||
},
|
||||
Windows: application.WindowsOptions{
|
||||
DisableWindowIcon: true,
|
||||
},
|
||||
Linux: application.LinuxOptions{
|
||||
Icon: []byte{/* icon data */},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Finding Windows
|
||||
|
||||
### Get Window by Name
|
||||
|
||||
Retrieve a window using its name:
|
||||
|
||||
```go
|
||||
window, exists := app.Window.GetByName("main-window")
|
||||
if exists {
|
||||
// Work with the window
|
||||
window.SetTitle("Found Window")
|
||||
}
|
||||
```
|
||||
|
||||
### Get Window by ID
|
||||
|
||||
Retrieve a window using its unique ID:
|
||||
|
||||
```go
|
||||
window, exists := app.Window.GetByID(123)
|
||||
if exists {
|
||||
// Work with the window
|
||||
}
|
||||
```
|
||||
|
||||
### Get Current Window
|
||||
|
||||
Get the currently active/focused window:
|
||||
|
||||
```go
|
||||
// Get current window (may be nil)
|
||||
currentWindow := app.Window.Current()
|
||||
if currentWindow != nil {
|
||||
currentWindow.SetTitle("Active Window")
|
||||
}
|
||||
|
||||
// Get current window with existence check
|
||||
currentWindow, exists := app.Window.GetCurrent()
|
||||
if exists {
|
||||
currentWindow.SetTitle("Active Window")
|
||||
}
|
||||
```
|
||||
|
||||
### Get All Windows
|
||||
|
||||
Retrieve all windows managed by the application:
|
||||
|
||||
```go
|
||||
allWindows := app.Window.GetAll()
|
||||
for i, window := range allWindows {
|
||||
window.SetTitle(fmt.Sprintf("Window %d", i+1))
|
||||
}
|
||||
```
|
||||
|
||||
## Window Lifecycle Management
|
||||
|
||||
### Window Creation Callbacks
|
||||
|
||||
Register callbacks to be notified when new windows are created:
|
||||
|
||||
```go
|
||||
app.Window.OnCreate(func(window application.Window) {
|
||||
app.Logger.Info("New window created", "name", window.Name())
|
||||
|
||||
// Configure the window
|
||||
if webviewWindow, ok := window.(*application.WebviewWindow); ok {
|
||||
webviewWindow.SetMinSize(400, 300)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Removing Windows
|
||||
|
||||
Remove windows from the manager:
|
||||
|
||||
```go
|
||||
// Remove by ID
|
||||
app.Window.Remove(windowID)
|
||||
|
||||
// Remove by name
|
||||
removed := app.Window.RemoveByName("window-name")
|
||||
if removed {
|
||||
app.Logger.Info("Window removed successfully")
|
||||
}
|
||||
```
|
||||
|
||||
## Window Types
|
||||
|
||||
### WebView Windows
|
||||
|
||||
The primary window type in Wails v3 is the WebView window, which embeds a web browser:
|
||||
|
||||
```go
|
||||
window := app.Window.New()
|
||||
|
||||
// WebView-specific operations
|
||||
window.SetURL("https://example.com")
|
||||
window.SetHTML("<h1>Hello World</h1>")
|
||||
window.Reload()
|
||||
```
|
||||
|
||||
### Window Interface
|
||||
|
||||
All windows implement the `Window` interface, providing common functionality:
|
||||
|
||||
```go
|
||||
type Window interface {
|
||||
ID() uint
|
||||
Name() string
|
||||
SetTitle(title string)
|
||||
SetSize(width, height int)
|
||||
SetPosition(x, y int)
|
||||
Show()
|
||||
Hide()
|
||||
Close()
|
||||
// ... other methods
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Window Management
|
||||
|
||||
### Multi-Window Applications
|
||||
|
||||
Create and manage multiple windows for complex applications:
|
||||
|
||||
```go
|
||||
// Create main window
|
||||
mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Main Application",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
})
|
||||
|
||||
// Create settings window
|
||||
settingsWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Settings",
|
||||
Width: 600,
|
||||
Height: 400,
|
||||
WindowState: application.WindowStateHidden, // Start hidden
|
||||
})
|
||||
|
||||
// Show settings when needed
|
||||
settingsWindow.Show()
|
||||
```
|
||||
|
||||
### Window Communication
|
||||
|
||||
Windows can communicate through events:
|
||||
|
||||
```go
|
||||
// In window A
|
||||
app.Event.Emit("data-updated", newData)
|
||||
|
||||
// In window B
|
||||
app.Event.On("data-updated", func(event *application.CustomEvent) {
|
||||
// Update UI with new data
|
||||
data := event.Data
|
||||
// Process data...
|
||||
})
|
||||
```
|
||||
|
||||
## Platform-Specific Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS, windows:
|
||||
|
||||
- Support native title bar customization
|
||||
- Can be configured with transparent title bars
|
||||
- Follow macOS window management conventions
|
||||
- Support native fullscreen mode
|
||||
- Integrate with Mission Control and Spaces
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows, windows:
|
||||
|
||||
- Support custom window icons
|
||||
- Can be configured without system title bars
|
||||
- Follow Windows window management conventions
|
||||
- Support Windows 11 snap layouts
|
||||
- Integrate with Windows taskbar features
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux, windows:
|
||||
|
||||
- Support custom icons
|
||||
- Follow desktop environment conventions
|
||||
- Work with various window managers
|
||||
- Support X11 and Wayland protocols
|
||||
- Integrate with desktop environment features
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Window Naming**: Always provide meaningful names for windows to make them easy to find:
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "settings-window",
|
||||
Title: "Application Settings",
|
||||
})
|
||||
```
|
||||
|
||||
2. **Resource Management**: Properly clean up windows when they're no longer needed:
|
||||
```go
|
||||
window.OnEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
app.Window.Remove(window.ID())
|
||||
})
|
||||
```
|
||||
|
||||
3. **Window State**: Consider starting secondary windows as hidden and showing them when needed:
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
WindowState: application.WindowStateHidden,
|
||||
})
|
||||
```
|
||||
|
||||
4. **Error Handling**: Always check if windows exist before using them:
|
||||
```go
|
||||
if window, exists := app.Window.GetByName("main"); exists {
|
||||
window.SetTitle("New Title")
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a complete example of a multi-window application:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Multi-Window Demo",
|
||||
})
|
||||
|
||||
// Create main window
|
||||
mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "main",
|
||||
Title: "Main Window",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
})
|
||||
|
||||
// Create hidden settings window
|
||||
settingsWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "settings",
|
||||
Title: "Settings",
|
||||
Width: 400,
|
||||
Height: 300,
|
||||
WindowState: application.WindowStateHidden,
|
||||
})
|
||||
|
||||
// Setup window creation callback
|
||||
app.Window.OnCreate(func(window application.Window) {
|
||||
app.Logger.Info("Window created", "name", window.Name())
|
||||
})
|
||||
|
||||
// Setup menu to show settings
|
||||
menu := app.Menu.New()
|
||||
menu.AddRole(application.AppMenu)
|
||||
|
||||
settingsMenu := menu.AddSubmenu("Settings")
|
||||
settingsMenu.Add("Open Settings").OnClick(func(ctx *application.Context) {
|
||||
if window, exists := app.Window.GetByName("settings"); exists {
|
||||
window.Show()
|
||||
window.Focus()
|
||||
}
|
||||
})
|
||||
|
||||
app.Menu.Set(menu)
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::tip[Pro Tip]
|
||||
Use the window manager's callback system to implement application-wide window policies, such as automatically setting minimum sizes or configuring window behaviors.
|
||||
:::
|
||||
|
||||
:::danger[Warning]
|
||||
Always check if windows exist before performing operations on them, as they may have been closed or removed by the user or system.
|
||||
:::
|
||||
|
|
@ -2,7 +2,9 @@
|
|||
title: Roadmap
|
||||
---
|
||||
|
||||
## Current Status: Alpha 9
|
||||
## Current Status: Alpha
|
||||
|
||||
Check out the [Changelog](./changelog) for the latest status.
|
||||
|
||||
Our goal is to reach Beta status. This roadmap outlines the key features and
|
||||
improvements we need to implement before transitioning to Beta. Please note that
|
||||
|
|
@ -28,7 +30,3 @@ direction. Your input is valuable in shaping the future of Wails! The roadmap is
|
|||
a living document and is subject to change. If you have any suggestions, please
|
||||
open an issue. Each milestone will have a set of goals that we are aiming to
|
||||
achieve. These are subject to change.
|
||||
|
||||
## Alpha 10 Status
|
||||
|
||||
Check out the [Changelog](./changelog) for the latest status
|
||||
|
|
@ -40,8 +40,8 @@ This will show you how to organize your code into reusable services and handle e
|
|||
return &QRService{}
|
||||
}
|
||||
|
||||
// GenerateQRCode creates a QR code from the given text
|
||||
func (s *QRService) GenerateQRCode(text string, size int) ([]byte, error) {
|
||||
// Generate creates a QR code from the given text
|
||||
func (s *QRService) Generate(text string, size int) ([]byte, error) {
|
||||
// Generate the QR code
|
||||
qr, err := qrcode.New(text, qrcode.Medium)
|
||||
if err != nil {
|
||||
|
|
@ -149,21 +149,21 @@ This will show you how to organize your code into reusable services and handle e
|
|||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* QRService handles QR code generation
|
||||
* @module
|
||||
*/
|
||||
* QRService handles QR code generation
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
/**
|
||||
* GenerateQRCode creates a QR code from the given text
|
||||
* @param {string} text
|
||||
* @param {number} size
|
||||
* @returns {Promise<string> & { cancel(): void }}
|
||||
*/
|
||||
export function GenerateQRCode(text, size) {
|
||||
* Generate creates a QR code from the given text
|
||||
* @param {string} text
|
||||
* @param {number} size
|
||||
* @returns {Promise<string> & { cancel(): void }}
|
||||
*/
|
||||
export function Generate(text, size) {
|
||||
let $resultPromise = /** @type {any} */($Call.ByID(3576998831, text, size));
|
||||
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
|
||||
return $Create.ByteSlice($result);
|
||||
|
|
@ -173,7 +173,7 @@ This will show you how to organize your code into reusable services and handle e
|
|||
}
|
||||
```
|
||||
|
||||
We can see that the bindings are generated for the `GenerateQRCode` method. The parameter names have been preserved,
|
||||
We can see that the bindings are generated for the `Generate` method. The parameter names have been preserved,
|
||||
as well as the comments. JSDoc has also been generated for the method to provide type information to your IDE.
|
||||
|
||||
:::note
|
||||
|
|
@ -190,14 +190,36 @@ This will show you how to organize your code into reusable services and handle e
|
|||
The bindings generator also supports generating Typescript bindings. You can do this by running `wails3 generate bindings -ts`.
|
||||
:::
|
||||
|
||||
<br/>
|
||||
The generated service is re-exported by an `index.js` file:
|
||||
|
||||
```js title="bindings/changeme/index.js"
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as QRService from "./qrservice.js";
|
||||
export {
|
||||
QRService
|
||||
};
|
||||
```
|
||||
|
||||
You may then access it through the simplified import path
|
||||
`./bindings/changeme` consisting just of your Go package path,
|
||||
without specifying any file name.
|
||||
|
||||
:::note
|
||||
Simplified import paths are only available when using frontend bundlers.
|
||||
If you prefer a vanilla frontend that does not employ a bundler,
|
||||
you will have to import either `index.js` or `qrservice.js` manually.
|
||||
:::
|
||||
<br/>
|
||||
|
||||
6. ## Use Bindings in Frontend
|
||||
|
||||
Firstly, update `frontend/src/main.js` to use the new bindings:
|
||||
|
||||
```js title="frontend/src/main.js"
|
||||
import { GenerateQRCode } from './bindings/changeme/qrservice.js';
|
||||
import { QRService } from './bindings/changeme';
|
||||
|
||||
async function generateQR() {
|
||||
const text = document.getElementById('text').value;
|
||||
|
|
@ -208,7 +230,7 @@ This will show you how to organize your code into reusable services and handle e
|
|||
|
||||
try {
|
||||
// Generate QR code as base64
|
||||
const qrCodeBase64 = await GenerateQRCode(text, 256);
|
||||
const qrCodeBase64 = await QRService.Generate(text, 256);
|
||||
|
||||
// Display the QR code
|
||||
const qrDiv = document.getElementById('qrcode');
|
||||
|
|
@ -285,7 +307,7 @@ This will show you how to organize your code into reusable services and handle e
|
|||
|
||||
Type in some text and click the "Generate QR Code" button. You should see a QR code in the center of the page:
|
||||
|
||||
<Image src={qr1} alt="QR Code"/>
|
||||
<Image src={qr1} alt="QR Code"/>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
|
@ -303,13 +325,14 @@ This will show you how to organize your code into reusable services and handle e
|
|||
If your service defines Go's standard http handler function `ServeHTTP(w http.ResponseWriter, r *http.Request)`,
|
||||
then it can be made accessible on the frontend. Let's extend our QR code service to do this:
|
||||
|
||||
```go title="qrservice.go" ins={5-6,36-63}
|
||||
```go title="qrservice.go" ins={4-5,37-65}
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/skip2/go-qrcode"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/skip2/go-qrcode"
|
||||
)
|
||||
|
||||
// QRService handles QR code generation
|
||||
|
|
@ -322,8 +345,8 @@ This will show you how to organize your code into reusable services and handle e
|
|||
return &QRService{}
|
||||
}
|
||||
|
||||
// GenerateQRCode creates a QR code from the given text
|
||||
func (s *QRService) GenerateQRCode(text string, size int) ([]byte, error) {
|
||||
// Generate creates a QR code from the given text
|
||||
func (s *QRService) Generate(text string, size int) ([]byte, error) {
|
||||
// Generate the QR code
|
||||
qr, err := qrcode.New(text, qrcode.Medium)
|
||||
if err != nil {
|
||||
|
|
@ -358,7 +381,7 @@ This will show you how to organize your code into reusable services and handle e
|
|||
}
|
||||
|
||||
// Generate the QR code
|
||||
qrCodeData, err := s.GenerateQRCode(text, size)
|
||||
qrCodeData, err := s.Generate(text, size)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
|
@ -408,11 +431,14 @@ This will show you how to organize your code into reusable services and handle e
|
|||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
If you do not set the `Route` option explicitly,
|
||||
the HTTP handler won't be accessible from the frontend.
|
||||
:::
|
||||
|
||||
Finally, update `main.js` to make the image source the path to the QR code service, passing the text as a query parameter:
|
||||
|
||||
```js title="frontend/src/main.js"
|
||||
import { GenerateQRCode } from './bindings/changeme/qrservice.js';
|
||||
|
||||
async function generateQR() {
|
||||
const text = document.getElementById('text').value;
|
||||
if (!text) {
|
||||
|
|
@ -422,7 +448,7 @@ This will show you how to organize your code into reusable services and handle e
|
|||
|
||||
const img = document.getElementById('qrcode');
|
||||
// Make the image source the path to the QR code service, passing the text
|
||||
img.src = `/qrservice?text=${text}`
|
||||
img.src = `/qrservice?text=${encodeURIComponent(text)}`
|
||||
}
|
||||
|
||||
export function initializeQRGenerator() {
|
||||
|
|
@ -440,4 +466,153 @@ This will show you how to organize your code into reusable services and handle e
|
|||
<Image src={qr1} alt="QR Code"/>
|
||||
<br/>
|
||||
|
||||
8. ## Supporting dynamic configurations
|
||||
|
||||
In the example above we used a hardcoded route `/qrservice`.
|
||||
If you edit `main.go` and change the `Route` option without updating `main.js`,
|
||||
the application will break:
|
||||
|
||||
```go title="main.go" ins={3}
|
||||
// ...
|
||||
application.NewService(NewQRService(), application.ServiceOptions{
|
||||
Route: "/services/qr",
|
||||
}),
|
||||
// ...
|
||||
```
|
||||
|
||||
Hardcoded routes can be good for many applications,
|
||||
but if you need more flexibility, method bindings and HTTP handlers
|
||||
can work together to improve the development experience.
|
||||
|
||||
The `ServiceStartup` Lifecycle method provides access to service options at startup,
|
||||
and a custom method can be used to announce the configured route to the frontend.
|
||||
|
||||
First, implement the `ServiceStartup` interface and add a new `URL` method:
|
||||
|
||||
```go title="qrservice.go" ins={4,6,10,15,23-27,46-55}
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/skip2/go-qrcode"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
// QRService handles QR code generation
|
||||
type QRService struct {
|
||||
route string
|
||||
}
|
||||
|
||||
// NewQRService creates a new QR service
|
||||
func NewQRService() *QRService {
|
||||
return &QRService{}
|
||||
}
|
||||
|
||||
// ServiceStartup runs at application startup.
|
||||
func (s *QRService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
s.route = options.Route
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate creates a QR code from the given text
|
||||
func (s *QRService) Generate(text string, size int) ([]byte, error) {
|
||||
// Generate the QR code
|
||||
qr, err := qrcode.New(text, qrcode.Medium)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert to PNG
|
||||
png, err := qr.PNG(size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return png, nil
|
||||
}
|
||||
|
||||
// URL returns an URL that may be used to fetch
|
||||
// a QR code with the given text and size.
|
||||
// It returns an error if the HTTP handler is not available.
|
||||
func (s *QRService) URL(text string, size int) (string, error) {
|
||||
if s.route == "" {
|
||||
return "", errors.New("http handler unavailable")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s?text=%s&size=%d", s.route, url.QueryEscape(text), size), nil
|
||||
}
|
||||
|
||||
func (s *QRService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Extract the text parameter from the request
|
||||
text := r.URL.Query().Get("text")
|
||||
if text == "" {
|
||||
http.Error(w, "Missing 'text' parameter", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// Extract Size parameter from the request
|
||||
sizeText := r.URL.Query().Get("size")
|
||||
if sizeText == "" {
|
||||
sizeText = "256"
|
||||
}
|
||||
size, err := strconv.Atoi(sizeText)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid 'size' parameter", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate the QR code
|
||||
qrCodeData, err := s.Generate(text, size)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Write the QR code data to the response
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
w.Write(qrCodeData)
|
||||
}
|
||||
```
|
||||
|
||||
Now update `main.js` to use the `URL` method in place of a hardcoded path:
|
||||
|
||||
```js title="frontend/src/main.js" ins={1,11-12}
|
||||
import { QRService } from "./bindings/changeme";
|
||||
|
||||
async function generateQR() {
|
||||
const text = document.getElementById('text').value;
|
||||
if (!text) {
|
||||
alert('Please enter some text');
|
||||
return;
|
||||
}
|
||||
|
||||
const img = document.getElementById('qrcode');
|
||||
// Invoke the URL method to obtain an URL for the given text.
|
||||
img.src = await QRService.URL(text, 256);
|
||||
}
|
||||
|
||||
export function initializeQRGenerator() {
|
||||
const button = document.getElementById('generateButton');
|
||||
if (button) {
|
||||
button.addEventListener('click', generateQR);
|
||||
} else {
|
||||
console.error('Generate button not found');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It should work just like the previous example,
|
||||
but changing the service route in `main.go`
|
||||
will not break the frontend anymore.
|
||||
|
||||
:::note
|
||||
If a Go method returns a non-nil error,
|
||||
the promise on the JS side will reject
|
||||
and await statements will throw an exception.
|
||||
:::
|
||||
<br/>
|
||||
|
||||
</Steps>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
@import './mermaid.css';
|
||||
|
||||
html {
|
||||
scrollbar-gutter: stable;
|
||||
overflow-y: scroll; /* Show vertical scrollbar */
|
||||
}
|
||||
}
|
||||
|
|
|
|||
108
docs/src/stylesheets/mermaid.css
Normal file
108
docs/src/stylesheets/mermaid.css
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/* Mermaid diagram styling for Starlight */
|
||||
|
||||
/* Container for the whole diagram component */
|
||||
figure.expandable-diagram {
|
||||
margin: 2rem 0;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
background-color: var(--sl-color-gray-6);
|
||||
box-shadow: var(--sl-shadow-sm);
|
||||
}
|
||||
|
||||
/* Dark mode adjustments */
|
||||
:root[data-theme="dark"] figure.expandable-diagram {
|
||||
background-color: var(--sl-color-gray-1);
|
||||
}
|
||||
|
||||
/* Title for the diagram */
|
||||
figure.expandable-diagram figcaption {
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--sl-color-text);
|
||||
}
|
||||
|
||||
/* Container for the actual diagram */
|
||||
.diagram-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 1rem 0;
|
||||
min-height: 100px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* The diagram itself */
|
||||
.mermaid {
|
||||
background-color: var(--sl-color-white);
|
||||
padding: 1rem;
|
||||
border-radius: 0.375rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .mermaid {
|
||||
background-color: var(--sl-color-black);
|
||||
}
|
||||
|
||||
/* Source code details element */
|
||||
figure.expandable-diagram details {
|
||||
margin-top: 1rem;
|
||||
border-top: 1px solid var(--sl-color-gray-5);
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] figure.expandable-diagram details {
|
||||
border-top-color: var(--sl-color-gray-3);
|
||||
}
|
||||
|
||||
/* Source button */
|
||||
figure.expandable-diagram summary {
|
||||
cursor: pointer;
|
||||
color: var(--sl-color-text-accent);
|
||||
font-weight: 500;
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
figure.expandable-diagram summary:hover {
|
||||
background-color: var(--sl-color-gray-5);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] figure.expandable-diagram summary:hover {
|
||||
background-color: var(--sl-color-gray-2);
|
||||
}
|
||||
|
||||
/* Source code */
|
||||
figure.expandable-diagram details pre {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.375rem;
|
||||
background-color: var(--sl-color-gray-7);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] figure.expandable-diagram details pre {
|
||||
background-color: var(--sl-color-gray-0);
|
||||
}
|
||||
|
||||
/* Mermaid diagram specific adjustments */
|
||||
.mermaid .label {
|
||||
font-family: var(--sl-font);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Fix for diagram text in dark mode */
|
||||
:root[data-theme="dark"] .mermaid text {
|
||||
fill: var(--sl-color-white);
|
||||
}
|
||||
|
||||
/* Ensure diagrams are responsive */
|
||||
@media (max-width: 768px) {
|
||||
.diagram-content {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.mermaid {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
# Changelog
|
||||
|
||||
<!--
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
- `Added` for new features.
|
||||
- `Changed` for changes in existing functionality.
|
||||
- `Deprecated` for soon-to-be removed features.
|
||||
- `Removed` for now removed features.
|
||||
- `Fixed` for any bug fixes.
|
||||
- `Security` in case of vulnerabilities.
|
||||
|
||||
-->
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- [darwin] add Event ApplicationShouldHandleReopen to be able to handle dock icon click by @5aaee9 in [#2991](https://github.com/wailsapp/wails/pull/2991)
|
||||
- [darwin] add getPrimaryScreen/getScreens to impl by @tmclane in [#2618](https://github.com/wailsapp/wails/pull/2618)
|
||||
- [darwin] add option for showing the toolbar in fullscreen mode on macOS by [@fbbdev](https://github.com/fbbdev) in [#3282](https://github.com/wailsapp/wails/pull/3282)
|
||||
- [linux] add onKeyPress logic to convert linux keypress into an accelerator @[Atterpac](https://github.com/Atterpac) in[#3022](https://github.com/wailsapp/wails/pull/3022])
|
||||
- [linux] add task `run:linux` by [@marcus-crane](https://github.com/marcus-crane) in [#3146](https://github.com/wailsapp/wails/pull/3146)
|
||||
- Export `SetIcon` method by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/3147)
|
||||
- Improve `OnShutdown` by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/3189)
|
||||
- Restore `ToggleMaximise` method in `Window` interface by [@fbbdev](https://github.com/fbbdev) in [#3281](https://github.com/wailsapp/wails/pull/3281)
|
||||
- Added more information to `Environment()`. By @leaanthony in [aba82cc](https://github.com/wailsapp/wails/commit/aba82cc52787c97fb99afa58b8b63a0004b7ff6c) based on [PR](https://github.com/wailsapp/wails/pull/2044) by @Mai-Lapyst
|
||||
- Expose the `WebviewWindow.IsFocused` method on the `Window` interface by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Support multiple space-separated trigger events in the WML system by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Add ESM exports from the bundled JS runtime script by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Add binding generator flag for using the bundled JS runtime script instead of the npm package by [@fbbdev](https://github.com/fbbdev) in [#3334](https://github.com/wailsapp/wails/pull/3334)
|
||||
- Implement `setIcon` on linux by [@abichinger](https://github.com/abichinger) in [#3354](https://github.com/wailsapp/wails/pull/3354)
|
||||
- Add flag `-port` to dev command and support environment variable `WAILS_VITE_PORT` by [@abichinger](https://github.com/abichinger) in [#3429](https://github.com/wailsapp/wails/pull/3429)
|
||||
- Add tests for bound method calls by [@abichinger](https://github.com/abichinger) in [#3431](https://github.com/wailsapp/wails/pull/3431)
|
||||
|
||||
### Fixed
|
||||
|
||||
- [linux] Fixed theme handling error on NixOS by [tmclane](https://github.com/tmclane) in [#3515)(https://github.com/wailsapp/wails/pull/3515)
|
||||
- Fixed cross volume project install for windows by [atterpac](https://github.com/atterac) in [#3512](https://github.com/wailsapp/wails/pull/3512)
|
||||
- Fixed react template css to show footer by [atterpac](https://github.com/atterpac) in [#3477](https://github.com/wailsapp/wails/pull/3477)
|
||||
- Fixed zombie processes when working in devmode by updating to latest refresh by [Atterpac](https://github.com/atterpac) in [#3320](https://github.com/wailsapp/wails/pull/3320).
|
||||
- Fixed appimage webkit file sourcing by [Atterpac](https://github.com/atterpac) in [#3306](https://github.com/wailsapp/wails/pull/3306).
|
||||
- Fixed Doctor apt package verify by [Atterpac](https://github.com/Atterpac) in [#2972](https://github.com/wailsapp/wails/pull/2972).
|
||||
- Fixed application frozen when quit (Darwin) by @5aaee9 in [#2982](https://github.com/wailsapp/wails/pull/2982)
|
||||
- Fixed background colours of examples on Windows by [mmgvh](https://github.com/mmghv) in [#2750](https://github.com/wailsapp/wails/pull/2750).
|
||||
- Fixed default context menus by [mmgvh](https://github.com/mmghv) in [#2753](https://github.com/wailsapp/wails/pull/2753).
|
||||
- Fixed hex values for arrow keys on Darwin by [jaybeecave](https://github.com/jaybeecave) in [#3052](https://github.com/wailsapp/wails/pull/3052).
|
||||
- Set drag-n-drop for windows to working. Added by [@pylotlight](https://github.com/pylotlight) in [PR](https://github.com/wailsapp/wails/pull/3039)
|
||||
- Fixed bug for linux in doctor in the event user doesn't have proper drivers installed. Added by [@pylotlight](https://github.com/pylotlight) in [PR](https://github.com/wailsapp/wails/pull/3032)
|
||||
- Fix dpi scaling on start up (windows). Changed by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/3145)
|
||||
- Fix replace line in `go.mod` to use relative paths. Fixes Windows paths with spaces - @leaanthony.
|
||||
- Fix MacOS systray click handling when no attached window by [thomas-senechal](https://github.com/thomas-senechal) in PR [#3207](https://github.com/wailsapp/wails/pull/3207)
|
||||
- Fix failing Windows build due to unknown option by [thomas-senechal](https://github.com/thomas-senechal) in PR [#3208](https://github.com/wailsapp/wails/pull/3208)
|
||||
- Fix crash on windows left clicking the systray icon when not having an attached window [tw1nk](https://github.com/tw1nk) in PR [#3271](https://github.com/wailsapp/wails/pull/3271)
|
||||
- Fix wrong baseURL when open window twice by @5aaee9 in PR [#3273](https://github.com/wailsapp/wails/pull/3273)
|
||||
- Fix ordering of if branches in `WebviewWindow.Restore` method by [@fbbdev](https://github.com/fbbdev) in [#3279](https://github.com/wailsapp/wails/pull/3279)
|
||||
- Correctly compute `startURL` across multiple `GetStartURL` invocations when `FRONTEND_DEVSERVER_URL` is present. [#3299](https://github.com/wailsapp/wails/pull/3299)
|
||||
- Fix the JS type of the `Screen` struct to match its Go counterpart by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Fix the `WML.Reload` method to ensure proper cleanup of registered event listeners by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Fix custom context menu closing immediately on linux by [@abichinger](https://github.com/abichinger) in [#3330](https://github.com/wailsapp/wails/pull/3330)
|
||||
- Fix the output path and extension of model files produced by the binding generator by [@fbbdev](https://github.com/fbbdev) in [#3334](https://github.com/wailsapp/wails/pull/3334)
|
||||
- Fix the import paths of model files in JS code produced by the binding generator by [@fbbdev](https://github.com/fbbdev) in [#3334](https://github.com/wailsapp/wails/pull/3334)
|
||||
- Fix drag-n-drop on some linux distros by [@abichinger](https://github.com/abichinger) in [#3346](https://github.com/wailsapp/wails/pull/3346)
|
||||
- Fix missing task for macOS when using `wails3 task dev` by [@hfoxy](https://github.com/hfoxy) in [#3417](https://github.com/wailsapp/wails/pull/3417)
|
||||
- Fix registering events causing a nil map assignment by [@hfoxy](https://github.com/hfoxy) in [#3426](https://github.com/wailsapp/wails/pull/3426)
|
||||
- Fix unmarshaling of bound method parameters by [@fbbdev](https://github.com/fbbdev) in [#3431](https://github.com/wailsapp/wails/pull/3431)
|
||||
- Fix handling of multiple return values from bound methods by [@fbbdev](https://github.com/fbbdev) in [#3431](https://github.com/wailsapp/wails/pull/3431)
|
||||
- Fix doctor detection of npm that is not installed with system package manager by [@pekim](https://github.com/pekim) in [#3458](https://github.com/wailsapp/wails/pull/3458)
|
||||
|
||||
### Changed
|
||||
|
||||
- Update linux webkit dependency to webkit2gtk-4.1 over webkitgtk2-4.0 to support Ubuntu 24.04 LTS by [atterpac](https://github.com/atterpac) in [#3461](https://github.com/wailsapp/wails/pull/3461)
|
||||
- The bundled JS runtime script is now an ESM module: script tags importing it must have the `type="module"` attribute. By [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- The `@wailsio/runtime` package does not publish its API on the `window.wails` object, and does not start the WML system. This has been done to improve encapsulation. The WML system can be started manually if desired by calling the new `WML.Enable` method. The bundled JS runtime script still performs both operations automatically. By [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- The Window API module `@wailsio/runtime/src/window` now exposes the containing window object as a default export. It is not possible anymore to import individual methods through ESM named or namespace import syntax.
|
||||
- The JS window API has been updated to match the current Go `WebviewWindow` API. Some methods have changed name or prototype, specifically: `Screen` becomes `GetScreen`; `GetZoomLevel`/`SetZoomLevel` become `GetZoom`/`SetZoom`; `GetZoom`, `Width` and `Height` now return values directly instead of wrapping them within objects. By [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- The binding generator now uses calls by ID by default. The `-id` CLI option has been removed. Use the `-names` CLI option to switch back to calls by name. By [@fbbdev](https://github.com/fbbdev) in [#3468](https://github.com/wailsapp/wails/pull/3468)
|
||||
- New binding code layout: output files were previously organised in folders named after their containing package; now full Go import paths are used, including the module path. By [@fbbdev](https://github.com/fbbdev) in [#3468](https://github.com/wailsapp/wails/pull/3468)
|
||||
- The struct field `application.Options.Bind` has been renamed to `application.Options.Services`. By [@fbbdev](https://github.com/fbbdev) in [#3468](https://github.com/wailsapp/wails/pull/3468)
|
||||
- New syntax for binding services: service instances must now be wrapped in a call to `application.NewService`. By [@fbbdev](https://github.com/fbbdev) in [#3468](https://github.com/wailsapp/wails/pull/3468)
|
||||
- Modified the `contentTypeSniffer` struct to include the `http.CloseNotifier` interface. Now compatible with Gin framework. By [@AnalogJ](https://github.com/AnalogJ) in [#3537](https://github.com/wailsapp/wails/pull/3537)
|
||||
|
||||
### Removed
|
||||
|
||||
### Deprecated
|
||||
|
||||
### Security
|
||||
123
scripts/AUTOMATION-README.md
Normal file
123
scripts/AUTOMATION-README.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# Wails Issue Management Automation
|
||||
|
||||
This directory contains automation workflows and scripts to help manage the Wails project with minimal time investment.
|
||||
|
||||
## GitHub Workflow Files
|
||||
|
||||
### 1. Auto-Label Issues (`auto-label-issues.yml`)
|
||||
- Automatically labels issues and PRs based on their content and modified files
|
||||
- Labels are defined in `issue-labeler.yml` and `file-labeler.yml`
|
||||
- Activates when issues are opened, edited, or reopened
|
||||
|
||||
### 2. Issue Triage Automation (`issue-triage-automation.yml`)
|
||||
- Performs automated actions for issue triage
|
||||
- Requests more info for incomplete bug reports
|
||||
- Prioritizes security issues
|
||||
- Adds issues to appropriate project boards
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### 1. Issue Content Labeler (`issue-labeler.yml`)
|
||||
- Defines patterns to match in issue title/body
|
||||
- Categorizes by version (v2/v3), component, type, and priority
|
||||
- Customize patterns as needed for your project
|
||||
|
||||
### 2. File Path Labeler (`file-labeler.yml`)
|
||||
- Labels PRs based on which files they modify
|
||||
- Helps identify which areas of the codebase are affected
|
||||
- Customize file patterns as needed
|
||||
|
||||
### 3. Stale Issues Config (`stale.yml`)
|
||||
- Marks issues as stale after 45 days of inactivity
|
||||
- Closes stale issues after an additional 10 days
|
||||
- Exempts issues with important labels
|
||||
|
||||
## Helper Scripts
|
||||
|
||||
### 1. Issue Triage Script (`scripts/issue-triage.ps1`)
|
||||
- PowerShell script to quickly triage issues
|
||||
- Lists recent issues needing attention
|
||||
- Provides easy keyboard shortcuts for common actions
|
||||
- Run during your dedicated issue triage time
|
||||
|
||||
### 2. PR Review Helper (`scripts/pr-review-helper.ps1`)
|
||||
- PowerShell script to efficiently review PRs
|
||||
- Generates review checklists
|
||||
- Provides easy shortcuts for common review actions
|
||||
- Run during your dedicated PR review time
|
||||
|
||||
## How to Use This System
|
||||
|
||||
### Daily Workflow (2 hours max)
|
||||
|
||||
**Monday (120 min):**
|
||||
1. Run `scripts/issue-triage.ps1` (30 min)
|
||||
2. Run `scripts/pr-review-helper.ps1` (30 min)
|
||||
3. Check Discord for critical discussions (30 min)
|
||||
4. Plan your week (30 min)
|
||||
|
||||
**Tuesday-Wednesday (120 min/day):**
|
||||
1. Quick check for urgent issues (10 min)
|
||||
2. v3 development (110 min)
|
||||
|
||||
**Thursday (120 min):**
|
||||
1. v2 maintenance (90 min)
|
||||
2. Documentation updates (30 min)
|
||||
|
||||
**Friday (120 min):**
|
||||
1. Run `scripts/pr-review-helper.ps1` (60 min)
|
||||
2. Discord updates/newsletter (30 min)
|
||||
3. Weekly reflection (30 min)
|
||||
|
||||
## Installation
|
||||
|
||||
1. The GitHub workflow files should be placed in `.github/workflows/`
|
||||
2. Configuration files should be placed in `.github/`
|
||||
3. Helper scripts should be placed in `scripts/`
|
||||
4. Make sure you have GitHub CLI (`gh`) installed and authenticated
|
||||
|
||||
## Customization
|
||||
|
||||
Feel free to modify the configuration files and scripts to better suit your project's needs:
|
||||
|
||||
1. **Adding New Label Categories**:
|
||||
- Add new patterns to `issue-labeler.yml` for additional components or types
|
||||
- Update `file-labeler.yml` if you add new directories or file types
|
||||
|
||||
2. **Adjusting Automation Thresholds**:
|
||||
- Modify `stale.yml` to change how long issues remain active
|
||||
- Update `issue-triage-automation.yml` to change conditions for automated actions
|
||||
|
||||
3. **Customizing Scripts**:
|
||||
- Update the scripts with your specific GitHub username
|
||||
- Add additional actions based on your workflow preferences
|
||||
- Adjust time allocations based on which tasks need more attention
|
||||
|
||||
## Benefits
|
||||
|
||||
This automated issue management system will:
|
||||
|
||||
1. **Save Time**: Reduce manual triage of most common issues
|
||||
2. **Improve Consistency**: Apply the same categorization rules every time
|
||||
3. **Increase Visibility**: Clear categorization helps community members find issues
|
||||
4. **Focus Development**: Clearer separation of v2 and v3 work
|
||||
5. **Reduce Backlog**: Better management of stale issues
|
||||
6. **Streamline Reviews**: Faster PR processing with guided workflows
|
||||
|
||||
## Requirements
|
||||
|
||||
- GitHub CLI (`gh`) installed and authenticated
|
||||
- PowerShell 5.1+ for Windows scripts
|
||||
- GitHub Actions enabled on your repository
|
||||
- Appropriate permissions to modify workflows
|
||||
|
||||
## Maintenance
|
||||
|
||||
This system requires minimal maintenance:
|
||||
|
||||
- Periodically review and update label patterns as your project evolves
|
||||
- Adjust time allocations based on where you need to focus
|
||||
- Update scripts if GitHub CLI commands change
|
||||
- Customize the workflow as you find pain points in your process
|
||||
|
||||
Remember that the goal is to maximize your limited time (2 hours per day) by automating repetitive tasks and streamlining essential ones.
|
||||
108
scripts/issue-triage.ps1
Normal file
108
scripts/issue-triage.ps1
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# issue-triage.ps1 - Script to help with quick issue triage
|
||||
# Run this at the start of your GitHub time to quickly process issues
|
||||
|
||||
# Set your GitHub username
|
||||
$GITHUB_USERNAME = "your-username"
|
||||
|
||||
# Get the latest 10 open issues that aren't assigned and aren't labeled as "awaiting feedback"
|
||||
Write-Host "Fetching recent unprocessed issues..."
|
||||
gh issue list --repo wailsapp/wails --limit 10 --json number,title,labels,assignees | Out-File -Encoding utf8 -FilePath "issues_temp.json"
|
||||
$issues = Get-Content -Raw -Path "issues_temp.json" | ConvertFrom-Json
|
||||
$newIssues = $issues | Where-Object {
|
||||
$_.assignees.Count -eq 0 -and
|
||||
($_.labels.Count -eq 0 -or -not ($_.labels | Where-Object { $_.name -eq "awaiting feedback" }))
|
||||
}
|
||||
|
||||
# Process each issue
|
||||
Write-Host "`n===== Issues Needing Triage =====`n"
|
||||
foreach ($issue in $newIssues) {
|
||||
$number = $issue.number
|
||||
$title = $issue.title
|
||||
$labelNames = $issue.labels | ForEach-Object { $_.name }
|
||||
$labelsStr = if ($labelNames) { $labelNames -join ", " } else { "none" }
|
||||
|
||||
Write-Host "Issue #$number`: $title"
|
||||
Write-Host "Labels: $labelsStr`n"
|
||||
|
||||
$continue = $true
|
||||
while ($continue) {
|
||||
Write-Host "Options:"
|
||||
Write-Host " [v] View issue in browser"
|
||||
Write-Host " [2] Add v2-only label"
|
||||
Write-Host " [3] Add v3-alpha label"
|
||||
Write-Host " [b] Add bug label"
|
||||
Write-Host " [e] Add enhancement label"
|
||||
Write-Host " [d] Add documentation label"
|
||||
Write-Host " [w] Add webview2 label"
|
||||
Write-Host " [f] Request more info (awaiting feedback)"
|
||||
Write-Host " [c] Close issue (duplicate/invalid)"
|
||||
Write-Host " [a] Assign to yourself"
|
||||
Write-Host " [s] Skip to next issue"
|
||||
Write-Host " [q] Quit script"
|
||||
$action = Read-Host "Enter action"
|
||||
|
||||
switch ($action) {
|
||||
"v" {
|
||||
gh issue view $number --repo wailsapp/wails --web
|
||||
}
|
||||
"2" {
|
||||
Write-Host "Adding v2-only label..."
|
||||
gh issue edit $number --repo wailsapp/wails --add-label "v2-only"
|
||||
}
|
||||
"3" {
|
||||
Write-Host "Adding v3-alpha label..."
|
||||
gh issue edit $number --repo wailsapp/wails --add-label "v3-alpha"
|
||||
}
|
||||
"b" {
|
||||
Write-Host "Adding bug label..."
|
||||
gh issue edit $number --repo wailsapp/wails --add-label "Bug"
|
||||
}
|
||||
"e" {
|
||||
Write-Host "Adding enhancement label..."
|
||||
gh issue edit $number --repo wailsapp/wails --add-label "Enhancement"
|
||||
}
|
||||
"d" {
|
||||
Write-Host "Adding documentation label..."
|
||||
gh issue edit $number --repo wailsapp/wails --add-label "Documentation"
|
||||
}
|
||||
"w" {
|
||||
Write-Host "Adding webview2 label..."
|
||||
gh issue edit $number --repo wailsapp/wails --add-label "webview2"
|
||||
}
|
||||
"f" {
|
||||
Write-Host "Requesting more info..."
|
||||
gh issue comment $number --repo wailsapp/wails --body "Thank you for reporting this issue. Could you please provide additional information to help us investigate?`n`n- [Specific details needed]`n`nThis will help us address your issue more effectively."
|
||||
gh issue edit $number --repo wailsapp/wails --add-label "awaiting feedback"
|
||||
}
|
||||
"c" {
|
||||
$reason = Read-Host "Reason for closing (duplicate/invalid/etc)"
|
||||
gh issue comment $number --repo wailsapp/wails --body "Closing this issue: $reason"
|
||||
gh issue close $number --repo wailsapp/wails
|
||||
}
|
||||
"a" {
|
||||
Write-Host "Assigning to yourself..."
|
||||
gh issue edit $number --repo wailsapp/wails --add-assignee "$GITHUB_USERNAME"
|
||||
}
|
||||
"s" {
|
||||
Write-Host "Skipping to next issue..."
|
||||
$continue = $false
|
||||
}
|
||||
"q" {
|
||||
Write-Host "Exiting script."
|
||||
exit
|
||||
}
|
||||
default {
|
||||
Write-Host "Invalid option. Please try again."
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
Write-Host "--------------------------------`n"
|
||||
}
|
||||
|
||||
Write-Host "No more issues to triage!"
|
||||
|
||||
# Clean up temp file
|
||||
Remove-Item -Path "issues_temp.json"
|
||||
103
scripts/issue-triage.sh
Normal file
103
scripts/issue-triage.sh
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#!/bin/bash
|
||||
# issue-triage.sh - Script to help with quick issue triage
|
||||
# Run this at the start of your GitHub time to quickly process issues
|
||||
|
||||
# Set your GitHub username
|
||||
GITHUB_USERNAME="your-username"
|
||||
|
||||
# Get the latest 10 open issues that aren't assigned and aren't labeled as "awaiting feedback"
|
||||
echo "Fetching recent unprocessed issues..."
|
||||
gh issue list --repo wailsapp/wails --limit 10 --json number,title,labels,assignees --jq '.[] | select(.assignees | length == 0) | select(any(.labels[]; .name != "awaiting feedback"))' > new_issues.json
|
||||
|
||||
# Process each issue
|
||||
echo -e "\n===== Issues Needing Triage =====\n"
|
||||
cat new_issues.json | jq -c '.[]' | while read -r issue; do
|
||||
number=$(echo $issue | jq -r '.number')
|
||||
title=$(echo $issue | jq -r '.title')
|
||||
labels=$(echo $issue | jq -r '.labels[] | .name' 2>/dev/null | tr '\n' ', ' | sed 's/,$//')
|
||||
|
||||
if [ -z "$labels" ]; then
|
||||
labels="none"
|
||||
fi
|
||||
|
||||
echo -e "Issue #$number: $title"
|
||||
echo -e "Labels: $labels\n"
|
||||
|
||||
while true; do
|
||||
echo "Options:"
|
||||
echo " [v] View issue in browser"
|
||||
echo " [2] Add v2-only label"
|
||||
echo " [3] Add v3-alpha label"
|
||||
echo " [b] Add bug label"
|
||||
echo " [e] Add enhancement label"
|
||||
echo " [d] Add documentation label"
|
||||
echo " [w] Add webview2 label"
|
||||
echo " [f] Request more info (awaiting feedback)"
|
||||
echo " [c] Close issue (duplicate/invalid)"
|
||||
echo " [a] Assign to yourself"
|
||||
echo " [s] Skip to next issue"
|
||||
echo " [q] Quit script"
|
||||
read -p "Enter action: " action
|
||||
|
||||
case $action in
|
||||
v)
|
||||
gh issue view $number --repo wailsapp/wails --web
|
||||
;;
|
||||
2)
|
||||
echo "Adding v2-only label..."
|
||||
gh issue edit $number --repo wailsapp/wails --add-label "v2-only"
|
||||
;;
|
||||
3)
|
||||
echo "Adding v3-alpha label..."
|
||||
gh issue edit $number --repo wailsapp/wails --add-label "v3-alpha"
|
||||
;;
|
||||
b)
|
||||
echo "Adding bug label..."
|
||||
gh issue edit $number --repo wailsapp/wails --add-label "Bug"
|
||||
;;
|
||||
e)
|
||||
echo "Adding enhancement label..."
|
||||
gh issue edit $number --repo wailsapp/wails --add-label "Enhancement"
|
||||
;;
|
||||
d)
|
||||
echo "Adding documentation label..."
|
||||
gh issue edit $number --repo wailsapp/wails --add-label "Documentation"
|
||||
;;
|
||||
w)
|
||||
echo "Adding webview2 label..."
|
||||
gh issue edit $number --repo wailsapp/wails --add-label "webview2"
|
||||
;;
|
||||
f)
|
||||
echo "Requesting more info..."
|
||||
gh issue comment $number --repo wailsapp/wails --body "Thank you for reporting this issue. Could you please provide additional information to help us investigate?\n\n- [Specific details needed]\n\nThis will help us address your issue more effectively."
|
||||
gh issue edit $number --repo wailsapp/wails --add-label "awaiting feedback"
|
||||
;;
|
||||
c)
|
||||
read -p "Reason for closing (duplicate/invalid/etc): " reason
|
||||
gh issue comment $number --repo wailsapp/wails --body "Closing this issue: $reason"
|
||||
gh issue close $number --repo wailsapp/wails
|
||||
;;
|
||||
a)
|
||||
echo "Assigning to yourself..."
|
||||
gh issue edit $number --repo wailsapp/wails --add-assignee "$GITHUB_USERNAME"
|
||||
;;
|
||||
s)
|
||||
echo "Skipping to next issue..."
|
||||
break
|
||||
;;
|
||||
q)
|
||||
echo "Exiting script."
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option. Please try again."
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo -e "--------------------------------\n"
|
||||
done
|
||||
|
||||
echo "No more issues to triage!"
|
||||
152
scripts/pr-review-helper.ps1
Normal file
152
scripts/pr-review-helper.ps1
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
# pr-review-helper.ps1 - Script to help with efficient PR reviews
|
||||
# Run this during your PR review time
|
||||
|
||||
# Set your GitHub username
|
||||
$GITHUB_USERNAME = "your-username"
|
||||
|
||||
# Get open PRs that are ready for review
|
||||
Write-Host "Fetching PRs ready for review..."
|
||||
gh pr list --repo wailsapp/wails --json number,title,author,labels,reviewDecision,additions,deletions,baseRefName,headRefName --limit 10 | Out-File -Encoding utf8 -FilePath "prs_temp.json"
|
||||
$prs = Get-Content -Raw -Path "prs_temp.json" | ConvertFrom-Json
|
||||
|
||||
# Process each PR
|
||||
Write-Host "`n===== PRs Needing Review =====`n"
|
||||
foreach ($pr in $prs) {
|
||||
$number = $pr.number
|
||||
$title = $pr.title
|
||||
$author = $pr.author.login
|
||||
$labels = if ($pr.labels) { $pr.labels | ForEach-Object { $_.name } | Join-String -Separator ", " } else { "none" }
|
||||
$reviewState = if ($pr.reviewDecision) { $pr.reviewDecision } else { "PENDING" }
|
||||
$baseRef = $pr.baseRefName
|
||||
$headRef = $pr.headRefName
|
||||
$changes = $pr.additions + $pr.deletions
|
||||
|
||||
Write-Host "PR #$number`: $title"
|
||||
Write-Host "Author: $author"
|
||||
Write-Host "Labels: $labels"
|
||||
Write-Host "Branch: $headRef -> $baseRef"
|
||||
Write-Host "Changes: +$($pr.additions)/-$($pr.deletions) lines"
|
||||
Write-Host "Review state: $reviewState`n"
|
||||
|
||||
# Determine complexity based on size
|
||||
$complexity = if ($changes -lt 50) {
|
||||
"Quick review"
|
||||
} elseif ($changes -lt 300) {
|
||||
"Moderate review"
|
||||
} else {
|
||||
"Extensive review"
|
||||
}
|
||||
|
||||
Write-Host "Complexity: $complexity"
|
||||
|
||||
$continue = $true
|
||||
while ($continue) {
|
||||
Write-Host "`nOptions:"
|
||||
Write-Host " [v] View PR in browser"
|
||||
Write-Host " [d] View diff in browser"
|
||||
Write-Host " [c] Generate review checklist"
|
||||
Write-Host " [a] Approve PR"
|
||||
Write-Host " [r] Request changes"
|
||||
Write-Host " [m] Add comment"
|
||||
Write-Host " [l] Add labels"
|
||||
Write-Host " [s] Skip to next PR"
|
||||
Write-Host " [q] Quit script"
|
||||
$action = Read-Host "Enter action"
|
||||
|
||||
switch ($action) {
|
||||
"v" {
|
||||
gh pr view $number --repo wailsapp/wails --web
|
||||
}
|
||||
"d" {
|
||||
gh pr diff $number --repo wailsapp/wails --web
|
||||
}
|
||||
"c" {
|
||||
# Generate review checklist
|
||||
$checklist = @"
|
||||
## PR Review: $title
|
||||
|
||||
### Basic Checks:
|
||||
- [ ] PR title is descriptive
|
||||
- [ ] PR description explains the changes
|
||||
- [ ] Related issues are linked
|
||||
|
||||
### Technical Checks:
|
||||
- [ ] Code follows project style
|
||||
- [ ] No unnecessary commented code
|
||||
- [ ] Error handling is appropriate
|
||||
- [ ] Documentation updated (if needed)
|
||||
- [ ] Tests included (if needed)
|
||||
|
||||
### Impact Assessment:
|
||||
- [ ] Changes are backward compatible (if applicable)
|
||||
- [ ] No breaking changes to public APIs
|
||||
- [ ] Performance impact considered
|
||||
|
||||
### Version Specific:
|
||||
"@
|
||||
|
||||
if ($baseRef -eq "master") {
|
||||
$checklist += @"
|
||||
|
||||
- [ ] Appropriate for v2 maintenance
|
||||
- [ ] No features that should be v3-only
|
||||
"@
|
||||
} elseif ($baseRef -eq "v3-alpha") {
|
||||
$checklist += @"
|
||||
|
||||
- [ ] Appropriate for v3 development
|
||||
- [ ] Aligns with v3 roadmap
|
||||
"@
|
||||
}
|
||||
|
||||
# Write to clipboard
|
||||
$checklist | Set-Clipboard
|
||||
Write-Host "`nReview checklist copied to clipboard!`n"
|
||||
}
|
||||
"a" {
|
||||
$comment = Read-Host "Approval comment (blank for none)"
|
||||
if ($comment) {
|
||||
gh pr review $number --repo wailsapp/wails --approve --body $comment
|
||||
} else {
|
||||
gh pr review $number --repo wailsapp/wails --approve
|
||||
}
|
||||
}
|
||||
"r" {
|
||||
$comment = Read-Host "Feedback for changes requested"
|
||||
gh pr review $number --repo wailsapp/wails --request-changes --body $comment
|
||||
}
|
||||
"m" {
|
||||
$comment = Read-Host "Comment text"
|
||||
gh pr comment $number --repo wailsapp/wails --body $comment
|
||||
}
|
||||
"l" {
|
||||
$labels = Read-Host "Labels to add (comma-separated)"
|
||||
$labelArray = $labels -split ","
|
||||
foreach ($label in $labelArray) {
|
||||
$labelTrimmed = $label.Trim()
|
||||
if ($labelTrimmed) {
|
||||
gh pr edit $number --repo wailsapp/wails --add-label $labelTrimmed
|
||||
}
|
||||
}
|
||||
}
|
||||
"s" {
|
||||
Write-Host "Skipping to next PR..."
|
||||
$continue = $false
|
||||
}
|
||||
"q" {
|
||||
Write-Host "Exiting script."
|
||||
exit
|
||||
}
|
||||
default {
|
||||
Write-Host "Invalid option. Please try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "--------------------------------`n"
|
||||
}
|
||||
|
||||
Write-Host "No more PRs to review!"
|
||||
|
||||
# Clean up temp file
|
||||
Remove-Item -Path "prs_temp.json"
|
||||
70
test-changelog-extraction.sh
Executable file
70
test-changelog-extraction.sh
Executable file
|
|
@ -0,0 +1,70 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Test script for changelog extraction logic
|
||||
set -e
|
||||
|
||||
echo "🧪 Testing Changelog Extraction Logic"
|
||||
echo "======================================"
|
||||
|
||||
# Test v2 changelog extraction
|
||||
echo "📋 Testing v2 changelog extraction..."
|
||||
CHANGELOG_FILE="website/src/pages/changelog.mdx"
|
||||
|
||||
if [ ! -f "$CHANGELOG_FILE" ]; then
|
||||
echo "❌ v2 changelog file not found: $CHANGELOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ v2 changelog file found"
|
||||
|
||||
# Extract unreleased section
|
||||
awk '
|
||||
/^## \[Unreleased\]/ { found=1; next }
|
||||
found && /^## / { exit }
|
||||
found && !/^$/ { print }
|
||||
' $CHANGELOG_FILE > test_v2_notes.md
|
||||
|
||||
echo "📝 v2 extracted content:"
|
||||
echo "------------------------"
|
||||
if [ -s test_v2_notes.md ]; then
|
||||
head -10 test_v2_notes.md
|
||||
echo "..."
|
||||
echo "✅ v2 changelog extraction successful ($(wc -l < test_v2_notes.md) lines)"
|
||||
else
|
||||
echo "⚠️ v2 unreleased section is empty"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Test v3 changelog extraction (when on v3-alpha branch)
|
||||
echo "📋 Testing v3 changelog extraction..."
|
||||
|
||||
# Check if we can access v3 changelog
|
||||
if git show v3-alpha:docs/src/content/docs/changelog.mdx > /dev/null 2>&1; then
|
||||
echo "✅ v3 changelog accessible from v3-alpha branch"
|
||||
|
||||
# Extract from v3-alpha branch
|
||||
git show v3-alpha:docs/src/content/docs/changelog.mdx | awk '
|
||||
/^## \[Unreleased\]/ { found=1; next }
|
||||
found && /^## / { exit }
|
||||
found && !/^$/ { print }
|
||||
' > test_v3_notes.md
|
||||
|
||||
echo "📝 v3 extracted content:"
|
||||
echo "------------------------"
|
||||
if [ -s test_v3_notes.md ]; then
|
||||
head -10 test_v3_notes.md
|
||||
echo "..."
|
||||
echo "✅ v3 changelog extraction successful ($(wc -l < test_v3_notes.md) lines)"
|
||||
else
|
||||
echo "⚠️ v3 unreleased section is empty"
|
||||
fi
|
||||
else
|
||||
echo "⚠️ v3 changelog not accessible (expected if not on v3-alpha branch)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🧹 Cleaning up test files..."
|
||||
rm -f test_v2_notes.md test_v3_notes.md
|
||||
|
||||
echo "✅ Changelog extraction test completed!"
|
||||
85
test-version-logic.sh
Executable file
85
test-version-logic.sh
Executable file
|
|
@ -0,0 +1,85 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Test script for version increment logic
|
||||
set -e
|
||||
|
||||
echo "🧪 Testing Version Increment Logic"
|
||||
echo "=================================="
|
||||
|
||||
# Test v2 version increment
|
||||
echo "📈 Testing v2 version increment..."
|
||||
|
||||
# Get current v2 version
|
||||
CURRENT_V2=$(cat v2/cmd/wails/internal/version.txt | sed 's/^v//')
|
||||
echo "Current v2 version: v$CURRENT_V2"
|
||||
|
||||
# Parse version parts
|
||||
IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_V2"
|
||||
MAJOR=${VERSION_PARTS[0]}
|
||||
MINOR=${VERSION_PARTS[1]}
|
||||
PATCH=${VERSION_PARTS[2]}
|
||||
|
||||
echo "Parsed: MAJOR=$MAJOR, MINOR=$MINOR, PATCH=$PATCH"
|
||||
|
||||
# Test patch increment
|
||||
PATCH_VERSION="$MAJOR.$MINOR.$((PATCH + 1))"
|
||||
echo "✅ Patch increment: v$CURRENT_V2 → v$PATCH_VERSION"
|
||||
|
||||
# Test minor increment
|
||||
MINOR_VERSION="$MAJOR.$((MINOR + 1)).0"
|
||||
echo "✅ Minor increment: v$CURRENT_V2 → v$MINOR_VERSION"
|
||||
|
||||
# Test major increment
|
||||
MAJOR_VERSION="$((MAJOR + 1)).0.0"
|
||||
echo "✅ Major increment: v$CURRENT_V2 → v$MAJOR_VERSION"
|
||||
|
||||
echo ""
|
||||
|
||||
# Test v3 version increment (simulate)
|
||||
echo "📈 Testing v3 version increment..."
|
||||
|
||||
# Simulate current v3 version
|
||||
CURRENT_V3="v3.0.0-alpha.9"
|
||||
echo "Simulated current v3 version: $CURRENT_V3"
|
||||
|
||||
if [[ $CURRENT_V3 =~ v3\.0\.0-alpha\.([0-9]+) ]]; then
|
||||
ALPHA_NUM=${BASH_REMATCH[1]}
|
||||
NEW_ALPHA_NUM=$((ALPHA_NUM + 1))
|
||||
NEW_V3_VERSION="v3.0.0-alpha.$NEW_ALPHA_NUM"
|
||||
echo "✅ Alpha increment: $CURRENT_V3 → $NEW_V3_VERSION"
|
||||
else
|
||||
echo "❌ Failed to parse v3 version format"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Test conventional commit detection
|
||||
echo "🔍 Testing Conventional Commit Detection..."
|
||||
|
||||
# Simulate commit messages
|
||||
COMMITS="
|
||||
feat: add new dialog API
|
||||
fix: resolve memory leak
|
||||
chore: update dependencies
|
||||
feat!: remove deprecated API
|
||||
docs: update README
|
||||
BREAKING CHANGE: remove v1 compatibility
|
||||
"
|
||||
|
||||
echo "Test commits:"
|
||||
echo "$COMMITS"
|
||||
|
||||
# Test release type detection
|
||||
if echo "$COMMITS" | grep -q "feat!\|fix!\|BREAKING CHANGE:"; then
|
||||
RELEASE_TYPE="major"
|
||||
elif echo "$COMMITS" | grep -q "feat\|BREAKING CHANGE"; then
|
||||
RELEASE_TYPE="minor"
|
||||
else
|
||||
RELEASE_TYPE="patch"
|
||||
fi
|
||||
|
||||
echo "✅ Detected release type: $RELEASE_TYPE"
|
||||
|
||||
echo ""
|
||||
echo "✅ Version logic test completed!"
|
||||
61
test-workflow.md
Normal file
61
test-workflow.md
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# Testing the Nightly Release Workflow
|
||||
|
||||
## Method 1: Fork Testing (Recommended)
|
||||
|
||||
1. **Create a fork** of the Wails repository
|
||||
2. **Push the workflow** to your fork
|
||||
3. **Test manually** using `workflow_dispatch`
|
||||
4. **Verify behavior** without affecting main repo
|
||||
|
||||
```bash
|
||||
# In your fork
|
||||
git remote add upstream https://github.com/wailsapp/wails.git
|
||||
git push origin master # Push workflow to your fork
|
||||
```
|
||||
|
||||
## Method 2: Local Script Testing
|
||||
|
||||
Create local test scripts to validate the logic:
|
||||
|
||||
```bash
|
||||
# Test changelog parsing
|
||||
./test-changelog-extraction.sh
|
||||
|
||||
# Test version increment logic
|
||||
./test-version-logic.sh
|
||||
|
||||
# Test commit analysis
|
||||
./test-commit-detection.sh
|
||||
```
|
||||
|
||||
## Method 3: Dry Run Workflow
|
||||
|
||||
Add a `dry_run` input parameter to test without creating releases:
|
||||
|
||||
```yaml
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: 'Run in dry-run mode (no releases created)'
|
||||
default: true
|
||||
type: boolean
|
||||
```
|
||||
|
||||
## Method 4: Act (GitHub Actions Local Runner)
|
||||
|
||||
Use `act` to run GitHub Actions locally:
|
||||
|
||||
```bash
|
||||
brew install act
|
||||
act workflow_dispatch -W .github/workflows/nightly-releases.yml
|
||||
```
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Changelog parsing works correctly
|
||||
- [ ] Version increment logic is accurate
|
||||
- [ ] Conventional commit detection works
|
||||
- [ ] Release notes format properly
|
||||
- [ ] Authorization checks function
|
||||
- [ ] Branch handling (master vs v3-alpha)
|
||||
- [ ] Error handling and fallbacks
|
||||
|
|
@ -29,11 +29,11 @@ require (
|
|||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.10 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/wailsapp/wails/v2 v2.1.0 => ../..
|
||||
|
|
|
|||
|
|
@ -63,11 +63,13 @@ github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhw
|
|||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/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=
|
||||
|
|
@ -80,10 +82,12 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
|||
|
|
@ -10,25 +10,25 @@ require (
|
|||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
||||
github.com/labstack/echo/v4 v4.9.1 // indirect
|
||||
github.com/labstack/echo/v4 v4.10.2 // indirect
|
||||
github.com/labstack/gommon v0.4.0 // indirect
|
||||
github.com/leaanthony/go-ansi-parser v1.6.0 // indirect
|
||||
github.com/leaanthony/gosod v1.0.3 // indirect
|
||||
github.com/leaanthony/slicer v1.6.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.2 // indirect
|
||||
github.com/samber/lo v1.27.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/samber/lo v1.38.1 // indirect
|
||||
github.com/tkrajina/go-reflector v0.5.6 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4P
|
|||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||
github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y=
|
||||
github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo=
|
||||
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
|
||||
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
|
||||
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
|
||||
|
|
@ -32,6 +33,7 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
|||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
|
@ -41,8 +43,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
||||
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/samber/lo v1.27.1 h1:sTXwkRiIFIQG+G0HeAvOEnGjqWeWtI9cg5/n51KrxPg=
|
||||
github.com/samber/lo v1.27.1/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
|
|
@ -53,17 +57,22 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
|
|||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
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/v2 v2.3.1 h1:ZJz+pyIBKyASkgO8JO31NuHO1gTTHmvwiHYHwei1CqM=
|
||||
github.com/wailsapp/wails/v2 v2.3.1/go.mod h1:zlNLI0E2c2qA6miiuAHtp0Bac8FaGH0tlhA19OssR/8=
|
||||
github.com/wailsapp/wails/v2 v2.8.0/go.mod h1:EFUGWkUX3KofO4fmKR/GmsLy3HhPH7NbyOEaMt8lBF0=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/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=
|
||||
|
|
@ -73,8 +82,10 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
|
|
|
|||
1
v3/.gitignore
vendored
1
v3/.gitignore
vendored
|
|
@ -8,3 +8,4 @@ cmd/wails3/wails
|
|||
/examples/plain/plain
|
||||
/cmd/wails3/ui/.task/
|
||||
!internal/commands/webview2/MicrosoftEdgeWebview2Setup.exe
|
||||
internal/commands/appimage_testfiles/appimage_testfiles
|
||||
452
v3/TESTING.md
Normal file
452
v3/TESTING.md
Normal file
|
|
@ -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/)
|
||||
259
v3/Taskfile.yaml
259
v3/Taskfile.yaml
|
|
@ -71,61 +71,298 @@ tasks:
|
|||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- echo "Building example {{.DIR}}"
|
||||
- echo "Building example {{.DIR}} for Darwin"
|
||||
- go mod tidy
|
||||
- go build -o "testbuild-{{.DIR}}{{exeExt}}"
|
||||
- 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:notdarwin:
|
||||
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
|
||||
- windows
|
||||
cmds:
|
||||
- echo "Building example {{.DIR}}"
|
||||
- echo "Building example {{.DIR}} for Linux"
|
||||
- go mod tidy
|
||||
- go build -o "testbuild-{{.DIR}}{{exeExt}}"
|
||||
- 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:examples:
|
||||
summary: Builds the examples
|
||||
dir: examples
|
||||
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
|
||||
html-dnd-api
|
||||
ignore-mouse
|
||||
keybindings
|
||||
menu
|
||||
notifications
|
||||
panic-handling
|
||||
plain
|
||||
raw-message
|
||||
screen
|
||||
services
|
||||
show-macos-toolbar
|
||||
single-instance
|
||||
systray-basic
|
||||
systray-menu
|
||||
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:notdarwin
|
||||
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
|
||||
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
|
||||
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"
|
||||
|
|
|
|||
53
v3/UNRELEASED_CHANGELOG.md
Normal file
53
v3/UNRELEASED_CHANGELOG.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# Unreleased Changes
|
||||
|
||||
<!--
|
||||
This file is used to collect changelog entries for the next v3-alpha release.
|
||||
Add your changes under the appropriate sections below.
|
||||
|
||||
Guidelines:
|
||||
- Follow the "Keep a Changelog" format (https://keepachangelog.com/)
|
||||
- Write clear, concise descriptions of changes
|
||||
- Include the impact on users when relevant
|
||||
- Use present tense ("Add feature" not "Added feature")
|
||||
- Reference issue/PR numbers when applicable
|
||||
|
||||
This file is automatically processed by the nightly release workflow.
|
||||
After processing, the content will be moved to the main changelog and this file will be reset.
|
||||
-->
|
||||
|
||||
## Added
|
||||
<!-- New features, capabilities, or enhancements -->
|
||||
|
||||
## Changed
|
||||
<!-- Changes in existing functionality -->
|
||||
|
||||
## Fixed
|
||||
<!-- Bug fixes -->
|
||||
|
||||
## Deprecated
|
||||
<!-- Soon-to-be removed features -->
|
||||
|
||||
## Removed
|
||||
<!-- Features removed in this release -->
|
||||
|
||||
## Security
|
||||
<!-- Security-related changes -->
|
||||
|
||||
---
|
||||
|
||||
### 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
|
||||
|
|
@ -21,13 +21,13 @@ It can be used to generate many things including:
|
|||
|
||||
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 | |
|
||||
| Flag | Type | Description | Default |
|
||||
|--------------------|--------|------------------------------------------------------|----------------------|
|
||||
| `-example` | bool | Generates example icon file (appicon.png) | |
|
||||
| `-input` | string | The input image file | |
|
||||
| `-sizes` | string | The sizes to generate in .ico file (comma separated) | "256,128,64,48,32,16" |
|
||||
| `-windowsFilename` | string | The output filename for the Windows icon | icons.ico |
|
||||
| `-macFilename` | string | The output filename for the Mac icon bundle | icons.icns |
|
||||
| `-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
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ func main() {
|
|||
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)
|
||||
|
||||
app.NewSubCommandFunction("version", "Print the version", commands.Version)
|
||||
app.NewSubCommand("sponsor", "Sponsor the project").Action(openSponsor)
|
||||
|
|
|
|||
128
v3/examples/badge-custom/README.md
Normal file
128
v3/examples/badge-custom/README.md
Normal file
|
|
@ -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()
|
||||
```
|
||||
34
v3/examples/badge-custom/Taskfile.yml
Normal file
34
v3/examples/badge-custom/Taskfile.yml
Normal file
|
|
@ -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}}
|
||||
|
||||
86
v3/examples/badge-custom/build/Taskfile.yml
Normal file
86
v3/examples/badge-custom/build/Taskfile.yml
Normal file
|
|
@ -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 .
|
||||
BIN
v3/examples/badge-custom/build/appicon.png
Normal file
BIN
v3/examples/badge-custom/build/appicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
63
v3/examples/badge-custom/build/config.yml
Normal file
63
v3/examples/badge-custom/build/config.yml
Normal file
|
|
@ -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
|
||||
32
v3/examples/badge-custom/build/darwin/Info.dev.plist
Normal file
32
v3/examples/badge-custom/build/darwin/Info.dev.plist
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>My Product</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>badge</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.wails.badge</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.1.0</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>This is a comment</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.1.0</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>icons</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.15.0</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>true</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>© now, My Company</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
27
v3/examples/badge-custom/build/darwin/Info.plist
Normal file
27
v3/examples/badge-custom/build/darwin/Info.plist
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>My Product</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>badge</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.wails.badge</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.1.0</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>This is a comment</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.1.0</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>icons</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.15.0</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>true</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>© now, My Company</string>
|
||||
</dict>
|
||||
</plist>
|
||||
81
v3/examples/badge-custom/build/darwin/Taskfile.yml
Normal file
81
v3/examples/badge-custom/build/darwin/Taskfile.yml
Normal file
|
|
@ -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}}'
|
||||
BIN
v3/examples/badge-custom/build/darwin/icons.icns
Normal file
BIN
v3/examples/badge-custom/build/darwin/icons.icns
Normal file
Binary file not shown.
119
v3/examples/badge-custom/build/linux/Taskfile.yml
Normal file
119
v3/examples/badge-custom/build/linux/Taskfile.yml
Normal file
|
|
@ -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}}'
|
||||
35
v3/examples/badge-custom/build/linux/appimage/build.sh
Normal file
35
v3/examples/badge-custom/build/linux/appimage/build.sh
Normal file
|
|
@ -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"
|
||||
|
||||
50
v3/examples/badge-custom/build/linux/nfpm/nfpm.yaml
Normal file
50
v3/examples/badge-custom/build/linux/nfpm/nfpm.yaml
Normal file
|
|
@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue